diff options
736 files changed, 26319 insertions, 8223 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index a271d063d1f9..b1f587e45a6d 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -15,6 +15,7 @@ aconfig_srcjars = [ ":android.app.usage.flags-aconfig-java{.generated_srcjars}", ":android.content.pm.flags-aconfig-java{.generated_srcjars}", + ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", ":android.os.flags-aconfig-java{.generated_srcjars}", ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", @@ -36,6 +37,7 @@ aconfig_srcjars = [ ":hwui_flags_java_lib{.generated_srcjars}", ":display_flags_lib{.generated_srcjars}", ":android.multiuser.flags-aconfig-java{.generated_srcjars}", + ":android.app.flags-aconfig-java{.generated_srcjars}", ] filegroup { @@ -327,3 +329,29 @@ java_aconfig_library { aconfig_declarations: "android.multiuser.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Activity Manager +aconfig_declarations { + name: "android.app.flags-aconfig", + package: "android.app", + srcs: ["core/java/android/app/*.aconfig"], +} + +java_aconfig_library { + name: "android.app.flags-aconfig-java", + aconfig_declarations: "android.app.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + +// Broadcast Radio +aconfig_declarations { + name: "android.hardware.radio.flags-aconfig", + package: "android.hardware.radio", + srcs: ["core/java/android/hardware/radio/*.aconfig"], +} + +java_aconfig_library { + name: "android.hardware.radio.flags-aconfig-java", + aconfig_declarations: "android.hardware.radio.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index f1a3af27a633..4c44974a84d6 100644 --- a/Android.bp +++ b/Android.bp @@ -525,6 +525,7 @@ java_library { required: [ "framework-minus-apex", "framework-platform-compat-config", + "framework-location-compat-config", "services-platform-compat-config", "icu4j-platform-compat-config", "TeleService-platform-compat-config", diff --git a/TEST_MAPPING b/TEST_MAPPING index 122e627b7067..b215ee4e2537 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -68,7 +68,6 @@ "name": "FrameworksInputMethodSystemServerTests", "options": [ {"include-filter": "com.android.server.inputmethod"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING index b8fef63fa008..6924cb210adb 100644 --- a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING @@ -7,7 +7,6 @@ ], "options": [ {"include-filter": "com.android.server.DeviceIdleControllerTest"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] }, @@ -29,4 +28,4 @@ ] } ] -}
\ No newline at end of file +} diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING index b76c582cf287..c06861164d31 100644 --- a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING @@ -4,7 +4,6 @@ "name": "FrameworksMockingServicesTests", "options": [ {"include-filter": "com.android.server.DeviceIdleControllerTest"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] } @@ -17,4 +16,4 @@ ] } ] -}
\ No newline at end of file +} diff --git a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING index 8504b1f0bdb1..e649485ed5e5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/job/TEST_MAPPING @@ -3,8 +3,6 @@ { "name": "CtsJobSchedulerTestCases", "options": [ - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.LargeTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.LargeTest"} ] @@ -13,18 +11,16 @@ "name": "FrameworksMockingServicesTests", "options": [ {"include-filter": "com.android.server.job"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.LargeTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"} + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "androidx.test.filters.LargeTest"} ] }, { "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.job"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.LargeTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"} + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "androidx.test.filters.LargeTest"} ] } ], diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING index 73b00b6cfa24..e194b8dbe33d 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/tare/TEST_MAPPING @@ -4,7 +4,6 @@ "name": "FrameworksMockingServicesTests", "options": [ {"include-filter": "com.android.server.tare"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] }, @@ -12,7 +11,6 @@ "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.tare"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] } diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING index 9ec799f73b41..a75415ec6151 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING @@ -4,7 +4,6 @@ "name": "CtsUsageStatsTestCases", "options": [ {"include-filter": "android.app.usage.cts.UsageStatsTest"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.MediumTest"}, {"exclude-annotation": "androidx.test.filters.LargeTest"} @@ -21,7 +20,6 @@ "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.usage"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] } @@ -37,4 +35,4 @@ ] } ] -}
\ No newline at end of file +} diff --git a/api/Android.bp b/api/Android.bp index 6986ac09f89e..f017a47a4013 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -91,6 +91,7 @@ combined_apis { "framework-media", "framework-mediaprovider", "framework-ondevicepersonalization", + "framework-pdf", "framework-permission", "framework-permission-s", "framework-scheduling", diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 56a69b46b12c..e5e0ad377b03 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -86,6 +86,9 @@ module_libs = " --show-annotation android.annotation.SystemApi\\(" + droidstubs { name: "system-api-stubs-docs-non-updatable", + srcs: [ + ":framework-minus-apex-aconfig-srcjars", + ], defaults: [ "android-non-updatable-stubs-defaults", "module-classpath-stubs-defaults", @@ -126,6 +129,9 @@ droidstubs { droidstubs { name: "test-api-stubs-docs-non-updatable", + srcs: [ + ":framework-minus-apex-aconfig-srcjars", + ], defaults: [ "android-non-updatable-stubs-defaults", "module-classpath-stubs-defaults", @@ -173,6 +179,9 @@ droidstubs { droidstubs { name: "module-lib-api-stubs-docs-non-updatable", + srcs: [ + ":framework-minus-apex-aconfig-srcjars", + ], defaults: [ "android-non-updatable-stubs-defaults", "module-classpath-stubs-defaults", diff --git a/boot/Android.bp b/boot/Android.bp index 93d425e439a9..b33fab6e1a9f 100644 --- a/boot/Android.bp +++ b/boot/Android.bp @@ -208,6 +208,13 @@ custom_platform_bootclasspath { ], } +genrule { // This module exists to make the srcjar output available to Make. + name: "platform-bootclasspath.srcjar", + srcs: [":platform-bootclasspath{.srcjar}"], + out: ["platform-bootclasspath.srcjar"], + cmd: "cp $(in) $(out)", +} + platform_systemserverclasspath { name: "platform-systemserverclasspath", } diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md index 82df55509a7a..e7361fe95e8e 100644 --- a/cmds/uinput/README.md +++ b/cmds/uinput/README.md @@ -48,6 +48,7 @@ Register a new uinput device | `vid` | 16-bit integer | Vendor ID | | `pid` | 16-bit integer | Product ID | | `bus` | string | Bus that device should use | +| `port` | string | `phys` value to report | | `configuration` | object array | uinput device configuration| | `ff_effects_max` | integer | `ff_effects_max` value | | `abs_info` | array | Absolute axes information | diff --git a/core/api/current.txt b/core/api/current.txt index 22b1ef8ecd20..7fd25b2cb5fb 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4616,9 +4616,9 @@ package android.app { public class ActivityManager { method public int addAppTask(@NonNull android.app.Activity, @NonNull android.content.Intent, @Nullable android.app.ActivityManager.TaskDescription, @NonNull android.graphics.Bitmap); - method public void addStartInfoTimestamp(@IntRange(from=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START, to=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER) int, long); + method @FlaggedApi("android.app.app_start_info") public void addStartInfoTimestamp(@IntRange(from=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START, to=android.app.ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER) int, long); method public void appNotResponding(@NonNull String); - method public void clearApplicationStartInfoCompletionListener(); + method @FlaggedApi("android.app.app_start_info") public void clearApplicationStartInfoCompletionListener(); method public boolean clearApplicationUserData(); method public void clearWatchHeapLimit(); method @RequiresPermission(android.Manifest.permission.DUMP) public void dumpPackageState(java.io.FileDescriptor, String); @@ -4626,7 +4626,7 @@ package android.app { method public java.util.List<android.app.ActivityManager.AppTask> getAppTasks(); method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo(); method @NonNull public java.util.List<android.app.ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String, @IntRange(from=0) int, @IntRange(from=0) int); - method @NonNull public java.util.List<android.app.ApplicationStartInfo> getHistoricalProcessStartReasons(@IntRange(from=0) int); + method @FlaggedApi("android.app.app_start_info") @NonNull public java.util.List<android.app.ApplicationStartInfo> getHistoricalProcessStartReasons(@IntRange(from=0) int); method public int getLargeMemoryClass(); method public int getLauncherLargeIconDensity(); method public int getLauncherLargeIconSize(); @@ -4653,7 +4653,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int); method @RequiresPermission(android.Manifest.permission.REORDER_TASKS) public void moveTaskToFront(int, int, android.os.Bundle); method @Deprecated public void restartPackage(String); - method public void setApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>); + method @FlaggedApi("android.app.app_start_info") public void setApplicationStartInfoCompletionListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.ApplicationStartInfo>); method public void setProcessStateSummary(@Nullable byte[]); method public static void setVrThread(int); method public void setWatchHeapLimit(long); @@ -5234,7 +5234,7 @@ package android.app { field public static final int REASON_USER_STOPPED = 11; // 0xb } - public final class ApplicationStartInfo implements android.os.Parcelable { + @FlaggedApi("android.app.app_start_info") public final class ApplicationStartInfo implements android.os.Parcelable { method public int describeContents(); method public int getDefiningUid(); method @Nullable public android.content.Intent getIntent(); @@ -5952,6 +5952,7 @@ package android.app { public class GrammaticalInflectionManager { method public int getApplicationGrammaticalGender(); + method @FlaggedApi("android.app.system_terms_of_address_enabled") public int getSystemGrammaticalGender(); method public void setRequestedApplicationGrammaticalGender(int); } @@ -6749,7 +6750,6 @@ package android.app { method public android.app.Notification.WearableExtender clone(); method public android.app.Notification.Builder extend(android.app.Notification.Builder); method public java.util.List<android.app.Notification.Action> getActions(); - method @Deprecated public android.graphics.Bitmap getBackground(); method public String getBridgeTag(); method public int getContentAction(); method @Deprecated public int getContentIcon(); @@ -6768,7 +6768,6 @@ package android.app { method @Deprecated public boolean getHintShowBackgroundOnly(); method @Deprecated public java.util.List<android.app.Notification> getPages(); method public boolean getStartScrollBottom(); - method @Deprecated public android.app.Notification.WearableExtender setBackground(android.graphics.Bitmap); method public android.app.Notification.WearableExtender setBridgeTag(String); method public android.app.Notification.WearableExtender setContentAction(int); method @Deprecated public android.app.Notification.WearableExtender setContentIcon(int); @@ -9682,6 +9681,7 @@ package android.companion.virtual { method public int describeContents(); method public int getDeviceId(); method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @NonNull public int[] getDisplayIds(); + method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public CharSequence getDisplayName(); method @Nullable public String getName(); method @Nullable public String getPersistentDeviceId(); method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomSensorSupport(); @@ -39008,6 +39008,7 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityStart(); method @NonNull public String getKeystoreAlias(); method public int getMaxUsageCount(); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); @@ -39015,6 +39016,7 @@ package android.security.keystore { method public boolean isDevicePropertiesAttestationIncluded(); method @NonNull public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified(); method public boolean isRandomizedEncryptionRequired(); method public boolean isStrongBoxBacked(); method public boolean isUnlockedDeviceRequired(); @@ -39046,6 +39048,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@Nullable java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean); @@ -39150,12 +39153,14 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityForOriginationEnd(); method @Nullable public java.util.Date getKeyValidityStart(); method public int getMaxUsageCount(); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); method public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified(); method public boolean isRandomizedEncryptionRequired(); method public boolean isUnlockedDeviceRequired(); method public boolean isUserAuthenticationRequired(); @@ -39177,6 +39182,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int); + method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean); @@ -42898,7 +42904,7 @@ package android.telephony { field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array"; field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string"; field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; - field public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle"; field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool"; field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_SUPPORTS_TETHERING_BOOL = "carrier_supports_tethering_bool"; @@ -43066,6 +43072,7 @@ package android.telephony { field public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = "rtt_supported_while_roaming_bool"; field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool"; field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool"; field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; @@ -54449,8 +54456,8 @@ package android.view.animation { public class AnimationUtils { ctor public AnimationUtils(); method public static long currentAnimationTimeMillis(); - method public static long getExpectedPresentationTimeMillis(); - method public static long getExpectedPresentationTimeNanos(); + method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeMillis(); + method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeNanos(); method public static android.view.animation.Animation loadAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException; method public static android.view.animation.Interpolator loadInterpolator(android.content.Context, @AnimRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException; method public static android.view.animation.LayoutAnimationController loadLayoutAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException; @@ -55022,10 +55029,12 @@ package android.view.inputmethod { method public int getInitialToolType(); method @NonNull public java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>> getSupportedHandwritingGesturePreviews(); method @NonNull public java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>> getSupportedHandwritingGestures(); + method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public boolean isStylusHandwritingEnabled(); method public final void makeCompatible(int); method public void setInitialSurroundingSubText(@NonNull CharSequence, int); method public void setInitialSurroundingText(@NonNull CharSequence); method public void setInitialToolType(int); + method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public void setStylusHandwritingEnabled(boolean); method public void setSupportedHandwritingGesturePreviews(@NonNull java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>>); method public void setSupportedHandwritingGestures(@NonNull java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>>); method public void writeToParcel(android.os.Parcel, int); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 9b8d2b496a30..052d614fa5fc 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -556,24 +556,24 @@ package android.provider { package android.se.omapi { - @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public class SeFrameworkInitializer { - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public static android.se.omapi.SeServiceManager getSeServiceManager(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public static void setSeServiceManager(@NonNull android.se.omapi.SeServiceManager); + @FlaggedApi("android.nfc.enable_nfc_mainline") public class SeFrameworkInitializer { + method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public static android.se.omapi.SeServiceManager getSeServiceManager(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public static void setSeServiceManager(@NonNull android.se.omapi.SeServiceManager); } - @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public class SeServiceManager { - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.se.omapi.SeServiceManager.ServiceRegisterer getSeManagerServiceRegisterer(); + @FlaggedApi("android.nfc.enable_nfc_mainline") public class SeServiceManager { + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.se.omapi.SeServiceManager.ServiceRegisterer getSeManagerServiceRegisterer(); } - @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public static class SeServiceManager.ServiceNotFoundException extends java.lang.Exception { - ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public SeServiceManager.ServiceNotFoundException(@NonNull String); + @FlaggedApi("android.nfc.enable_nfc_mainline") public static class SeServiceManager.ServiceNotFoundException extends java.lang.Exception { + ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public SeServiceManager.ServiceNotFoundException(@NonNull String); } - @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public static final class SeServiceManager.ServiceRegisterer { - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public android.os.IBinder get(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.os.IBinder getOrThrow() throws android.se.omapi.SeServiceManager.ServiceNotFoundException; - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void register(@NonNull android.os.IBinder); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public android.os.IBinder tryGet(); + @FlaggedApi("android.nfc.enable_nfc_mainline") public static final class SeServiceManager.ServiceRegisterer { + method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public android.os.IBinder get(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.os.IBinder getOrThrow() throws android.se.omapi.SeServiceManager.ServiceNotFoundException; + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void register(@NonNull android.os.IBinder); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public android.os.IBinder tryGet(); } } diff --git a/core/api/removed.txt b/core/api/removed.txt index 8b3696a1e6d9..57e2e73854c1 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -23,6 +23,11 @@ package android.app { method @Deprecated public android.app.Notification.Builder setTimeout(long); } + public static final class Notification.WearableExtender implements android.app.Notification.Extender { + method @Deprecated public android.graphics.Bitmap getBackground(); + method @Deprecated public android.app.Notification.WearableExtender setBackground(android.graphics.Bitmap); + } + } package android.app.slice { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ad658061a0f6..8748e6939ccd 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -540,7 +540,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int); method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void forceStopPackage(String); method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS_FULL"}) public static int getCurrentUser(); - method @NonNull @RequiresPermission(android.Manifest.permission.DUMP) public java.util.List<android.app.ApplicationStartInfo> getExternalHistoricalProcessStartReasons(@NonNull String, @IntRange(from=0) int); + method @FlaggedApi("android.app.app_start_info") @NonNull @RequiresPermission(android.Manifest.permission.DUMP) public java.util.List<android.app.ApplicationStartInfo> getExternalHistoricalProcessStartReasons(@NonNull String, @IntRange(from=0) int); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getPackageImportance(String); method @NonNull public java.util.Collection<java.util.Locale> getSupportedLocales(); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidImportance(int); @@ -3193,7 +3193,7 @@ package android.companion.virtual { public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable { method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener); - method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName); + method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName); method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); method @NonNull public android.content.Context createContext(); @@ -3214,40 +3214,40 @@ package android.companion.virtual { method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener); - method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName); + method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName); method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener); - method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int); + method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); } public final class VirtualDeviceParams implements android.os.Parcelable { method public int describeContents(); - method @NonNull public java.util.Set<android.content.ComponentName> getAllowedActivities(); - method @NonNull public java.util.Set<android.content.ComponentName> getAllowedCrossTaskNavigations(); + method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getAllowedActivities(); + method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getAllowedCrossTaskNavigations(); method public int getAudioPlaybackSessionId(); method public int getAudioRecordingSessionId(); - method @NonNull public java.util.Set<android.content.ComponentName> getBlockedActivities(); - method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations(); - method public int getDefaultActivityPolicy(); - method public int getDefaultNavigationPolicy(); + method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getBlockedActivities(); + method @Deprecated @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations(); + method @Deprecated public int getDefaultActivityPolicy(); + method @Deprecated public int getDefaultNavigationPolicy(); method public int getDevicePolicy(int); - method @FlaggedApi(Flags.FLAG_VDM_CUSTOM_HOME) @Nullable public android.content.ComponentName getHomeComponent(); + method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent(); method public int getLockState(); method @Nullable public String getName(); method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts(); method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs(); method public void writeToParcel(@NonNull android.os.Parcel, int); - field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0 - field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1 + field @Deprecated public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0 + field @Deprecated public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1 field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR; field public static final int DEVICE_POLICY_CUSTOM = 1; // 0x1 field public static final int DEVICE_POLICY_DEFAULT = 0; // 0x0 field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1 field public static final int LOCK_STATE_DEFAULT = 0; // 0x0 - field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0 - field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1 - field @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3 + field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0 + field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1 + field @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3 field public static final int POLICY_TYPE_AUDIO = 1; // 0x1 field public static final int POLICY_TYPE_RECENTS = 2; // 0x2 field public static final int POLICY_TYPE_SENSORS = 0; // 0x0 @@ -3257,14 +3257,14 @@ package android.companion.virtual { ctor public VirtualDeviceParams.Builder(); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig); method @NonNull public android.companion.virtual.VirtualDeviceParams build(); - method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>); - method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>); + method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>); + method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioPlaybackSessionId(int); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioRecordingSessionId(int); - method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>); - method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>); + method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>); + method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int); - method @FlaggedApi(Flags.FLAG_VDM_CUSTOM_HOME) @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName); + method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName); method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>); @@ -3805,8 +3805,8 @@ package android.content.pm { public class PackageInstaller { method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException; method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException; - method @FlaggedApi(Flags.FLAG_ARCHIVING) @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; - method @FlaggedApi(Flags.FLAG_ARCHIVING) @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean); field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL"; field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL"; @@ -3817,8 +3817,8 @@ package android.content.pm { field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE"; field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS"; field public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH"; - field @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; - field @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; + field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS"; + field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME"; field public static final int LOCATION_DATA_APP = 0; // 0x0 field public static final int LOCATION_MEDIA_DATA = 2; // 0x2 field public static final int LOCATION_MEDIA_OBB = 1; // 0x1 @@ -3879,7 +3879,7 @@ package android.content.pm { method public static void forceSafeLabels(); method @Deprecated @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager); method @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager, @FloatRange(from=0) float, int); - field @FlaggedApi(Flags.FLAG_ARCHIVING) public boolean isArchived; + field @FlaggedApi("android.content.pm.archiving") public boolean isArchived; } public abstract class PackageManager { @@ -3929,7 +3929,7 @@ package android.content.pm { method @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public void setHarmfulAppWarning(@NonNull String, @Nullable CharSequence); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable String); method @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo); - method @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int); + method @FlaggedApi("android.content.pm.quarantined_enabled") @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int); method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean); method public void setSystemAppState(@NonNull String, int); method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean); @@ -3980,7 +3980,7 @@ package android.content.pm { field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 512; // 0x200 field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 256; // 0x100 field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1 - field @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) public static final int FLAG_SUSPEND_QUARANTINED = 1; // 0x1 + field @FlaggedApi("android.content.pm.quarantined_enabled") public static final int FLAG_SUSPEND_QUARANTINED = 1; // 0x1 field public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; // 0xffffffff field public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; // 0xfffffff3 field public static final int INSTALL_FAILED_CONTAINER_ERROR = -18; // 0xffffffee @@ -4529,6 +4529,7 @@ package android.hardware.display { method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float); + field public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80 field public static final int VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED = 65536; // 0x10000 field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400 } @@ -9631,67 +9632,67 @@ package android.nfc { package android.nfc.cardemulation { - @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public final class AidGroup implements android.os.Parcelable { - ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public AidGroup(@NonNull java.util.List<java.lang.String>, @Nullable String); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public static android.nfc.cardemulation.AidGroup createFromXml(@NonNull org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int describeContents(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dump(@NonNull android.util.proto.ProtoOutputStream); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getAids(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getCategory(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeAsXml(@NonNull org.xmlpull.v1.XmlSerializer) throws java.io.IOException; - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeToParcel(@NonNull android.os.Parcel, int); - field @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.AidGroup> CREATOR; - } - - @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public final class ApduServiceInfo implements android.os.Parcelable { - ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int describeContents(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<android.nfc.cardemulation.AidGroup> getAidGroups(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getAids(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getCategoryForAid(@NonNull String); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.content.ComponentName getComponent(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getDescription(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Nullable public String getOffHostSecureElement(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getPrefixAids(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getSettingsActivityName(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public java.util.List<java.lang.String> getSubsetAids(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int getUid(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean hasCategory(@NonNull String); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean isOnHost(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public CharSequence loadAppLabel(@NonNull android.content.pm.PackageManager); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.graphics.drawable.Drawable loadBanner(@NonNull android.content.pm.PackageManager); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean requiresScreenOn(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public boolean requiresUnlock(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void resetOffHostSecureElement(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setOffHostSecureElement(@NonNull String); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeToParcel(@NonNull android.os.Parcel, int); - field @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR; - } - - @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public final class NfcFServiceInfo implements android.os.Parcelable { - ctor @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public NfcFServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int describeContents(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.content.ComponentName getComponent(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getDescription(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getNfcid2(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getSystemCode(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getT3tPmm(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int getUid(); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setDynamicNfcid2(@NonNull String); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setDynamicSystemCode(@NonNull String); - method @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void writeToParcel(@NonNull android.os.Parcel, int); - field @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.NfcFServiceInfo> CREATOR; + @FlaggedApi("android.nfc.enable_nfc_mainline") public final class AidGroup implements android.os.Parcelable { + ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public AidGroup(@NonNull java.util.List<java.lang.String>, @Nullable String); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public static android.nfc.cardemulation.AidGroup createFromXml(@NonNull org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.util.proto.ProtoOutputStream); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getAids(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getCategory(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeAsXml(@NonNull org.xmlpull.v1.XmlSerializer) throws java.io.IOException; + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.AidGroup> CREATOR; + } + + @FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable { + ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.AidGroup> getAidGroups(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getAids(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getCategoryForAid(@NonNull String); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.content.ComponentName getComponent(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public String getOffHostSecureElement(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean hasCategory(@NonNull String); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean isOnHost(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadAppLabel(@NonNull android.content.pm.PackageManager); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadBanner(@NonNull android.content.pm.PackageManager); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresScreenOn(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setOffHostSecureElement(@NonNull String); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR; + } + + @FlaggedApi("android.nfc.enable_nfc_mainline") public final class NfcFServiceInfo implements android.os.Parcelable { + ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public NfcFServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.content.ComponentName getComponent(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getNfcid2(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSystemCode(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getT3tPmm(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid(); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager); + method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicNfcid2(@NonNull String); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicSystemCode(@NonNull String); + method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.NfcFServiceInfo> CREATOR; } } @@ -9842,7 +9843,6 @@ package android.os { field public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1; // 0x1 field public static final int BUGREPORT_MODE_FULL = 0; // 0x0 field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1 - field public static final int BUGREPORT_MODE_ONBOARDING = 7; // 0x7 field public static final int BUGREPORT_MODE_REMOTE = 2; // 0x2 field public static final int BUGREPORT_MODE_TELEPHONY = 4; // 0x4 field public static final int BUGREPORT_MODE_WEAR = 3; // 0x3 @@ -10705,13 +10705,13 @@ package android.permission { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetUnusedAppCount(@NonNull java.util.function.IntConsumer); method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable); method @Deprecated @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String); - method @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int); method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>); method @Deprecated @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable); - method @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable); method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); @@ -16731,17 +16731,22 @@ package android.telephony.satellite { } public final class SatelliteManager { + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>); @@ -16768,6 +16773,7 @@ package android.telephony.satellite { field public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2 field public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4 field public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1 field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0 field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7 field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6 @@ -16777,13 +16783,13 @@ package android.telephony.satellite { field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3 field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2 field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff - field @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8 - field @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7 field public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3 field public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2 field public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0 field public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1 - field @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6 field public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4 field public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5 field public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 4f4569134c19..3bf2ccaf9923 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -24,6 +24,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import android.Manifest; import android.annotation.ColorInt; import android.annotation.DrawableRes; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -3982,6 +3983,7 @@ public class ActivityManager { * the order from most recent to least recent. */ @NonNull + @FlaggedApi(Flags.FLAG_APP_START_INFO) public List<ApplicationStartInfo> getHistoricalProcessStartReasons( @IntRange(from = 0) int maxNum) { try { @@ -4012,6 +4014,7 @@ public class ActivityManager { */ @NonNull @SystemApi + @FlaggedApi(Flags.FLAG_APP_START_INFO) @RequiresPermission(Manifest.permission.DUMP) public List<ApplicationStartInfo> getExternalHistoricalProcessStartReasons( @NonNull String packageName, @IntRange(from = 0) int maxNum) { @@ -4044,6 +4047,7 @@ public class ActivityManager { * * @throws IllegalArgumentException if executor or listener are null. */ + @FlaggedApi(Flags.FLAG_APP_START_INFO) public void setApplicationStartInfoCompletionListener(@NonNull final Executor executor, @NonNull final Consumer<ApplicationStartInfo> listener) { Preconditions.checkNotNull(executor, "executor cannot be null"); @@ -4065,6 +4069,7 @@ public class ActivityManager { /** * Removes the callback set by {@link #setApplicationStartInfoCompletionListener} if there is one. */ + @FlaggedApi(Flags.FLAG_APP_START_INFO) public void clearApplicationStartInfoCompletionListener() { try { getService().clearApplicationStartInfoCompleteListener(mContext.getUserId()); @@ -4089,6 +4094,7 @@ public class ActivityManager { * Will thow {@link java.lang.IllegalArgumentException} if not in range. * @param timestampNs Clock monotonic time in nanoseconds of event to be recorded. */ + @FlaggedApi(Flags.FLAG_APP_START_INFO) public void addStartInfoTimestamp(@IntRange( from = ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START, to = ApplicationStartInfo.START_TIMESTAMP_RESERVED_RANGE_DEVELOPER) int key, diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 25c48e630380..9a90df93b2cd 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -62,7 +62,7 @@ import android.app.servertransaction.ActivityLifecycleItem.LifecycleState; import android.app.servertransaction.ActivityRelaunchItem; import android.app.servertransaction.ActivityResultItem; import android.app.servertransaction.ClientTransaction; -import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.DestroyActivityItem; import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.PendingTransactionActions; import android.app.servertransaction.PendingTransactionActions.StopInfo; @@ -263,6 +263,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.TimeZone; @@ -375,8 +376,8 @@ public final class ActivityThread extends ClientTransactionHandler @GuardedBy("mPendingOverrideConfigs") private final ArrayMap<IBinder, Configuration> mPendingOverrideConfigs = new ArrayMap<>(); /** The activities to be truly destroyed (not include relaunch). */ - final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed = - Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>()); + final Map<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed = + Collections.synchronizedMap(new ArrayMap<>()); // List of new activities that should be reported when next we idle. final ArrayList<ActivityClientRecord> mNewActivities = new ArrayList<>(); // Number of activities that are currently visible on-screen. @@ -3033,7 +3034,7 @@ public final class ActivityThread extends ClientTransactionHandler "%13s %8s %8s %8s %8s %8s %8s %8s %8s"; private static final String ONE_COUNT_COLUMN = "%21s %8d"; private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d"; - private static final String THREE_COUNT_COLUMNS = "%21s %8d %21s %8s %21s %8d"; + private static final String THREE_COUNT_COLUMNS = "%21s %8d %21s %8d %21s %8d"; private static final String TWO_COUNT_COLUMN_HEADER = "%21s %8s %21s %8s"; private static final String ONE_ALT_COUNT_COLUMN = "%21s %8s %21s %8d"; @@ -3041,7 +3042,7 @@ public final class ActivityThread extends ClientTransactionHandler private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 4; static void printRow(PrintWriter pw, String format, Object...objs) { - pw.println(String.format(format, objs)); + pw.println(String.format(Locale.US, format, objs)); } @NeverCompile @@ -5799,7 +5800,7 @@ public final class ActivityThread extends ClientTransactionHandler } @Override - public Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed() { + public Map<IBinder, DestroyActivityItem> getActivitiesToBeDestroyed() { return mActivitiesToBeDestroyed; } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 4f9225ff5585..ca10d144d4cd 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2336,6 +2336,7 @@ public class AppOpsManager { OP_WRITE_MEDIA_VIDEO, OP_READ_MEDIA_IMAGES, OP_WRITE_MEDIA_IMAGES, + OP_READ_MEDIA_VISUAL_USER_SELECTED, // Nearby devices OP_BLUETOOTH_SCAN, OP_BLUETOOTH_CONNECT, @@ -2376,7 +2377,6 @@ public class AppOpsManager { OP_MANAGE_MEDIA, OP_TURN_SCREEN_ON, OP_RUN_USER_INITIATED_JOBS, - OP_READ_MEDIA_VISUAL_USER_SELECTED, OP_FOREGROUND_SERVICE_SPECIAL_USE, OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, OP_USE_FULL_SCREEN_INTENT diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index f5fb6edfcc04..a6a57cd5745a 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -37,6 +38,7 @@ import java.util.Set; /** * Provide information related to a processes startup. */ +@FlaggedApi(Flags.FLAG_APP_START_INFO) public final class ApplicationStartInfo implements Parcelable { /** diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 98020ff2d173..25075e91088f 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -19,7 +19,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread.ActivityClientRecord; import android.app.servertransaction.ClientTransaction; -import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.DestroyActivityItem; import android.app.servertransaction.PendingTransactionActions; import android.app.servertransaction.TransactionExecutor; import android.content.Context; @@ -108,7 +108,7 @@ public abstract class ClientTransactionHandler { // and deliver callbacks. /** Get activity and its corresponding transaction item which are going to destroy. */ - public abstract Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed(); + public abstract Map<IBinder, DestroyActivityItem> getActivitiesToBeDestroyed(); /** Destroy the activity. */ public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing, diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java index 1905b6a46d7e..bc6fe6146764 100644 --- a/core/java/android/app/GrammaticalInflectionManager.java +++ b/core/java/android/app/GrammaticalInflectionManager.java @@ -16,12 +16,15 @@ package android.app; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.SystemService; import android.content.Context; import android.content.res.Configuration; import android.os.RemoteException; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -31,11 +34,15 @@ import java.util.Set; */ @SystemService(Context.GRAMMATICAL_INFLECTION_SERVICE) public class GrammaticalInflectionManager { - private static final Set<Integer> VALID_GENDER_VALUES = new HashSet<>(Arrays.asList( + + /** @hide */ + @NonNull + public static final Set<Integer> VALID_GRAMMATICAL_GENDER_VALUES = + Collections.unmodifiableSet(new HashSet<>(Arrays.asList( Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED, Configuration.GRAMMATICAL_GENDER_NEUTRAL, Configuration.GRAMMATICAL_GENDER_FEMININE, - Configuration.GRAMMATICAL_GENDER_MASCULINE)); + Configuration.GRAMMATICAL_GENDER_MASCULINE))); private final Context mContext; private final IGrammaticalInflectionManager mService; @@ -79,7 +86,7 @@ public class GrammaticalInflectionManager { */ public void setRequestedApplicationGrammaticalGender( @Configuration.GrammaticalGender int grammaticalGender) { - if (!VALID_GENDER_VALUES.contains(grammaticalGender)) { + if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) { throw new IllegalArgumentException("Unknown grammatical gender"); } @@ -90,4 +97,44 @@ public class GrammaticalInflectionManager { throw e.rethrowFromSystemServer(); } } + + /** + * Sets the current grammatical gender for all privileged applications. The value will be + * stored in an encrypted file at {@link android.os.Environment#getDataSystemCeDirectory(int) + * + * @param grammaticalGender the terms of address the user preferred in system. + * + * @see Configuration#getGrammaticalGender + * @hide + */ + public void setSystemWideGrammaticalGender( + @Configuration.GrammaticalGender int grammaticalGender) { + if (!VALID_GRAMMATICAL_GENDER_VALUES.contains(grammaticalGender)) { + throw new IllegalArgumentException("Unknown grammatical gender"); + } + + try { + mService.setSystemWideGrammaticalGender(mContext.getUserId(), grammaticalGender); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the current grammatical gender of privileged application from the encrypted file, + * which is stored under {@link android.os.Environment#getDataSystemCeDirectory(int)}. + * + * @return the value of grammatical gender + * + * @see Configuration#getGrammaticalGender + */ + @FlaggedApi(Flags.FLAG_SYSTEM_TERMS_OF_ADDRESS_ENABLED) + @Configuration.GrammaticalGender + public int getSystemGrammaticalGender() { + try { + return mService.getSystemGrammaticalGender(mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl index 9366a45551da..48a48416d592 100644 --- a/core/java/android/app/IGrammaticalInflectionManager.aidl +++ b/core/java/android/app/IGrammaticalInflectionManager.aidl @@ -16,4 +16,14 @@ package android.app; * Sets a specified app’s app-specific grammatical gender. */ void setRequestedApplicationGrammaticalGender(String appPackageName, int userId, int gender); - }
\ No newline at end of file + + /** + * Sets the grammatical gender to system. + */ + void setSystemWideGrammaticalGender(int userId, int gender); + + /** + * Gets the grammatical gender from system. + */ + int getSystemGrammaticalGender(int userId); + } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 7f38b27c12c8..ec5effd0963d 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -98,6 +98,7 @@ interface INotificationManager ParceledListSlice getNotificationChannelGroupsForPackage(String pkg, int uid, boolean includeDeleted); NotificationChannelGroup getNotificationChannelGroupForPackage(String groupId, String pkg, int uid); NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage(String pkg, int uid, String groupId, boolean includeDeleted); + ParceledListSlice getRecentBlockedNotificationChannelGroupsForPackage(String pkg, int uid); void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group); void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel); void unlockNotificationChannel(String pkg, int uid, String channelId); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2c42df3c8819..93c2b5ad4d86 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -44,6 +44,9 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.admin.DevicePolicyManager; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; @@ -296,6 +299,15 @@ public class Notification implements Parcelable public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft"; /** + * The call to WearableExtender#setBackground(Bitmap) will have no effect and the passed + * Bitmap will not be retained in memory. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + @VisibleForTesting + static final long WEARABLE_EXTENDER_BACKGROUND_BLOCKED = 270551184L; + + /** * A timestamp related to this notification, in milliseconds since the epoch. * * Default value: {@link System#currentTimeMillis() Now}. @@ -11148,9 +11160,20 @@ public class Notification implements Parcelable wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( new Notification[mPages.size()])); } + if (mBackground != null) { - wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); + // Keeping WearableExtender backgrounds in memory despite them being deprecated has + // added noticeable increase in system server and system ui memory usage. After + // target VERSION_CODE#VANILLA_ICE_CREAM the background will not be populated + // anymore. + if (CompatChanges.isChangeEnabled(WEARABLE_EXTENDER_BACKGROUND_BLOCKED)) { + Log.d(TAG, "Use of background in WearableExtenders has been deprecated and " + + "will not be populated anymore."); + } else { + wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); + } } + if (mContentIcon != 0) { wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); } @@ -11369,12 +11392,21 @@ public class Notification implements Parcelable * * @param background the background bitmap * @return this object for method chaining - * @see android.app.Notification.WearableExtender#getBackground - * @deprecated Background images are no longer supported. + * @removed Not functional since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. + * The wearable background is not used by wearables anymore and uses up + * unnecessary memory. */ @Deprecated public WearableExtender setBackground(Bitmap background) { - mBackground = background; + // Keeping WearableExtender backgrounds in memory despite them being deprecated has + // added noticeable increase in system server and system ui memory usage. After + // target VERSION_CODE#VANILLA_ICE_CREAM the background will not be populated anymore. + if (CompatChanges.isChangeEnabled(WEARABLE_EXTENDER_BACKGROUND_BLOCKED)) { + Log.d(TAG, "Use of background in WearableExtenders has been deprecated and " + + "will not be populated anymore."); + } else { + mBackground = background; + } return this; } @@ -11384,11 +11416,13 @@ public class Notification implements Parcelable * will work with any notification style. * * @return the background image - * @see android.app.Notification.WearableExtender#setBackground - * @deprecated Background images are no longer supported. + * @removed Not functional since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. The + * wearable background is not used by wearables anymore and uses up + * unnecessary memory. */ @Deprecated public Bitmap getBackground() { + Log.w(TAG, "Use of background in WearableExtender has been removed, returning null."); return mBackground; } diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index e1c45d98e678..9cf54e3b9063 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -54,6 +54,9 @@ per-file IBackupAgent.aidl = file:/services/backup/OWNERS per-file Broadcast* = file:/BROADCASTS_OWNERS per-file ReceiverInfo* = file:/BROADCASTS_OWNERS +# GrammaticalInflectionManager +per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS + # KeyguardManager per-file KeyguardManager.java = file:/services/core/java/com/android/server/locksettings/OWNERS diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 315a0556042a..a29c196d88de 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -19,7 +19,7 @@ "name": "CtsAppOpsTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] }, diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig new file mode 100644 index 000000000000..2076e85828a6 --- /dev/null +++ b/core/java/android/app/activity_manager.aconfig @@ -0,0 +1,8 @@ +package: "android.app" + +flag { + namespace: "system_performance" + name: "app_start_info" + description: "Control collecting of ApplicationStartInfo records and APIs." + bug: "247814855" +}
\ No newline at end of file diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index ed0f872bf9bc..15bd1dcc3980 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -22,8 +22,10 @@ import android.os.PooledStringWriter; import android.os.RemoteException; import android.os.SystemClock; import android.service.autofill.FillRequest; +import android.text.InputType; import android.text.Spanned; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.view.View; @@ -2452,7 +2454,7 @@ public class AssistStructure implements Parcelable { + node.getTextStyle()); Log.i(TAG, prefix + " Text color fg: #" + Integer.toHexString(node.getTextColor()) + ", bg: #" + Integer.toHexString(node.getTextBackgroundColor())); - Log.i(TAG, prefix + " Input type: " + node.getInputType()); + Log.i(TAG, prefix + " Input type: " + getInputTypeString(node.getInputType())); Log.i(TAG, prefix + " Resource id: " + node.getTextIdEntry()); } String webDomain = node.getWebDomain(); @@ -2664,4 +2666,33 @@ public class AssistStructure implements Parcelable { return new AssistStructure[size]; } }; + + private static final ArrayMap<Integer, String> INPUT_TYPE_VARIATIONS = new ArrayMap<>(); + static { + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS, "EmailSubject"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS, "PostalAddress"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_PERSON_NAME, "PersonName"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_PASSWORD, "Password"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD, "VisiblePassword"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_URI, "URI"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS, "WebEmailAddress"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD, "WebPassword"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE, "LongMessage"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE, "ShortMessage"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_FLAG_MULTI_LINE, "MultiLine"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE, "ImeMultiLine"); + INPUT_TYPE_VARIATIONS.put(InputType.TYPE_TEXT_VARIATION_FILTER, "Filter"); + } + + private static String getInputTypeString(int inputType) { + StringBuilder sb = new StringBuilder(); + sb.append(inputType); + sb.append("(class=").append(inputType & InputType.TYPE_MASK_CLASS).append(')'); + for (int variation : INPUT_TYPE_VARIATIONS.keySet()) { + if ((variation & inputType) == variation) { + sb.append('|').append(INPUT_TYPE_VARIATIONS.get(variation)); + } + } + return sb.toString(); + } } diff --git a/core/java/android/app/grammatical_inflection_manager.aconfig b/core/java/android/app/grammatical_inflection_manager.aconfig new file mode 100644 index 000000000000..989ce61337a3 --- /dev/null +++ b/core/java/android/app/grammatical_inflection_manager.aconfig @@ -0,0 +1,8 @@ +package: "android.app" + +flag { + name: "system_terms_of_address_enabled" + namespace: "grammatical_gender" + description: "Feature flag for System Terms of Address" + bug: "297798866" +} diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index a5b0f18dad3b..8617386516af 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -23,7 +23,6 @@ import android.annotation.Nullable; import android.app.ClientTransactionHandler; import android.app.IApplicationThread; import android.compat.annotation.UnsupportedAppUsage; -import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; @@ -83,23 +82,6 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { return mActivityCallbacks; } - /** Get the target activity. */ - @Nullable - @UnsupportedAppUsage - public IBinder getActivityToken() { - // TODO(b/260873529): remove after we allow multiple activity items in one transaction. - if (mLifecycleStateRequest != null) { - return mLifecycleStateRequest.getActivityToken(); - } - for (int i = mActivityCallbacks.size() - 1; i >= 0; i--) { - final IBinder token = mActivityCallbacks.get(i).getActivityToken(); - if (token != null) { - return token; - } - } - return null; - } - /** Get the target state lifecycle request. */ @VisibleForTesting(visibility = PACKAGE) @UnsupportedAppUsage diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java index ddb6df10517c..f9cf075d6062 100644 --- a/core/java/android/app/servertransaction/DestroyActivityItem.java +++ b/core/java/android/app/servertransaction/DestroyActivityItem.java @@ -50,6 +50,13 @@ public class DestroyActivityItem extends ActivityLifecycleItem { } @Override + public void postExecute(@NonNull ClientTransactionHandler client, + @NonNull PendingTransactionActions pendingActions) { + // Cleanup after execution. + client.getActivitiesToBeDestroyed().remove(getActivityToken()); + } + + @Override public int getTargetState() { return ON_DESTROY; } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 44336735254d..066f9fe84970 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -47,7 +47,6 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.util.List; -import java.util.Map; /** * Class that manages transaction execution in the correct order. @@ -75,34 +74,14 @@ public class TransactionExecutor { * either remain in the initial state, or last state needed by a callback. */ public void execute(@NonNull ClientTransaction transaction) { - if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Start resolving transaction"); - - final IBinder token = transaction.getActivityToken(); - if (token != null) { - final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed = - mTransactionHandler.getActivitiesToBeDestroyed(); - final ClientTransactionItem destroyItem = activitiesToBeDestroyed.get(token); - if (destroyItem != null) { - if (transaction.getLifecycleStateRequest() == destroyItem) { - // It is going to execute the transaction that will destroy activity with the - // token, so the corresponding to-be-destroyed record can be removed. - activitiesToBeDestroyed.remove(token); - } - if (mTransactionHandler.getActivityClient(token) == null) { - // The activity has not been created but has been requested to destroy, so all - // transactions for the token are just like being cancelled. - Slog.w(TAG, tId(transaction) + "Skip pre-destroyed transaction:\n" - + transactionToString(transaction, mTransactionHandler)); - return; - } - } + if (DEBUG_RESOLVER) { + Slog.d(TAG, tId(transaction) + "Start resolving transaction"); + Slog.d(TAG, transactionToString(transaction, mTransactionHandler)); } - if (DEBUG_RESOLVER) Slog.d(TAG, transactionToString(transaction, mTransactionHandler)); - executeCallbacks(transaction); - executeLifecycleState(transaction); + mPendingActions.clear(); if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction"); } @@ -135,6 +114,14 @@ public class TransactionExecutor { final IBinder token = item.getActivityToken(); ActivityClientRecord r = mTransactionHandler.getActivityClient(token); + if (token != null && r == null + && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) { + // The activity has not been created but has been requested to destroy, so all + // transactions for the token are just like being cancelled. + Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item); + continue; + } + if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item); final int postExecutionState = item.getPostExecutionState(); @@ -211,6 +198,10 @@ public class TransactionExecutor { } if (r == null) { + if (mTransactionHandler.getActivitiesToBeDestroyed().get(token) == lifecycleItem) { + // Always cleanup for destroy item. + lifecycleItem.postExecute(mTransactionHandler, mPendingActions); + } // Ignore requests for non-existent client records for now. return; } diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index bf00a5afd6ea..2f97080901f9 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -23,6 +23,8 @@ import android.companion.virtual.audio.IAudioRoutingCallback; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; +import android.companion.virtual.camera.IVirtualCamera; +import android.companion.virtual.camera.VirtualCameraHalConfig; import android.content.ComponentName; import android.content.IntentFilter; import android.graphics.Point; @@ -232,4 +234,10 @@ interface IVirtualDevice { */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor); + + /** + * Creates a new VirtualCamera and registers it with the VirtualCameraProvider. + */ + @EnforcePermission("CREATE_VIRTUAL_DEVICE") + void registerVirtualCamera(in IVirtualCamera camera); } diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java index 4692f921beb2..ce883cddc952 100644 --- a/core/java/android/companion/virtual/VirtualDevice.java +++ b/core/java/android/companion/virtual/VirtualDevice.java @@ -44,6 +44,7 @@ public final class VirtualDevice implements Parcelable { private final int mId; private final @Nullable String mPersistentId; private final @Nullable String mName; + private final @Nullable CharSequence mDisplayName; /** * Creates a new instance of {@link VirtualDevice}. @@ -53,6 +54,18 @@ public final class VirtualDevice implements Parcelable { */ public VirtualDevice(@NonNull IVirtualDevice virtualDevice, int id, @Nullable String persistentId, @Nullable String name) { + this(virtualDevice, id, persistentId, name, null); + } + + /** + * Creates a new instance of {@link VirtualDevice}. Only to be used by the + * VirtualDeviceManagerService. + * + * @hide + */ + public VirtualDevice(@NonNull IVirtualDevice virtualDevice, int id, + @Nullable String persistentId, @Nullable String name, + @Nullable CharSequence displayName) { if (id <= Context.DEVICE_ID_DEFAULT) { throw new IllegalArgumentException("VirtualDevice ID must be greater than " + Context.DEVICE_ID_DEFAULT); @@ -61,6 +74,7 @@ public final class VirtualDevice implements Parcelable { mId = id; mPersistentId = persistentId; mName = name; + mDisplayName = displayName; } private VirtualDevice(@NonNull Parcel parcel) { @@ -68,6 +82,7 @@ public final class VirtualDevice implements Parcelable { mId = parcel.readInt(); mPersistentId = parcel.readString8(); mName = parcel.readString8(); + mDisplayName = parcel.readCharSequence(); } /** @@ -112,6 +127,15 @@ public final class VirtualDevice implements Parcelable { } /** + * Returns the human-readable name of the virtual device, if defined, which is suitable to be + * shown in UI. + */ + @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) + public @Nullable CharSequence getDisplayName() { + return mDisplayName; + } + + /** * Returns the IDs of all virtual displays that belong to this device, if any. * * <p>The actual {@link android.view.Display} objects can be obtained by passing the returned @@ -156,6 +180,7 @@ public final class VirtualDevice implements Parcelable { dest.writeInt(mId); dest.writeString8(mPersistentId); dest.writeString8(mName); + dest.writeCharSequence(mDisplayName); } @Override @@ -165,6 +190,7 @@ public final class VirtualDevice implements Parcelable { + " mId=" + mId + " mPersistentId=" + mPersistentId + " mName=" + mName + + " mDisplayName=" + mDisplayName + ")"; } diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java index 7bf2e91b422d..f6a7d2a465fb 100644 --- a/core/java/android/companion/virtual/VirtualDeviceInternal.java +++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java @@ -22,6 +22,8 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.companion.virtual.audio.VirtualAudioDevice; +import android.companion.virtual.camera.VirtualCamera; +import android.companion.virtual.camera.VirtualCameraConfig; import android.companion.virtual.sensor.VirtualSensor; import android.content.ComponentName; import android.content.Context; @@ -339,6 +341,11 @@ public class VirtualDeviceInternal { } @NonNull + VirtualCamera createVirtualCamera(@NonNull VirtualCameraConfig config) { + return new VirtualCamera(mVirtualDevice, config); + } + + @NonNull void setShowPointerIcon(boolean showPointerIcon) { try { mVirtualDevice.setShowPointerIcon(showPointerIcon); diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index d338d17791ed..baed7f9b2a38 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -33,6 +33,8 @@ import android.app.PendingIntent; import android.companion.AssociationInfo; import android.companion.virtual.audio.VirtualAudioDevice; import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback; +import android.companion.virtual.camera.VirtualCamera; +import android.companion.virtual.camera.VirtualCameraConfig; import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.VirtualSensor; import android.content.ComponentName; @@ -851,6 +853,24 @@ public final class VirtualDeviceManager { } /** + * Creates a new virtual camera. If a virtual camera was already created, it will be closed. + * + * @param config camera config. + * @return newly created camera; + * @hide + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) + public VirtualCamera createVirtualCamera(@NonNull VirtualCameraConfig config) { + if (!Flags.virtualCamera()) { + throw new UnsupportedOperationException( + "Flag is not enabled: %s".formatted(Flags.FLAG_VIRTUAL_CAMERA)); + } + return mVirtualDeviceInternal.createVirtualCamera(Objects.requireNonNull(config)); + } + + /** * Sets the visibility of the pointer icon for this VirtualDevice's associated displays. * * @param showPointerIcon True if the pointer should be shown; false otherwise. The default diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index 0fa78c88c863..0975cbb8684f 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -94,13 +94,19 @@ public final class VirtualDeviceParams implements Parcelable { /** * Indicates that activities are allowed by default on this virtual device, unless they are * explicitly blocked by {@link Builder#setBlockedActivities}. + * + * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_DEFAULT} */ + @Deprecated public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; /** * Indicates that activities are blocked by default on this virtual device, unless they are * allowed by {@link Builder#setAllowedActivities}. + * + * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_CUSTOM} */ + @Deprecated public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; /** @hide */ @@ -113,13 +119,19 @@ public final class VirtualDeviceParams implements Parcelable { /** * Indicates that tasks are allowed to navigate to other tasks on this virtual device, * unless they are explicitly blocked by {@link Builder#setBlockedCrossTaskNavigations}. + * + * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_DEFAULT} */ + @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; /** * Indicates that tasks are blocked from navigating to other tasks by default on this virtual * device, unless allowed by {@link Builder#setAllowedCrossTaskNavigations}. + * + * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and {@link #DEVICE_POLICY_CUSTOM} */ + @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; /** @hide */ @@ -325,7 +337,10 @@ public final class VirtualDeviceParams implements Parcelable { * be be allowed by default. * * @see Builder#setAllowedCrossTaskNavigations(Set) + * + * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption} */ + @Deprecated @NonNull public Set<ComponentName> getAllowedCrossTaskNavigations() { return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_ALLOWED @@ -340,7 +355,10 @@ public final class VirtualDeviceParams implements Parcelable { * will be be allowed by default. * * @see Builder#setBlockedCrossTaskNavigations(Set) + * + * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption} */ + @Deprecated @NonNull public Set<ComponentName> getBlockedCrossTaskNavigations() { return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_BLOCKED @@ -355,7 +373,10 @@ public final class VirtualDeviceParams implements Parcelable { * * @see Builder#setAllowedCrossTaskNavigations * @see Builder#setBlockedCrossTaskNavigations + * + * @deprecated Use {@link #getDevicePolicy} with {@link #POLICY_TYPE_ACTIVITY} */ + @Deprecated @NavigationPolicy public int getDefaultNavigationPolicy() { return mDefaultNavigationPolicy; @@ -366,7 +387,10 @@ public final class VirtualDeviceParams implements Parcelable { * allowed, except the ones explicitly blocked. * * @see Builder#setAllowedActivities(Set) + * + * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption} */ + @Deprecated @NonNull public Set<ComponentName> getAllowedActivities() { return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED @@ -379,7 +403,10 @@ public final class VirtualDeviceParams implements Parcelable { * that all activities in {@link #getAllowedActivities} are allowed. * * @see Builder#setBlockedActivities(Set) + * + * @deprecated See {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption} */ + @Deprecated @NonNull public Set<ComponentName> getBlockedActivities() { return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED @@ -394,7 +421,10 @@ public final class VirtualDeviceParams implements Parcelable { * * @see Builder#setBlockedActivities * @see Builder#setAllowedActivities + * + * @deprecated Use {@link #getDevicePolicy} with {@link #POLICY_TYPE_ACTIVITY} */ + @Deprecated @ActivityPolicy public int getDefaultActivityPolicy() { return mDefaultActivityPolicy; @@ -743,7 +773,11 @@ public final class VirtualDeviceParams implements Parcelable { * * @param allowedCrossTaskNavigations A set of tasks {@link ComponentName} allowed to * navigate to new tasks in the virtual device. + * + * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and + * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption} */ + @Deprecated @NonNull public Builder setAllowedCrossTaskNavigations( @NonNull Set<ComponentName> allowedCrossTaskNavigations) { @@ -774,7 +808,11 @@ public final class VirtualDeviceParams implements Parcelable { * * @param blockedCrossTaskNavigations A set of tasks {@link ComponentName} to be * blocked from navigating to new tasks in the virtual device. + * + * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and + * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption} */ + @Deprecated @NonNull public Builder setBlockedCrossTaskNavigations( @NonNull Set<ComponentName> blockedCrossTaskNavigations) { @@ -802,7 +840,11 @@ public final class VirtualDeviceParams implements Parcelable { * * @param allowedActivities A set of activity {@link ComponentName} allowed to be launched * in the virtual device. + * + * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and + * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption} */ + @Deprecated @NonNull public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) { if (mDefaultActivityPolicyConfigured @@ -828,7 +870,11 @@ public final class VirtualDeviceParams implements Parcelable { * * @param blockedActivities A set of {@link ComponentName} to be blocked launching from * virtual device. + * + * @deprecated Use {@link #POLICY_TYPE_ACTIVITY} and + * {@link VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption} */ + @Deprecated @NonNull public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) { if (mDefaultActivityPolicyConfigured diff --git a/core/java/android/companion/virtual/camera/IVirtualCamera.aidl b/core/java/android/companion/virtual/camera/IVirtualCamera.aidl new file mode 100644 index 000000000000..58b850dac41c --- /dev/null +++ b/core/java/android/companion/virtual/camera/IVirtualCamera.aidl @@ -0,0 +1,17 @@ +package android.companion.virtual.camera; + +import android.companion.virtual.camera.IVirtualCameraSession; +import android.companion.virtual.camera.VirtualCameraHalConfig; + +/** + * Counterpart of ICameraDevice for virtual camera. + * + * @hide + */ +interface IVirtualCamera { + + IVirtualCameraSession open(); + + VirtualCameraHalConfig getHalConfig(); + +}
\ No newline at end of file diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl new file mode 100644 index 000000000000..252980773264 --- /dev/null +++ b/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.companion.virtual.camera; + +/** + * Counterpart of ICameraDeviceSession for virtual camera. + * + * @hide + */ +interface IVirtualCameraSession { + + void configureStream(int width, int height, int format); + + void close(); +} diff --git a/core/java/android/companion/virtual/camera/VirtualCamera.java b/core/java/android/companion/virtual/camera/VirtualCamera.java new file mode 100644 index 000000000000..791bf0a1ff1f --- /dev/null +++ b/core/java/android/companion/virtual/camera/VirtualCamera.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.camera; + +import android.companion.virtual.IVirtualDevice; +import android.os.RemoteException; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +/** + * Virtual camera that is used to send image data into system. + * + * @hide + */ +public final class VirtualCamera extends IVirtualCamera.Stub { + + private final VirtualCameraConfig mConfig; + + /** + * VirtualCamera device constructor. + * + * @param virtualDevice The Binder object representing this camera in the server. + * @param config Configuration for the new virtual camera + */ + public VirtualCamera( + @NonNull IVirtualDevice virtualDevice, @NonNull VirtualCameraConfig config) { + mConfig = Objects.requireNonNull(config); + Objects.requireNonNull(virtualDevice); + try { + virtualDevice.registerVirtualCamera(this); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** Get the camera session associated with this device */ + @Override + public IVirtualCameraSession open() { + // TODO: b/302255544 - Make this async. + VirtualCameraSession session = mConfig.getCallback().onOpenSession(); + return new VirtualCameraSessionInternal(session); + } + + /** Returns the configuration of this virtual camera instance. */ + @NonNull + public VirtualCameraConfig getConfig() { + return mConfig; + } + + /** + * Returns the configuration to be used by the virtual camera HAL. + * + * @hide + */ + @Override + @NonNull + public VirtualCameraHalConfig getHalConfig() { + return mConfig.getHalConfig(); + } +} diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java new file mode 100644 index 000000000000..a7c3d4fac7dc --- /dev/null +++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.camera; + +import android.hardware.camera2.params.SessionConfiguration; + +import java.util.concurrent.Executor; + +/** + * Interface to be provided when creating a new {@link VirtualCamera} in order to receive callbacks + * from the framework and the camera system. + * + * @see VirtualCameraConfig.Builder#setCallback(Executor, VirtualCameraCallback) + * @hide + */ +public interface VirtualCameraCallback { + + /** + * Called when a client opens a new camera session for the associated {@link VirtualCamera} + * + * @see android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration) + */ + VirtualCameraSession onOpenSession(); +} diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java new file mode 100644 index 000000000000..fb464d53b9b5 --- /dev/null +++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.camera; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.graphics.ImageFormat; +import android.util.ArraySet; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.Executor; + +/** + * Configuration to create a new {@link VirtualCamera}. + * + * <p>Instance of this class are created using the {@link VirtualCameraConfig.Builder}. + * + * @hide + */ +public final class VirtualCameraConfig { + + private final String mDisplayName; + private final Set<VirtualCameraStreamConfig> mStreamConfigurations; + private final VirtualCameraCallback mCallback; + private final Executor mCallbackExecutor; + + /** + * Builder for {@link VirtualCameraConfig}. + * + * <p>To build an instance of {@link VirtualCameraConfig} the following conditions must be met: + * <li>At least one stream must be added wit {@link #addStreamConfiguration(int, int, int)}. + * <li>A name must be set with {@link #setDisplayName(String)} + * <li>A callback must be set wit {@link #setCallback(Executor, VirtualCameraCallback)} + */ + public static final class Builder { + + private String mDisplayName; + private final ArraySet<VirtualCameraStreamConfig> mStreamConfiguration = new ArraySet<>(); + private Executor mCallbackExecutor; + private VirtualCameraCallback mCallback; + + /** Set the visible name of this camera for the user. */ + // TODO: b/290172356 - Take a resource id instead of displayName + @NonNull + public Builder setDisplayName(@NonNull String displayName) { + mDisplayName = requireNonNull(displayName); + return this; + } + + /** + * Add an available stream configuration fot this {@link VirtualCamera}. + * + * <p>At least one {@link VirtualCameraStreamConfig} must be added. + * + * @param width The width of the stream + * @param height The height of the stream + * @param format The {@link ImageFormat} of the stream + */ + @NonNull + public Builder addStreamConfiguration( + int width, int height, @ImageFormat.Format int format) { + VirtualCameraStreamConfig streamConfig = new VirtualCameraStreamConfig(); + streamConfig.width = width; + streamConfig.height = height; + streamConfig.format = format; + mStreamConfiguration.add(streamConfig); + return this; + } + + /** + * Sets the {@link VirtualCameraCallback} used by the framework to communicate with the + * {@link VirtualCamera} owner. + * + * <p>Setting a callback is mandatory. + * + * @param executor The executor onto which the callback methods will be called + * @param callback The instance of the callback to be added. Subsequent call to this method + * will replace the callback set. + */ + public Builder setCallback( + @NonNull Executor executor, @NonNull VirtualCameraCallback callback) { + mCallbackExecutor = requireNonNull(executor); + mCallback = requireNonNull(callback); + return this; + } + + /** + * Builds a new instance of {@link VirtualCameraConfig} + * + * @throws NullPointerException if some required parameters are missing. + */ + @NonNull + public VirtualCameraConfig build() { + return new VirtualCameraConfig( + mDisplayName, mStreamConfiguration, mCallbackExecutor, mCallback); + } + } + + private VirtualCameraConfig( + @NonNull String displayName, + @NonNull Set<VirtualCameraStreamConfig> streamConfigurations, + @NonNull Executor executor, + @NonNull VirtualCameraCallback callback) { + mDisplayName = requireNonNull(displayName, "Missing display name"); + mStreamConfigurations = + Collections.unmodifiableSet( + requireNonNull(streamConfigurations, "Missing stream configuration")); + if (mStreamConfigurations.isEmpty()) { + throw new IllegalArgumentException( + "At least one StreamConfiguration is needed to create a virtual camera."); + } + mCallback = requireNonNull(callback, "Missing callback"); + mCallbackExecutor = requireNonNull(executor, "Missing callback executor"); + } + + /** + * @return The display name of this VirtualCamera + */ + @NonNull + public String getDisplayName() { + return mDisplayName; + } + + /** + * Returns an unmodifiable set of the stream configurations added to this {@link + * VirtualCameraConfig}. + * + * @see VirtualCameraConfig.Builder#addStreamConfiguration(int, int, int) + */ + @NonNull + public Set<VirtualCameraStreamConfig> getStreamConfigs() { + return mStreamConfigurations; + } + + /** Returns the callback used to communicate from the server to the client. */ + @NonNull + public VirtualCameraCallback getCallback() { + return mCallback; + } + + /** Returns the executor onto which the callback should be run. */ + @NonNull + public Executor getCallbackExecutor() { + return mCallbackExecutor; + } + + /** + * Returns a new instance of {@link VirtualCameraHalConfig} initialized with data from this + * {@link VirtualCameraConfig} + */ + @NonNull + public VirtualCameraHalConfig getHalConfig() { + VirtualCameraHalConfig halConfig = new VirtualCameraHalConfig(); + halConfig.displayName = mDisplayName; + halConfig.streamConfigs = mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]); + return halConfig; + } +} diff --git a/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl new file mode 100644 index 000000000000..7070a38b6414 --- /dev/null +++ b/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl @@ -0,0 +1,12 @@ +package android.companion.virtual.camera; + +import android.companion.virtual.camera.VirtualCameraStreamConfig; + +/** + * Configuration for VirtualCamera to be passed to the server and HAL service. + * @hide + */ +parcelable VirtualCameraHalConfig { + String displayName; + VirtualCameraStreamConfig[] streamConfigs; +} diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSession.java b/core/java/android/companion/virtual/camera/VirtualCameraSession.java new file mode 100644 index 000000000000..c25d97711e75 --- /dev/null +++ b/core/java/android/companion/virtual/camera/VirtualCameraSession.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.camera; + +/*** + * Counterpart of {@link android.hardware.camera2.CameraCaptureSession} for producing + * images from a {@link VirtualCamera}. + * @hide + */ +// TODO: b/289881985 - This is just a POC implementation for now, this will be extended +// to a full featured Camera Session +public interface VirtualCameraSession { + + /** Close the session and release its resources. */ + default void close() {} +} diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java b/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java new file mode 100644 index 000000000000..da168de41cee --- /dev/null +++ b/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.camera; + +import android.graphics.ImageFormat; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +/** + * Wraps the client side {@link VirtualCameraSession} into an {@link IVirtualCameraSession}. + * + * @hide + */ +final class VirtualCameraSessionInternal extends IVirtualCameraSession.Stub { + + @SuppressWarnings("FieldCanBeLocal") + // TODO: b/289881985: Will be used once connected with the CameraService + private final VirtualCameraSession mVirtualCameraSession; + + VirtualCameraSessionInternal(@NonNull VirtualCameraSession virtualCameraSession) { + mVirtualCameraSession = Objects.requireNonNull(virtualCameraSession); + } + + @Override + public void configureStream(int width, int height, @ImageFormat.Format int format) {} + + public void close() {} +} diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl new file mode 100644 index 000000000000..304d4553bd2a --- /dev/null +++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.companion.virtual.camera; + +/** + * A stream configuration supported by a virtual camera + * @hide + */ +parcelable VirtualCameraStreamConfig { + int width; + int height; + int format; +} diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index 3e96c96d8d17..d0e13cd977ef 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -34,3 +34,10 @@ flag { description: "Enable Virtual Camera" bug: "270352264" } + +flag { + name: "stream_permissions" + namespace: "virtual_devices" + description: "Enable streaming permission dialogs to Virtual Devices" + bug: "291737919" +} diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index ab669cc141f1..b0ab11f48858 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -45,7 +45,8 @@ ] }, { - "name":"CarrierAppIntegrationTestCases" + "name":"CarrierAppIntegrationTestCases", + "keywords": ["internal"] }, { "name":"CtsSilentUpdateHostTestCases" diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 0c8eb02e9681..b5efe588e63b 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -13,3 +13,17 @@ flag { description: "Feature flag to enable the archiving feature." bug: "278553670" } + +flag { + name: "prevent_sdk_lib_app" + namespace: "package_manager_service" + description: "Feature flag to enable the prevent sdk-library be an application." + bug: "295843617" +} + +flag { + name: "stay_stopped" + namespace: "backstage_power" + description: "Feature flag to improve stopped state enforcement" + bug: "296644915" +} diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index f0c87a138a98..990ebc5fdbcd 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -27,6 +27,7 @@ import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -364,11 +365,20 @@ public final class DisplayManager { /** * Virtual display flag: Indicates that the orientation of this display device is coupled to - * the rotation of its associated logical display. + * the orientation of its associated logical display. + * <p> + * The flag should not be set when the physical display is mounted in a fixed orientation + * such as on a desk. Without this flag, display manager will apply a coordinate transformation + * such as a scale and translation to letterbox or pillarbox format under the assumption that + * the physical orientation of the display is invariant. With this flag set, the content will + * rotate to fill in the space of the display, as it does on the internal device display. + * </p> * * @see #createVirtualDisplay * @hide */ + @SuppressLint("UnflaggedApi") + @SystemApi public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7; /** diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 4700720736b5..4791a8341912 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -698,4 +698,34 @@ public abstract class DisplayManagerInternal { return "AmbientLightSensorData(" + sensorName + ", " + sensorType + ")"; } } + + /** + * Associate a internal display to a {@link DisplayOffloader}. + * + * @param displayId the id of the internal display. + * @param displayOffloader the {@link DisplayOffloader} that controls offloading ops of internal + * display whose id is displayId. + * @return a {@link DisplayOffloadSession} associated with given displayId and displayOffloader. + */ + public abstract DisplayOffloadSession registerDisplayOffloader( + int displayId, DisplayOffloader displayOffloader); + + /** The callbacks that controls the entry & exit of display offloading. */ + public interface DisplayOffloader { + boolean startOffload(); + + void stopOffload(); + } + + /** A session token that associates a internal display with a {@link DisplayOffloader}. */ + public interface DisplayOffloadSession { + /** Provide the display state to use in place of state DOZE. */ + void setDozeStateOverride(int displayState); + /** Returns the associated DisplayOffloader. */ + DisplayOffloader getDisplayOffloader(); + /** Returns whether displayoffload supports the given display state. */ + static boolean isSupportedOffloadState(int displayState) { + return Display.isSuspendedState(displayState); + } + } } diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index 727716e67f28..12442ba12044 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -147,8 +147,8 @@ public final class ProgramSelector implements Parcelable { * * <p>Consists of (from the LSB): * <li> - * <ul>132bit: Station ID number. - * <ul>14bit: HD_SUBCHANNEL. + * <ul>32bit: Station ID number. + * <ul>4bit: HD_SUBCHANNEL. * <ul>18bit: AMFM_FREQUENCY. * </li> * diff --git a/core/java/android/hardware/radio/flags.aconfig b/core/java/android/hardware/radio/flags.aconfig new file mode 100644 index 000000000000..dbc1a4b21cfb --- /dev/null +++ b/core/java/android/hardware/radio/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.hardware.radio" + +flag { + name: "hd_radio_improved" + namespace: "car_framework" + description: "Feature flag for improved HD radio support with less vendor extensions" + bug: "280300929" +} diff --git a/core/java/android/net/metrics/WakeupEvent.java b/core/java/android/net/metrics/WakeupEvent.java index af9a73ca31ee..53a3ea516530 100644 --- a/core/java/android/net/metrics/WakeupEvent.java +++ b/core/java/android/net/metrics/WakeupEvent.java @@ -29,7 +29,7 @@ public class WakeupEvent { public String iface; public int uid; public int ethertype; - public MacAddress dstHwAddr; + public MacAddress dstHwAddr; // actually used to store a src mac address public String srcIp; public String dstIp; public int ipNextHeader; @@ -44,7 +44,7 @@ public class WakeupEvent { j.add(iface); j.add("uid: " + Integer.toString(uid)); j.add("eth=0x" + Integer.toHexString(ethertype)); - j.add("dstHw=" + dstHwAddr); + j.add("srcMac=" + dstHwAddr); // really!! http://b/292404319#comment11 if (ipNextHeader > 0) { j.add("ipNxtHdr=" + ipNextHeader); j.add("srcIp=" + srcIp); diff --git a/core/java/android/net/network-policy-restrictions.md b/core/java/android/net/network-policy-restrictions.md index 04c658c39ad3..20f3d747cf0c 100644 --- a/core/java/android/net/network-policy-restrictions.md +++ b/core/java/android/net/network-policy-restrictions.md @@ -29,8 +29,8 @@ More specifically: | **DS** | *AL* | ok | blk | ok | ok | | **ON** | *!AL* | blk | blk | blk | blk | | | *DL* | blk | blk | blk | blk | -| **DS** | *AL* | blk | blk | ok | ok | -| **OFF** | *!AL* | blk | blk | ok | ok | +| **DS** | *AL* | ok | blk | ok | ok | +| **OFF** | *!AL* | ok | blk | ok | ok | | | *DL* | blk | blk | blk | blk | diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING index 71ad687b7889..5b5ea3790010 100644 --- a/core/java/android/nfc/TEST_MAPPING +++ b/core/java/android/nfc/TEST_MAPPING @@ -2,6 +2,9 @@ "presubmit": [ { "name": "NfcManagerTests" + }, + { + "name": "CtsNfcTestCases" } ] } diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index ada55325aded..f817fb8dcaed 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -32,7 +32,9 @@ import java.io.FileDescriptor; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -613,15 +615,35 @@ public final class BinderProxy implements IBinder { */ public native boolean transactNative(int code, Parcel data, Parcel reply, int flags) throws RemoteException; + + /* This list is to hold strong reference to the death recipients that are waiting for the death + * of binder that this proxy references. Previously, the death recipients were strongy + * referenced from JNI, but that can cause memory leak (b/298374304) when the application has a + * strong reference from the death recipient to the proxy object. The JNI reference is now weak. + * And this strong reference is to keep death recipients at least until the proxy is GC'ed. */ + private List<DeathRecipient> mDeathRecipients = Collections.synchronizedList(new ArrayList<>()); + /** * See {@link IBinder#linkToDeath(DeathRecipient, int)} */ - public native void linkToDeath(DeathRecipient recipient, int flags) - throws RemoteException; + public void linkToDeath(DeathRecipient recipient, int flags) + throws RemoteException { + linkToDeathNative(recipient, flags); + mDeathRecipients.add(recipient); + } + /** * See {@link IBinder#unlinkToDeath} */ - public native boolean unlinkToDeath(DeathRecipient recipient, int flags); + public boolean unlinkToDeath(DeathRecipient recipient, int flags) { + mDeathRecipients.remove(recipient); + return unlinkToDeathNative(recipient, flags); + } + + private native void linkToDeathNative(DeathRecipient recipient, int flags) + throws RemoteException; + + private native boolean unlinkToDeathNative(DeathRecipient recipient, int flags); /** * Perform a dump on the remote object diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java index f10467f0760e..47ad72fe8d0e 100644 --- a/core/java/android/os/BugreportParams.java +++ b/core/java/android/os/BugreportParams.java @@ -124,6 +124,8 @@ public final class BugreportParams { /** * Options for a lightweight bugreport intended to be taken for onboarding-related flows. + * + * @hide */ public static final int BUGREPORT_MODE_ONBOARDING = IDumpstate.BUGREPORT_MODE_ONBOARDING; diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index ad3abd9b531c..2d6e09a1b268 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -8,7 +8,6 @@ "name": "FrameworksVibratorCoreTests", "options": [ {"exclude-annotation": "androidx.test.filters.LargeTest"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] @@ -21,7 +20,6 @@ "name": "FrameworksVibratorServicesTests", "options": [ {"exclude-annotation": "androidx.test.filters.LargeTest"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] @@ -34,7 +32,6 @@ "name": "CtsVibratorTestCases", "options": [ {"exclude-annotation": "androidx.test.filters.LargeTest"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 80b7d40c14c5..4c8ef97a7437 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2940,6 +2940,12 @@ public class UserManager { * Used to check if the context user is a restricted profile. Restricted profiles * may have a reduced number of available apps, app restrictions, and account restrictions. * + * <p>The caller must be in the same profile group as the context user or else hold + * <li>{@link android.Manifest.permission#MANAGE_USERS}, + * <li>or {@link android.Manifest.permission#CREATE_USERS}, + * <li>or, for devices after {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * {@link android.Manifest.permission#QUERY_USERS}. + * * @return whether the context user is a restricted profile. * @hide */ @@ -2963,6 +2969,12 @@ public class UserManager { * Check if a user is a restricted profile. Restricted profiles may have a reduced number of * available apps, app restrictions, and account restrictions. * + * <p>Requires + * <li>{@link android.Manifest.permission#MANAGE_USERS}, + * <li>or {@link android.Manifest.permission#CREATE_USERS}, + * <li>or, for devices after {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * {@link android.Manifest.permission#QUERY_USERS}. + * * @param user the user to check * @return whether the user is a restricted profile. * @hide diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 77be5d400853..1294f983b3b9 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -6,3 +6,10 @@ flag { description: "enable device aware permission APIs" bug: "274852670" } + +flag { + name: "voice_activation_permission_apis" + namespace: "permissions" + description: "enable voice activation permission APIs" + bug: "287264308" +}
\ No newline at end of file diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e829ca288925..f40232b266d0 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9908,6 +9908,48 @@ public final class Settings { */ public static final String DOCK_SETUP_STATE = "dock_setup_state"; + + /** + * Default, indicates that the user has not yet started the hub mode tutorial. + * + * @hide + */ + public static final int HUB_MODE_TUTORIAL_NOT_STARTED = 0; + + /** + * Indicates that the user has started but not yet completed the hub mode tutorial. + * One of the possible states for {@link #HUB_MODE_TUTORIAL_STATE}. + * + * @hide + */ + public static final int HUB_MODE_TUTORIAL_STARTED = 1; + + /** + * Indicates that the user has completed the hub mode tutorial. + * One of the possible states for {@link #HUB_MODE_TUTORIAL_STATE}. + * + * @hide + */ + public static final int HUB_MODE_TUTORIAL_COMPLETED = 10; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + HUB_MODE_TUTORIAL_NOT_STARTED, + HUB_MODE_TUTORIAL_STARTED, + HUB_MODE_TUTORIAL_COMPLETED + }) + public @interface HubModeTutorialState { + } + + /** + * Defines the user's current state of navigating through the hub mode tutorial. + * The possible states are defined in {@link HubModeTutorialState}. + * + * @hide + */ + public static final String HUB_MODE_TUTORIAL_STATE = "hub_mode_tutorial_state"; + /** * The default NFC payment component * @hide @@ -11105,6 +11147,12 @@ public final class Settings { public static final String BLUETOOTH_ON_WHILE_DRIVING = "bluetooth_on_while_driving"; /** + * Volume dialog timeout in ms. + * @hide + */ + public static final String VOLUME_DIALOG_DISMISS_TIMEOUT = "volume_dialog_dismiss_timeout"; + + /** * What behavior should be invoked when the volume hush gesture is triggered * One of VOLUME_HUSH_OFF, VOLUME_HUSH_VIBRATE, VOLUME_HUSH_MUTE. * diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java index 2dbb5da9a258..dae3202b2043 100644 --- a/core/java/android/security/FileIntegrityManager.java +++ b/core/java/android/security/FileIntegrityManager.java @@ -76,28 +76,38 @@ public final class FileIntegrityManager { * Enables fs-verity to the owned file under the calling app's private directory. It always uses * the common configuration, i.e. SHA-256 digest algorithm, 4K block size, and without salt. * - * The operation can only succeed when the file is not opened as writable by any process. + * <p>For enabling fs-verity to succeed, the device must support fs-verity, the file must be + * writable by the app and not already have fs-verity enabled, and the file must not currently + * be open for writing by any process. To check whether the device supports fs-verity, use + * {@link #isApkVeritySupported()}. * - * It takes O(file size) time to build the underlying data structure for continuous + * <p>It takes O(file size) time to build the underlying data structure for continuous * verification. The operation is atomic, i.e. it's either enabled or not, even in case of * power failure during or after the call. * - * Note for the API users: When the file's authenticity is crucial, the app typical needs to + * <p>Note for the API users: When the file's authenticity is crucial, the app typical needs to * perform a signature check by itself before using the file. The signature is often delivered * as a separate file and stored next to the targeting file in the filesystem. The public key of * the signer (normally the same app developer) can be put in the APK, and the app can use the * public key to verify the signature to the file's actual fs-verity digest (from {@link - * #getFsVerityDigest}) before using the file. The exact format is not prescribed by the + * #getFsVerityDigest(File)}) before using the file. The exact format is not prescribed by the * framework. App developers may choose to use common practices like JCA for the signing and * verification, or their own preferred approach. * - * @param file The file to enable fs-verity. It should be an absolute path. + * @param file The file to enable fs-verity. It must represent an absolute path. + * @throws IllegalArgumentException If the provided file is not an absolute path. + * @throws IOException If the operation failed. * * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a> */ @FlaggedApi(Flags.FLAG_FSVERITY_API) public void setupFsVerity(@NonNull File file) throws IOException { if (!file.isAbsolute()) { + // fs-verity is to be enabled by installd, which enforces the validation to the + // (untrusted) file path passed from here. To make this less error prone, installd + // accepts only absolute path. When a relative path is provided, we fail with an + // explicit exception to help developers understand the requirement to use an absolute + // path. throw new IllegalArgumentException("Expect an absolute path"); } IFsveritySetupAuthToken authToken; @@ -121,11 +131,12 @@ public final class FileIntegrityManager { } /** - * Returns the fs-verity digest for the owned file under the calling app's - * private directory, or null when the file does not have fs-verity enabled. + * Returns the fs-verity digest for the owned file under the calling app's private directory, or + * null when the file does not have fs-verity enabled (including when fs-verity is not supported + * on older devices). * * @param file The file to measure the fs-verity digest. - * @return The fs-verity digeset in byte[], null if none. + * @return The fs-verity digest in byte[], null if none. * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a> */ @FlaggedApi(Flags.FLAG_FSVERITY_API) diff --git a/core/java/android/service/notification/TEST_MAPPING b/core/java/android/service/notification/TEST_MAPPING index 7b8d52f51cee..468c4518602e 100644 --- a/core/java/android/service/notification/TEST_MAPPING +++ b/core/java/android/service/notification/TEST_MAPPING @@ -4,9 +4,6 @@ "name": "CtsNotificationTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { @@ -21,9 +18,6 @@ "name": "FrameworksUiServicesTests", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 94d851603064..6a82f6da67b3 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -959,8 +959,8 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales, AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER); } - notifyStateChangedLocked(); } + notifyStateChanged(availability); } /** @@ -1370,8 +1370,8 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { mAvailability = STATE_INVALID; mIsAvailabilityOverriddenByTestApi = false; - notifyStateChangedLocked(); } + notifyStateChanged(STATE_INVALID); super.destroy(); } @@ -1401,6 +1401,8 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { */ // TODO(b/281608561): remove the enrollment flow from AlwaysOnHotwordDetector void onSoundModelsChanged() { + boolean notifyError = false; + synchronized (mLock) { if (mAvailability == STATE_INVALID || mAvailability == STATE_HARDWARE_UNAVAILABLE @@ -1441,6 +1443,9 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { // calling stopRecognition where there is no started session. Log.w(TAG, "Failed to stop recognition after enrollment update: code=" + result); + + // Execute a refresh availability task - which should then notify of a change. + new RefreshAvailabilityTask().execute(); } catch (Exception e) { Slog.w(TAG, "Failed to stop recognition after enrollment update", e); if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) { @@ -1449,14 +1454,14 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { + Log.getStackTraceString(e), FailureSuggestedAction.RECREATE_DETECTOR)); } else { - updateAndNotifyStateChangedLocked(STATE_ERROR); + notifyError = true; } - return; } } + } - // Execute a refresh availability task - which should then notify of a change. - new RefreshAvailabilityTask().execute(); + if (notifyError) { + updateAndNotifyStateChanged(STATE_ERROR); } } @@ -1572,10 +1577,11 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } } - @GuardedBy("mLock") - private void updateAndNotifyStateChangedLocked(int availability) { - updateAvailabilityLocked(availability); - notifyStateChangedLocked(); + private void updateAndNotifyStateChanged(int availability) { + synchronized (mLock) { + updateAvailabilityLocked(availability); + } + notifyStateChanged(availability); } @GuardedBy("mLock") @@ -1589,17 +1595,17 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } } - @GuardedBy("mLock") - private void notifyStateChangedLocked() { + private void notifyStateChanged(int newAvailability) { Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED); - message.arg1 = mAvailability; + message.arg1 = newAvailability; message.sendToTarget(); } - @GuardedBy("mLock") private void sendUnknownFailure(String failureMessage) { - // update but do not call onAvailabilityChanged callback for STATE_ERROR - updateAvailabilityLocked(STATE_ERROR); + synchronized (mLock) { + // update but do not call onAvailabilityChanged callback for STATE_ERROR + updateAvailabilityLocked(STATE_ERROR); + } Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget(); } @@ -1802,19 +1808,17 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { availability = STATE_KEYPHRASE_UNENROLLED; } } - updateAndNotifyStateChangedLocked(availability); } + updateAndNotifyStateChanged(availability); } catch (Exception e) { // Any exception here not caught will crash the process because AsyncTask does not // bubble up the exceptions to the client app, so we must propagate it to the app. Slog.w(TAG, "Failed to refresh availability", e); - synchronized (mLock) { - if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) { - sendUnknownFailure( - "Failed to refresh availability: " + Log.getStackTraceString(e)); - } else { - updateAndNotifyStateChangedLocked(STATE_ERROR); - } + if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) { + sendUnknownFailure( + "Failed to refresh availability: " + Log.getStackTraceString(e)); + } else { + updateAndNotifyStateChanged(STATE_ERROR); } } diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java new file mode 100644 index 000000000000..46fa5017106b --- /dev/null +++ b/core/java/android/text/ClientFlags.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text; + +import com.android.text.flags.Flags; + +/** + * An aconfig feature flags that can be accessible from application process without + * ContentProvider IPCs. + * + * When you add new flags, you have to add flag string to {@link TextFlags#TEXT_ACONFIGS_FLAGS}. + * + * @hide + */ +public class ClientFlags { + + /** + * @see Flags#deprecateFontsXml() + */ + public static boolean deprecateFontsXml() { + return TextFlags.isFeatureEnabled(Flags.FLAG_DEPRECATE_FONTS_XML); + } + + /** + * @see Flags#noBreakNoHyphenationSpan() + */ + public static boolean noBreakNoHyphenationSpan() { + return TextFlags.isFeatureEnabled(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN); + } + + /** + * @see Flags#phraseStrictFallback() + */ + public static boolean phraseStrictFallback() { + return TextFlags.isFeatureEnabled(Flags.FLAG_PHRASE_STRICT_FALLBACK); + } + + /** + * @see Flags#useBoundsForWidth() + */ + public static boolean useBoundsForWidth() { + return TextFlags.isFeatureEnabled(Flags.FLAG_USE_BOUNDS_FOR_WIDTH); + } +} diff --git a/core/java/android/text/TEST_MAPPING b/core/java/android/text/TEST_MAPPING index 0fe974afd15c..c9bd2cacb138 100644 --- a/core/java/android/text/TEST_MAPPING +++ b/core/java/android/text/TEST_MAPPING @@ -4,7 +4,7 @@ "name": "CtsTextTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "exclude-annotation": "androidx.test.filters.LargeTest" diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java index 4be6a8def852..536e3ccd98f6 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -16,6 +16,11 @@ package android.text; +import android.annotation.NonNull; +import android.app.AppGlobals; + +import com.android.text.flags.Flags; + /** * Flags in the "text" namespace. * @@ -46,4 +51,28 @@ public final class TextFlags { */ public static final boolean ENABLE_NEW_CONTEXT_MENU_DEFAULT = true; + /** + * List of text flags to be transferred to the application process. + */ + public static final String[] TEXT_ACONFIGS_FLAGS = { + Flags.FLAG_DEPRECATE_FONTS_XML, + Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN, + Flags.FLAG_PHRASE_STRICT_FALLBACK, + Flags.FLAG_USE_BOUNDS_FOR_WIDTH, + }; + + /** + * Get a key for the feature flag. + */ + public static String getKeyForFlag(@NonNull String flag) { + return "text__" + flag; + } + + /** + * Return true if the feature flag is enabled. + */ + public static boolean isFeatureEnabled(@NonNull String flag) { + return AppGlobals.getIntCoreSetting( + getKeyForFlag(flag), 0 /* aconfig is false by default */) != 0; + } } diff --git a/core/java/android/text/flags/fix_double_underline.aconfig b/core/java/android/text/flags/fix_double_underline.aconfig new file mode 100644 index 000000000000..b0aa72a765cc --- /dev/null +++ b/core/java/android/text/flags/fix_double_underline.aconfig @@ -0,0 +1,8 @@ +package: "com.android.text.flags" + +flag { + name: "fix_double_underline" + namespace: "text" + description: "Feature flag for fixing double underline because of the multiple font used in the single line." + bug: "297336724" +} diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index a46136a53e95..31d759ea92e6 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -264,6 +264,16 @@ public abstract class DisplayEventReceiver { } /** + * Called when a display hotplug event with connection error is received. + * + * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()} + * timebase. + * @param connectionError the hotplug connection error code. + */ + public void onHotplugConnectionError(long timestampNanos, int connectionError) { + } + + /** * Called when a display mode changed event is received. * * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()} @@ -345,6 +355,11 @@ public abstract class DisplayEventReceiver { onHotplug(timestampNanos, physicalDisplayId, connected); } + @SuppressWarnings("unused") + private void dispatchHotplugConnectionError(long timestampNanos, int connectionError) { + onHotplugConnectionError(timestampNanos, connectionError); + } + // Called from native code. @SuppressWarnings("unused") private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId, diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 512f4f2b5d22..981911ec8880 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.Display.Mode.INVALID_MODE_ID; import static android.view.DisplayInfoProto.APP_HEIGHT; import static android.view.DisplayInfoProto.APP_WIDTH; import static android.view.DisplayInfoProto.CUTOUT; @@ -200,6 +201,11 @@ public final class DisplayInfo implements Parcelable { public int defaultModeId; /** + * The user preferred display mode. + */ + public int userPreferredModeId = INVALID_MODE_ID; + + /** * The supported modes of this display. */ public Display.Mode[] supportedModes = Display.Mode.EMPTY_ARRAY; @@ -420,6 +426,7 @@ public final class DisplayInfo implements Parcelable { && modeId == other.modeId && renderFrameRate == other.renderFrameRate && defaultModeId == other.defaultModeId + && userPreferredModeId == other.userPreferredModeId && Arrays.equals(supportedModes, other.supportedModes) && colorMode == other.colorMode && Arrays.equals(supportedColorModes, other.supportedColorModes) @@ -478,6 +485,7 @@ public final class DisplayInfo implements Parcelable { modeId = other.modeId; renderFrameRate = other.renderFrameRate; defaultModeId = other.defaultModeId; + userPreferredModeId = other.userPreferredModeId; supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length); colorMode = other.colorMode; supportedColorModes = Arrays.copyOf( @@ -530,6 +538,7 @@ public final class DisplayInfo implements Parcelable { modeId = source.readInt(); renderFrameRate = source.readFloat(); defaultModeId = source.readInt(); + userPreferredModeId = source.readInt(); int nModes = source.readInt(); supportedModes = new Display.Mode[nModes]; for (int i = 0; i < nModes; i++) { @@ -596,6 +605,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(modeId); dest.writeFloat(renderFrameRate); dest.writeInt(defaultModeId); + dest.writeInt(userPreferredModeId); dest.writeInt(supportedModes.length); for (int i = 0; i < supportedModes.length; i++) { supportedModes[i].writeToParcel(dest, flags); @@ -832,9 +842,12 @@ public final class DisplayInfo implements Parcelable { sb.append(presentationDeadlineNanos); sb.append(", mode "); sb.append(modeId); + sb.append(", renderFrameRate "); sb.append(renderFrameRate); sb.append(", defaultMode "); sb.append(defaultModeId); + sb.append(", userPreferredModeId "); + sb.append(userPreferredModeId); sb.append(", modes "); sb.append(Arrays.toString(supportedModes)); sb.append(", hdrCapabilities "); diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index bdd0a9cf653a..e4e8b7b5b6a1 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -30,8 +31,10 @@ import android.graphics.SurfaceTexture; import android.graphics.TextureLayer; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.Trace; import android.util.AttributeSet; import android.util.Log; +import android.view.flags.Flags; /** * <p>A TextureView can be used to display a content stream, such as that @@ -51,9 +54,9 @@ import android.util.Log; * <th style="text-align: center;">SurfaceView</th> * </tr> * <tr> - * <td>Supports alpha</td> + * <td>Supports View alpha</td> * <td style="text-align: center;">X</td> - * <td style="text-align: center;"> </td> + * <td style="text-align: center;">U+</td> * </tr> * <tr> * <td>Supports rotations</td> @@ -194,6 +197,9 @@ public class TextureView extends View { private Canvas mCanvas; private int mSaveCount; + @FloatRange(from = 0.0) float mFrameRate; + @Surface.FrameRateCompatibility int mFrameRateCompatibility; + private final Object[] mNativeWindowLock = new Object[0]; // Set by native code, do not write! @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -465,6 +471,16 @@ public class TextureView extends View { mLayer.setSurfaceTexture(mSurface); mSurface.setDefaultBufferSize(getWidth(), getHeight()); mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler); + if (Flags.toolkitSetFrameRate()) { + mSurface.setOnSetFrameRateListener( + (surfaceTexture, frameRate, compatibility, strategy) -> { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.instant(Trace.TRACE_TAG_VIEW, "setFrameRate: " + frameRate); + } + mFrameRate = frameRate; + mFrameRateCompatibility = compatibility; + }, mAttachInfo.mHandler); + } if (mListener != null && createNewSurface) { mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight()); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 55374b994cd4..76741317f8d1 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1328,6 +1328,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public static final String AUTOFILL_HINT_PASSWORD_AUTO = "passwordAuto"; /** + * Hint indicating that the developer intends to fill this view with output from + * CredentialManager. + * + * @hide + */ + public static final String AUTOFILL_HINT_CREDENTIAL_MANAGER = "credential"; + + /** * Hints for the autofill services that describes the content of the view. */ private @Nullable String[] mAutofillHints; @@ -8562,6 +8570,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * announcements every time a View is updated. * * <p> + * For notifying users about errors, such as in a login screen with text that displays an + * "incorrect password" notification, that view should send an AccessibilityEvent of type + * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set + * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose + * error-setting methods that support accessibility automatically. For example, instead of + * explicitly sending this event when using a TextView, use + * {@link android.widget.TextView#setError(CharSequence)}. + * + * <p> * Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the * user interface. While a live region may send different types of events generated by the view, * state description will send {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events of @@ -32618,6 +32635,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see android.view.inputmethod.InputMethodManager#startStylusHandwriting(View) * @param enabled whether auto handwriting initiation is enabled for this view. * @attr ref android.R.styleable#View_autoHandwritingEnabled + * @see EditorInfo#setStylusHandwritingEnabled(boolean) */ public void setAutoHandwritingEnabled(boolean enabled) { if (enabled) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0ba5d06b64dc..f4213510a1c1 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3858,9 +3858,7 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } - if (mActiveSurfaceSyncGroup != null) { - mActiveSurfaceSyncGroup.markSyncReady(); - } + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction); } else if (cancelAndRedraw) { mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason() @@ -3874,8 +3872,8 @@ public final class ViewRootImpl implements ViewParent, } mPendingTransitions.clear(); } - if (!performDraw(mActiveSurfaceSyncGroup) && mActiveSurfaceSyncGroup != null) { - mActiveSurfaceSyncGroup.markSyncReady(); + if (!performDraw(mActiveSurfaceSyncGroup)) { + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction); } } @@ -3890,6 +3888,7 @@ public final class ViewRootImpl implements ViewParent, mReportNextDraw = false; mLastReportNextDrawReason = null; mActiveSurfaceSyncGroup = null; + mHasPendingTransactions = false; mSyncBuffer = false; if (isInWMSRequestedSync()) { mWmsRequestSyncGroup.markSyncReady(); @@ -4688,7 +4687,8 @@ public final class ViewRootImpl implements ViewParent, return false; } - final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null; + final boolean fullRedrawNeeded = + mFullRedrawNeeded || surfaceSyncGroup != null || mHasPendingTransactions; mFullRedrawNeeded = false; mIsDrawing = true; @@ -4718,8 +4718,15 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mPendingAnimatingRenderNodes.clear(); } - if (mReportNextDraw) { + final Transaction pendingTransaction; + if (!usingAsyncReport && mHasPendingTransactions) { + pendingTransaction = new Transaction(); + pendingTransaction.merge(mPendingTransaction); + } else { + pendingTransaction = null; + } + if (mReportNextDraw) { // if we're using multi-thread renderer, wait for the window frame draws if (mWindowDrawCountDown != null) { try { @@ -4741,9 +4748,7 @@ public final class ViewRootImpl implements ViewParent, if (mSurfaceHolder != null && mSurface.isValid()) { usingAsyncReport = true; SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> { - if (surfaceSyncGroup != null) { - surfaceSyncGroup.markSyncReady(); - } + handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction); }); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); @@ -4756,15 +4761,27 @@ public final class ViewRootImpl implements ViewParent, } } - if (surfaceSyncGroup != null && !usingAsyncReport) { - surfaceSyncGroup.markSyncReady(); + if (!usingAsyncReport) { + handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction); } + if (mPerformContentCapture) { performContentCaptureInitialReport(); } return true; } + private void handleSyncRequestWhenNoAsyncDraw(SurfaceSyncGroup surfaceSyncGroup, + @Nullable Transaction pendingTransaction) { + if (surfaceSyncGroup != null) { + if (pendingTransaction != null) { + surfaceSyncGroup.addTransaction(pendingTransaction); + } + surfaceSyncGroup.markSyncReady(); + } else if (pendingTransaction != null) { + pendingTransaction.apply(); + } + } /** * Checks (and caches) if content capture is enabled for this context. */ @@ -4850,8 +4867,8 @@ public final class ViewRootImpl implements ViewParent, } } - private boolean draw(boolean fullRedrawNeeded, - @Nullable SurfaceSyncGroup activeSyncGroup, boolean syncBuffer) { + private boolean draw(boolean fullRedrawNeeded, @Nullable SurfaceSyncGroup activeSyncGroup, + boolean syncBuffer) { Surface surface = mSurface; if (!surface.isValid()) { return false; @@ -4995,12 +5012,11 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mThreadedRenderer.forceDrawNextFrame(); } } else if (mHasPendingTransactions) { - // Register a calback if there's no sync involved but there were calls to + // Register a callback if there's no sync involved but there were calls to // applyTransactionOnDraw. If there is a sync involved, the sync callback will // handle merging the pending transaction. registerCallbackForPendingTransactions(); } - mHasPendingTransactions = false; mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); } else { @@ -8977,13 +8993,7 @@ public final class ViewRootImpl implements ViewParent, mAdded = false; AnimationHandler.removeRequestor(this); } - if (mActiveSurfaceSyncGroup != null) { - mActiveSurfaceSyncGroup.markSyncReady(); - mActiveSurfaceSyncGroup = null; - } - if (mHasPendingTransactions) { - mPendingTransaction.apply(); - } + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction); WindowManagerGlobal.getInstance().doRemoveView(this); } @@ -11502,9 +11512,7 @@ public final class ViewRootImpl implements ViewParent, Log.d(mTag, "registerCallbacksForSync syncBuffer=" + syncBuffer); } - Transaction t = new Transaction(); - t.merge(mPendingTransaction); - + surfaceSyncGroup.addTransaction(mPendingTransaction); mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() { @Override public void onFrameDraw(long frame) { @@ -11518,7 +11526,6 @@ public final class ViewRootImpl implements ViewParent, + frame + "."); } - mergeWithNextTransaction(t, frame); // If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or // SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up // any blast sync or commit callback, and the code should directly call diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index 8ba8b8cca5ed..a07b62fe2890 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -16,7 +16,11 @@ package android.view.animation; +import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API; +import static android.view.flags.Flags.expectedPresentationTimeApi; + import android.annotation.AnimRes; +import android.annotation.FlaggedApi; import android.annotation.InterpolatorRes; import android.annotation.TestApi; import android.compat.annotation.ChangeId; @@ -151,7 +155,12 @@ public class AnimationUtils { * @return the expected presentation time of a frame in the * {@link System#nanoTime()} time base. */ + @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API) public static long getExpectedPresentationTimeNanos() { + if (!expectedPresentationTimeApi()) { + return SystemClock.uptimeMillis(); + } + AnimationState state = sAnimationState.get(); return state.mExpectedPresentationTimeNanos; } @@ -164,6 +173,7 @@ public class AnimationUtils { * @return the expected presentation time of a frame in the * {@link SystemClock#uptimeMillis()} time base. */ + @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API) public static long getExpectedPresentationTimeMillis() { return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS; } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 4cb8788ab9f2..6cf185a5b460 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1464,7 +1464,7 @@ public final class AutofillManager { if (infos.size() == 0) { throw new IllegalArgumentException("No VirtualViewInfo found"); } - if (view.isCredential() && mIsFillAndSaveDialogDisabledForCredentialManager) { + if (isCredmanRequested(view) && mIsFillAndSaveDialogDisabledForCredentialManager) { if (sDebug) { Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:" + view.getAutofillId().toString()); @@ -1488,7 +1488,7 @@ public final class AutofillManager { * @hide */ public void notifyViewEnteredForFillDialog(View v) { - if (v.isCredential() + if (isCredmanRequested(v) && mIsFillAndSaveDialogDisabledForCredentialManager) { if (sDebug) { Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:" @@ -3434,6 +3434,22 @@ public final class AutofillManager { } } + private boolean isCredmanRequested(View view) { + if (view.isCredential()) { + return true; + } + String[] hints = view.getAutofillHints(); + if (hints == null) { + return false; + } + for (String hint : hints) { + if (hint.equals(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) { + return true; + } + } + return false; + } + /** * Find a single view by its id. * diff --git a/core/java/android/view/flags/variable_refresh_rate_flags.aconfig b/core/java/android/view/flags/variable_refresh_rate_flags.aconfig index e3972ade8aa3..13a6f8d52dc6 100644 --- a/core/java/android/view/flags/variable_refresh_rate_flags.aconfig +++ b/core/java/android/view/flags/variable_refresh_rate_flags.aconfig @@ -12,4 +12,11 @@ flag { namespace: "toolkit" description: "Feature flag for toolkit to set frame rate" bug: "293512962" +} + +flag { + name: "expected_presentation_time_api" + namespace: "toolkit" + description: "Feature flag for using expected presentation time of the Choreographer" + bug: "278730197" }
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index 4e5cec7b777d..a92420a2f373 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -23,7 +23,9 @@ import static android.view.inputmethod.EditorInfoProto.INPUT_TYPE; import static android.view.inputmethod.EditorInfoProto.PACKAGE_NAME; import static android.view.inputmethod.EditorInfoProto.PRIVATE_IME_OPTIONS; import static android.view.inputmethod.EditorInfoProto.TARGET_INPUT_METHOD_USER_ID; +import static android.view.inputmethod.Flags.FLAG_EDITORINFO_HANDWRITING_ENABLED; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -45,6 +47,7 @@ import android.view.MotionEvent; import android.view.MotionEvent.ToolType; import android.view.View; import android.view.autofill.AutofillId; +import android.widget.Editor; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.InputMethodDebug; @@ -716,6 +719,33 @@ public class EditorInfo implements InputType, Parcelable { return set; } + private boolean mIsStylusHandwritingEnabled; + + /** + * Set {@code true} if the {@link Editor} has + * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled. + * {@code false} by default, {@link Editor} must set it {@code true} to indicate that + * it supports stylus handwriting. + * + * @param enabled {@code true} if stylus handwriting is enabled. + * @see View#setAutoHandwritingEnabled(boolean) + */ + @FlaggedApi(FLAG_EDITORINFO_HANDWRITING_ENABLED) + public void setStylusHandwritingEnabled(boolean enabled) { + mIsStylusHandwritingEnabled = enabled; + } + + /** + * Returns {@code true} when an {@link Editor} has stylus handwriting enabled. + * {@code false} by default. + * @see #setStylusHandwritingEnabled(boolean) + * @see InputMethodManager#isStylusHandwritingAvailable() + */ + @FlaggedApi(FLAG_EDITORINFO_HANDWRITING_ENABLED) + public boolean isStylusHandwritingEnabled() { + return mIsStylusHandwritingEnabled; + } + /** * If not {@code null}, this editor needs to talk to IMEs that run for the specified user, no * matter what user ID the calling process has. @@ -1211,6 +1241,7 @@ public class EditorInfo implements InputType, Parcelable { pw.println(prefix + "supportedHandwritingGesturePreviewTypes=" + InputMethodDebug.handwritingGestureTypeFlagsToString( mSupportedHandwritingGesturePreviewTypes)); + pw.println(prefix + "isStylusHandwritingEnabled=" + mIsStylusHandwritingEnabled); pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes)); if (targetInputMethodUser != null) { pw.println(prefix + "targetInputMethodUserId=" + targetInputMethodUser.getIdentifier()); @@ -1277,6 +1308,9 @@ public class EditorInfo implements InputType, Parcelable { dest.writeBundle(extras); dest.writeInt(mSupportedHandwritingGestureTypes); dest.writeInt(mSupportedHandwritingGesturePreviewTypes); + if (Flags.editorinfoHandwritingEnabled()) { + dest.writeBoolean(mIsStylusHandwritingEnabled); + } dest.writeBoolean(mInitialSurroundingText != null); if (mInitialSurroundingText != null) { mInitialSurroundingText.writeToParcel(dest, flags); @@ -1316,6 +1350,9 @@ public class EditorInfo implements InputType, Parcelable { res.extras = source.readBundle(); res.mSupportedHandwritingGestureTypes = source.readInt(); res.mSupportedHandwritingGesturePreviewTypes = source.readInt(); + if (Flags.editorinfoHandwritingEnabled()) { + res.mIsStylusHandwritingEnabled = source.readBoolean(); + } boolean hasInitialSurroundingText = source.readBoolean(); if (hasInitialSurroundingText) { res.mInitialSurroundingText = diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index c144289f1a29..c14b5104242a 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -6,4 +6,12 @@ flag { description: "Feature flag for refactoring InsetsController and removing ImeInsetsSourceConsumer" bug: "298172246" is_fixed_read_only: true +} + +flag { + name: "editorinfo_handwriting_enabled" + namespace: "input_method" + description: "Feature flag for adding EditorInfo#mStylusHandwritingEnabled" + bug: "293898187" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index a0d0656a4e50..2c413300ef17 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -103,6 +103,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.text.BoringLayout; +import android.text.ClientFlags; import android.text.DynamicLayout; import android.text.Editable; import android.text.GetChars; @@ -1634,7 +1635,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) { - mUseBoundsForWidth = false; // TODO: Connect to the flag. + mUseBoundsForWidth = ClientFlags.useBoundsForWidth(); } else { mUseBoundsForWidth = false; } diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index d25c8a834c7b..7b7e34172fed 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -25,9 +25,12 @@ import android.window.WindowContainerTransaction; interface ITaskFragmentOrganizerController { /** - * Registers a TaskFragmentOrganizer to manage TaskFragments. + * Registers a TaskFragmentOrganizer to manage TaskFragments. Registering a system + * organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer will have additional + * system capabilities. */ - void registerOrganizer(in ITaskFragmentOrganizer organizer); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true)") + void registerOrganizer(in ITaskFragmentOrganizer organizer, in boolean isSystemOrganizer); /** * Unregisters a previously registered TaskFragmentOrganizer. diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java index a88e394c0985..451acbe84a60 100644 --- a/core/java/android/window/StartingWindowInfo.java +++ b/core/java/android/window/StartingWindowInfo.java @@ -122,7 +122,7 @@ public final class StartingWindowInfo implements Parcelable { TYPE_PARAMETER_PROCESS_RUNNING, TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT, TYPE_PARAMETER_ACTIVITY_CREATED, - TYPE_PARAMETER_ALLOW_ICON, + TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN, TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN, TYPE_PARAMETER_WINDOWLESS, TYPE_PARAMETER_LEGACY_SPLASH_SCREEN @@ -143,7 +143,7 @@ public final class StartingWindowInfo implements Parcelable { /** @hide */ public static final int TYPE_PARAMETER_ACTIVITY_CREATED = 0x00000010; /** @hide */ - public static final int TYPE_PARAMETER_ALLOW_ICON = 0x00000020; + public static final int TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN = 0x00000020; /** * The parameter which indicates if the activity has finished drawing. * @hide diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index f785a3d1514e..a6c9cecb508f 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -22,9 +22,11 @@ import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; import android.annotation.CallSuper; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.os.Bundle; import android.os.IBinder; @@ -32,6 +34,8 @@ import android.os.RemoteException; import android.view.RemoteAnimationDefinition; import android.view.WindowManager; +import com.android.window.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; @@ -140,12 +144,34 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } /** - * Registers a TaskFragmentOrganizer to manage TaskFragments. + * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments. */ @CallSuper public void registerOrganizer() { + // TODO(b/302420256) point to registerOrganizer(boolean) when flag is removed. + try { + getController().registerOrganizer(mInterface, false /* isSystemOrganizer */); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Registers a {@link TaskFragmentOrganizer} to manage TaskFragments. + * + * Registering a system organizer requires MANAGE_ACTIVITY_TASKS permission, and the organizer + * will have additional system capabilities, including: (1) it will receive SurfaceControl for + * the organized TaskFragment, and (2) it needs to update the + * {@link android.view.SurfaceControl} following the window change accordingly. + * + * @hide + */ + @CallSuper + @RequiresPermission(value = "android.permission.MANAGE_ACTIVITY_TASKS", conditional = true) + @FlaggedApi(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG) + public void registerOrganizer(boolean isSystemOrganizer) { try { - getController().registerOrganizer(mInterface); + getController().registerOrganizer(mInterface, isSystemOrganizer); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java index 3c5d60dc8ae2..4dada108c4c6 100644 --- a/core/java/android/window/TaskFragmentTransaction.java +++ b/core/java/android/window/TaskFragmentTransaction.java @@ -30,6 +30,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.view.SurfaceControl; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -192,6 +193,9 @@ public final class TaskFragmentTransaction implements Parcelable { @Nullable private TaskFragmentParentInfo mTaskFragmentParentInfo; + @Nullable + private SurfaceControl mSurfaceControl; + public Change(@ChangeType int type) { mType = type; } @@ -206,6 +210,7 @@ public final class TaskFragmentTransaction implements Parcelable { mActivityIntent = in.readTypedObject(Intent.CREATOR); mActivityToken = in.readStrongBinder(); mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR); + mSurfaceControl = in.readTypedObject(SurfaceControl.CREATOR); } @Override @@ -219,6 +224,7 @@ public final class TaskFragmentTransaction implements Parcelable { dest.writeTypedObject(mActivityIntent, flags); dest.writeStrongBinder(mActivityToken); dest.writeTypedObject(mTaskFragmentParentInfo, flags); + dest.writeTypedObject(mSurfaceControl, flags); } /** The change is related to the TaskFragment created with this unique token. */ @@ -306,6 +312,13 @@ public final class TaskFragmentTransaction implements Parcelable { return this; } + /** @hide */ + @NonNull + public Change setTaskFragmentSurfaceControl(@Nullable SurfaceControl sc) { + mSurfaceControl = sc; + return this; + } + @ChangeType public int getType() { return mType; @@ -359,6 +372,21 @@ public final class TaskFragmentTransaction implements Parcelable { return mTaskFragmentParentInfo; } + /** + * Gets the {@link SurfaceControl} of the TaskFragment. This field is {@code null} for + * a regular {@link TaskFragmentOrganizer} and is only available for a system + * {@link TaskFragmentOrganizer} in the + * {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_APPEARED} event. See + * {@link ITaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer, + * boolean)} + * + * @hide + */ + @Nullable + public SurfaceControl getTaskFragmentSurfaceControl() { + return mSurfaceControl; + } + @Override public String toString() { return "Change{ type=" + mType + " }"; diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java index 9b10a7ff5d12..932608a3b57b 100644 --- a/core/java/android/window/TransitionRequestInfo.java +++ b/core/java/android/window/TransitionRequestInfo.java @@ -16,6 +16,8 @@ package android.window; +import static android.view.WindowManager.transitTypeToString; + import android.annotation.Nullable; import android.app.ActivityManager; import android.app.WindowConfiguration; @@ -88,6 +90,11 @@ public final class TransitionRequestInfo implements Parcelable { this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags); } + /** @hide */ + String typeToString() { + return transitTypeToString(mType); + } + /** Requested change to a display. */ @DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false) public static class DisplayChange implements Parcelable { @@ -263,7 +270,7 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1693425051905L, + time = 1695667226050L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", inputSignatures = "private final int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate int mStartRotation\nprivate int mEndRotation\nprivate boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)") @@ -298,11 +305,11 @@ public final class TransitionRequestInfo implements Parcelable { * @param type * The type of the transition being requested. * @param triggerTask - * If non-null, If non-null, the task containing the activity whose lifecycle change (start or + * If non-null, the task containing the activity whose lifecycle change (start or * finish) has caused this transition to occur. * @param pipTask - * If non-null, If non-null, the task containing the activity whose lifecycle change (start or - * finish) has caused this transition to occur. + * If non-null, the task containing the pip activity that participates in this + * transition. * @param remoteTransition * If non-null, a remote-transition associated with the source of this transition. * @param displayChange @@ -431,7 +438,7 @@ public final class TransitionRequestInfo implements Parcelable { // String fieldNameToString() { ... } return "TransitionRequestInfo { " + - "type = " + mType + ", " + + "type = " + typeToString() + ", " + "triggerTask = " + mTriggerTask + ", " + "pipTask = " + mPipTask + ", " + "remoteTransition = " + mRemoteTransition + ", " + @@ -506,10 +513,10 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1693425051928L, + time = 1695667226088L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", - inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") + inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\n java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") @Deprecated private void __metadata() {} diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java index 0d1871d379fa..0b69030d384f 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -48,7 +48,6 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { static final String TAG = "PackageMonitor"; final IntentFilter mPackageFilt; - final IntentFilter mNonDataFilt; Context mRegisteredContext; Handler mRegisteredHandler; @@ -78,13 +77,6 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { if (isCore) { mPackageFilt.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); } - - mNonDataFilt = new IntentFilter(); - // UserController sends the broadcast - mNonDataFilt.addAction(Intent.ACTION_USER_STOPPED); - if (isCore) { - mNonDataFilt.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - } } @UnsupportedAppUsage @@ -111,10 +103,8 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { mRegisteredHandler = Objects.requireNonNull(handler); if (user != null) { context.registerReceiverAsUser(this, user, mPackageFilt, null, mRegisteredHandler); - context.registerReceiverAsUser(this, user, mNonDataFilt, null, mRegisteredHandler); } else { context.registerReceiver(this, mPackageFilt, null, mRegisteredHandler); - context.registerReceiver(this, mNonDataFilt, null, mRegisteredHandler); } if (mPackageMonitorCallback == null) { PackageManager pm = mRegisteredContext.getPackageManager(); @@ -203,14 +193,19 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { } return false; } - + + /** + * Direct reflection of {@link Intent#ACTION_PACKAGE_CHANGED + * Intent.ACTION_PACKAGE_CHANGED} being received, this callback + * has extras passed in. + */ + public void onPackageChangedWithExtras(String packageName, Bundle extras) { + } + public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { return false; } - public void onHandleUserStop(Intent intent, int userHandle) { - } - public void onUidRemoved(int uid) { } @@ -238,21 +233,34 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { } /** + * Called when a package disappears with extras passed in. + */ + public void onPackageDisappearedWithExtras(String packageName, Bundle extras) { + } + + /** * Called when a package appears for any reason. */ public void onPackageAppeared(String packageName, int reason) { } + + /** + * Called when a package appears with extras passed in. + */ + public void onPackageAppearedWithExtras(String packageName, Bundle extras) { + } + /** * Called when an existing package is updated or its disabled state changes. */ public void onPackageModified(@NonNull String packageName) { } - + public boolean didSomePackagesChange() { return mSomePackagesChanged; } - + public int isPackageAppearing(String packageName) { if (mAppearingPackages != null) { for (int i=mAppearingPackages.length-1; i>=0; i--) { @@ -381,6 +389,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { mChangeType = PACKAGE_PERMANENT_CHANGE; onPackageAdded(pkg, uid); } + onPackageAppearedWithExtras(pkg, intent.getExtras()); onPackageAppeared(pkg, mChangeType); } } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { @@ -403,6 +412,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { onPackageRemovedAllUsers(pkg, uid); } } + onPackageDisappearedWithExtras(pkg, intent.getExtras()); onPackageDisappeared(pkg, mChangeType); } } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { @@ -417,6 +427,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { if (onPackageChanged(pkg, uid, mModifiedComponents)) { mSomePackagesChanged = true; } + onPackageChangedWithExtras(pkg, intent.getExtras()); onPackageModified(pkg); } } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) { @@ -439,10 +450,6 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { intent.getIntExtra(Intent.EXTRA_UID, 0), true); } else if (Intent.ACTION_UID_REMOVED.equals(action)) { onUidRemoved(intent.getIntExtra(Intent.EXTRA_UID, 0)); - } else if (Intent.ACTION_USER_STOPPED.equals(action)) { - if (intent.hasExtra(Intent.EXTRA_USER_HANDLE)) { - onHandleUserStop(intent, intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); - } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); mAppearingPackages = pkgList; diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING index ddfd0ed18bef..c09181f2f496 100644 --- a/core/java/com/android/internal/infra/TEST_MAPPING +++ b/core/java/com/android/internal/infra/TEST_MAPPING @@ -4,7 +4,7 @@ "name": "CtsRoleTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] }, @@ -15,7 +15,7 @@ "include-filter": "android.permission.cts.PermissionControllerTest" }, { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] }, diff --git a/core/java/com/android/internal/net/OWNERS b/core/java/com/android/internal/net/OWNERS index 71f997bb57c5..71576837c2e1 100644 --- a/core/java/com/android/internal/net/OWNERS +++ b/core/java/com/android/internal/net/OWNERS @@ -1,4 +1,4 @@ set noparent -file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking +file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking jsharkey@android.com diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index c6f5086b8346..4d8eeac6d5ab 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -154,6 +154,7 @@ oneway interface IStatusBar void addQsTile(in ComponentName tile); void remQsTile(in ComponentName tile); + void setQsTiles(in String[] tiles); void clickQsTile(in ComponentName tile); void handleSystemKey(in KeyEvent key); @@ -315,7 +316,14 @@ oneway interface IStatusBar */ void requestTileServiceListeningState(in ComponentName componentName); - void requestAddTile(in ComponentName componentName, in CharSequence appName, in CharSequence label, in Icon icon, in IAddTileResultCallback callback); + void requestAddTile( + int callingUid, + in ComponentName componentName, + in CharSequence appName, + in CharSequence label, + in Icon icon, + in IAddTileResultCallback callback + ); void cancelRequestAddTile(in String packageName); /** Notifies System UI about an update to the media tap-to-transfer sender state. */ diff --git a/core/java/com/android/internal/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java index acb0e44f29a6..89f46599322e 100644 --- a/core/java/com/android/internal/widget/CallLayout.java +++ b/core/java/com/android/internal/widget/CallLayout.java @@ -49,6 +49,7 @@ public class CallLayout extends FrameLayout { private CachingIconView mIcon; private CachingIconView mConversationIconBadgeBg; private TextView mConversationText; + private boolean mSetDataAsyncEnabled = false; public CallLayout(@NonNull Context context) { super(context); @@ -83,7 +84,8 @@ public class CallLayout extends FrameLayout { }); } - private void updateCallLayout() { + @NonNull + private Icon getConversationIcon() { CharSequence callerName = ""; String symbol = ""; Icon icon = null; @@ -98,8 +100,7 @@ public class CallLayout extends FrameLayout { if (icon == null) { icon = mPeopleHelper.createAvatarSymbol(callerName, symbol, mLayoutColor); } - // TODO(b/179178086): crop/clip the icon to a circle? - mConversationIconView.setImageIcon(icon); + return icon; } @RemotableViewMethod @@ -123,10 +124,38 @@ public class CallLayout extends FrameLayout { /** * Set the notification extras so that this layout has access */ - @RemotableViewMethod + @RemotableViewMethod(asyncImpl = "setDataAsync") public void setData(Bundle extras) { - setUser(extras.getParcelable(Notification.EXTRA_CALL_PERSON, android.app.Person.class)); - updateCallLayout(); + final Person person = getPerson(extras); + setUser(person); + + final Icon icon = getConversationIcon(); + mConversationIconView.setImageIcon(icon); + } + + + public void setSetDataAsyncEnabled(boolean setDataAsyncEnabled) { + mSetDataAsyncEnabled = setDataAsyncEnabled; + } + + /** + * Async implementation for setData + */ + public Runnable setDataAsync(Bundle extras) { + if (!mSetDataAsyncEnabled) { + return () -> setData(extras); + } + + final Person person = getPerson(extras); + setUser(person); + + final Icon conversationIcon = getConversationIcon(); + return mConversationIconView.setImageIconAsync(conversationIcon); + } + + @Nullable + private Person getPerson(Bundle extras) { + return extras.getParcelable(Notification.EXTRA_CALL_PERSON, Person.class); } private void setUser(Person user) { diff --git a/core/java/com/android/server/net/OWNERS b/core/java/com/android/server/net/OWNERS index 62c5737a2e8e..c24680e9b06a 100644 --- a/core/java/com/android/server/net/OWNERS +++ b/core/java/com/android/server/net/OWNERS @@ -1,2 +1,2 @@ set noparent -file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking +file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking diff --git a/core/jni/Android.bp b/core/jni/Android.bp index ad196c014dec..3795fc8594ae 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -15,7 +15,19 @@ license { ], } -cc_library_shared { +soong_config_module_type { + name: "cc_library_shared_for_libandroid_runtime", + module_type: "cc_library_shared", + config_namespace: "ANDROID", + bool_variables: [ + "release_binder_death_recipient_weak_from_jni", + ], + properties: [ + "cflags", + ], +} + +cc_library_shared_for_libandroid_runtime { name: "libandroid_runtime", host_supported: true, cflags: [ @@ -46,6 +58,12 @@ cc_library_shared { }, }, + soong_config_variables: { + release_binder_death_recipient_weak_from_jni: { + cflags: ["-DBINDER_DEATH_RECIPIENT_WEAK_FROM_JNI"], + }, + }, + cpp_std: "gnu++20", srcs: [ @@ -84,6 +102,7 @@ cc_library_shared { static_libs: [ "libnativehelper_lazy", "libziparchive_for_incfs", + "libguiflags", ], export_include_dirs: [ diff --git a/core/jni/android_graphics_SurfaceTexture.cpp b/core/jni/android_graphics_SurfaceTexture.cpp index 21487abadbae..50832a5c256a 100644 --- a/core/jni/android_graphics_SurfaceTexture.cpp +++ b/core/jni/android_graphics_SurfaceTexture.cpp @@ -17,27 +17,24 @@ #undef LOG_TAG #define LOG_TAG "SurfaceTexture" -#include <stdio.h> - #include <EGL/egl.h> #include <EGL/eglext.h> #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> - +#include <com_android_graphics_libgui_flags.h> +#include <cutils/atomic.h> #include <gui/BufferQueue.h> #include <gui/Surface.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> +#include <stdio.h> #include <surfacetexture/SurfaceTexture.h> #include <surfacetexture/surface_texture_platform.h> - -#include "core_jni_helpers.h" - -#include <cutils/atomic.h> #include <utils/Log.h> #include <utils/misc.h> +#include "core_jni_helpers.h" #include "jni.h" -#include <nativehelper/JNIHelp.h> -#include <nativehelper/ScopedLocalRef.h> // ---------------------------------------------------------------------------- @@ -55,6 +52,7 @@ struct fields_t { jfieldID producer; jfieldID frameAvailableListener; jmethodID postEvent; + jmethodID postOnSetFrameRateEvent; }; static fields_t fields; @@ -139,61 +137,81 @@ bool android_SurfaceTexture_isInstanceOf(JNIEnv* env, jobject thiz) { // ---------------------------------------------------------------------------- -class JNISurfaceTextureContext : public SurfaceTexture::FrameAvailableListener -{ +class JNISurfaceTextureContextCommon { public: - JNISurfaceTextureContext(JNIEnv* env, jobject weakThiz, jclass clazz); - virtual ~JNISurfaceTextureContext(); - virtual void onFrameAvailable(const BufferItem& item); + JNISurfaceTextureContextCommon(JNIEnv* env, jobject weakThiz, jclass clazz) + : mWeakThiz(env->NewGlobalRef(weakThiz)), mClazz((jclass)env->NewGlobalRef(clazz)) {} + + virtual ~JNISurfaceTextureContextCommon() { + JNIEnv* env = getJNIEnv(); + if (env != NULL) { + env->DeleteGlobalRef(mWeakThiz); + env->DeleteGlobalRef(mClazz); + } else { + ALOGW("leaking JNI object references"); + } + } -private: - static JNIEnv* getJNIEnv(); + void onFrameAvailable(const BufferItem& item) { + JNIEnv* env = getJNIEnv(); + if (env != NULL) { + env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz); + } else { + ALOGW("onFrameAvailable event will not posted"); + } + } + +protected: + static JNIEnv* getJNIEnv() { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + if (env == NULL) { + JavaVMAttachArgs args = {JNI_VERSION_1_4, "JNISurfaceTextureContext", NULL}; + JavaVM* vm = AndroidRuntime::getJavaVM(); + int result = vm->AttachCurrentThreadAsDaemon(&env, (void*)&args); + if (result != JNI_OK) { + ALOGE("thread attach failed: %#x", result); + return NULL; + } + } + return env; + } jobject mWeakThiz; jclass mClazz; }; -JNISurfaceTextureContext::JNISurfaceTextureContext(JNIEnv* env, - jobject weakThiz, jclass clazz) : - mWeakThiz(env->NewGlobalRef(weakThiz)), - mClazz((jclass)env->NewGlobalRef(clazz)) -{} - -JNIEnv* JNISurfaceTextureContext::getJNIEnv() { - JNIEnv* env = AndroidRuntime::getJNIEnv(); - if (env == NULL) { - JavaVMAttachArgs args = { - JNI_VERSION_1_4, "JNISurfaceTextureContext", NULL }; - JavaVM* vm = AndroidRuntime::getJavaVM(); - int result = vm->AttachCurrentThreadAsDaemon(&env, (void*)&args); - if (result != JNI_OK) { - ALOGE("thread attach failed: %#x", result); - return NULL; - } +class JNISurfaceTextureContextFrameAvailableListener + : public JNISurfaceTextureContextCommon, + public SurfaceTexture::FrameAvailableListener { +public: + JNISurfaceTextureContextFrameAvailableListener(JNIEnv* env, jobject weakThiz, jclass clazz) + : JNISurfaceTextureContextCommon(env, weakThiz, clazz) {} + void onFrameAvailable(const BufferItem& item) override { + JNISurfaceTextureContextCommon::onFrameAvailable(item); } - return env; -} +}; -JNISurfaceTextureContext::~JNISurfaceTextureContext() -{ - JNIEnv* env = getJNIEnv(); - if (env != NULL) { - env->DeleteGlobalRef(mWeakThiz); - env->DeleteGlobalRef(mClazz); - } else { - ALOGW("leaking JNI object references"); +class JNISurfaceTextureContextListener : public JNISurfaceTextureContextCommon, + public SurfaceTexture::SurfaceTextureListener { +public: + JNISurfaceTextureContextListener(JNIEnv* env, jobject weakThiz, jclass clazz) + : JNISurfaceTextureContextCommon(env, weakThiz, clazz) {} + + void onFrameAvailable(const BufferItem& item) override { + JNISurfaceTextureContextCommon::onFrameAvailable(item); } -} -void JNISurfaceTextureContext::onFrameAvailable(const BufferItem& /* item */) -{ - JNIEnv* env = getJNIEnv(); - if (env != NULL) { - env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz); - } else { - ALOGW("onFrameAvailable event will not posted"); + void onSetFrameRate(float frameRate, int8_t compatibility, + int8_t changeFrameRateStrategy) override { + JNIEnv* env = getJNIEnv(); + if (env != NULL) { + env->CallStaticVoidMethod(mClazz, fields.postOnSetFrameRateEvent, mWeakThiz, frameRate, + compatibility, changeFrameRateStrategy); + } else { + ALOGW("onSetFrameRate event will not posted"); + } } -} +}; // ---------------------------------------------------------------------------- @@ -229,6 +247,13 @@ static void SurfaceTexture_classInit(JNIEnv* env, jclass clazz) if (fields.postEvent == NULL) { ALOGE("can't find android/graphics/SurfaceTexture.postEventFromNative"); } + + fields.postOnSetFrameRateEvent = + env->GetStaticMethodID(clazz, "postOnSetFrameRateEventFromNative", + "(Ljava/lang/ref/WeakReference;FII)V"); + if (fields.postOnSetFrameRateEvent == NULL) { + ALOGE("can't find android/graphics/SurfaceTexture.postOnSetFrameRateEventFromNative"); + } } static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached, @@ -274,17 +299,27 @@ static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached, return; } - sp<JNISurfaceTextureContext> ctx(new JNISurfaceTextureContext(env, weakThiz, - clazz)); - surfaceTexture->setFrameAvailableListener(ctx); - SurfaceTexture_setFrameAvailableListener(env, thiz, ctx); + if (com::android::graphics::libgui::flags::bq_setframerate()) { + sp<JNISurfaceTextureContextListener> ctx( + new JNISurfaceTextureContextListener(env, weakThiz, clazz)); + surfaceTexture->setSurfaceTextureListener(ctx); + } else { + sp<JNISurfaceTextureContextFrameAvailableListener> ctx( + new JNISurfaceTextureContextFrameAvailableListener(env, weakThiz, clazz)); + surfaceTexture->setFrameAvailableListener(ctx); + SurfaceTexture_setFrameAvailableListener(env, thiz, ctx); + } } static void SurfaceTexture_finalize(JNIEnv* env, jobject thiz) { sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz)); - surfaceTexture->setFrameAvailableListener(0); - SurfaceTexture_setFrameAvailableListener(env, thiz, 0); + if (com::android::graphics::libgui::flags::bq_setframerate()) { + surfaceTexture->setSurfaceTextureListener(0); + } else { + surfaceTexture->setFrameAvailableListener(0); + SurfaceTexture_setFrameAvailableListener(env, thiz, 0); + } SurfaceTexture_setSurfaceTexture(env, thiz, 0); SurfaceTexture_setProducer(env, thiz, 0); } diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 041f9c7edeef..bfd80a9e4f74 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -17,19 +17,8 @@ #define LOG_TAG "JavaBinder" //#define LOG_NDEBUG 0 -#include "android_os_Parcel.h" #include "android_util_Binder.h" -#include <atomic> -#include <fcntl.h> -#include <inttypes.h> -#include <mutex> -#include <stdio.h> -#include <string> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - #include <android-base/stringprintf.h> #include <binder/BpBinder.h> #include <binder/IInterface.h> @@ -40,7 +29,16 @@ #include <binder/Stability.h> #include <binderthreadstate/CallerUtils.h> #include <cutils/atomic.h> +#include <fcntl.h> +#include <inttypes.h> #include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedLocalRef.h> +#include <nativehelper/ScopedUtfChars.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> #include <utils/KeyedVector.h> #include <utils/List.h> #include <utils/Log.h> @@ -48,10 +46,11 @@ #include <utils/SystemClock.h> #include <utils/threads.h> -#include <nativehelper/JNIHelp.h> -#include <nativehelper/ScopedLocalRef.h> -#include <nativehelper/ScopedUtfChars.h> +#include <atomic> +#include <mutex> +#include <string> +#include "android_os_Parcel.h" #include "core_jni_helpers.h" //#undef ALOGV @@ -553,14 +552,48 @@ public: }; // ---------------------------------------------------------------------------- +#ifdef BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI +#if __BIONIC__ +#include <android/api-level.h> +static bool target_sdk_is_at_least_vic() { + return android_get_application_target_sdk_version() >= __ANDROID_API_V__; +} +#else +static constexpr bool target_sdk_is_at_least_vic() { + // If not built for Android (i.e. glibc host), follow the latest behavior as there's no compat + // requirement there. + return true; +} +#endif // __BIONIC__ +#endif // BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI class JavaDeathRecipient : public IBinder::DeathRecipient { public: JavaDeathRecipient(JNIEnv* env, jobject object, const sp<DeathRecipientList>& list) - : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)), - mObjectWeak(NULL), mList(list) - { + : mVM(jnienv_to_javavm(env)), mObject(NULL), mObjectWeak(NULL), mList(list) { + // b/298374304: For apps targeting Android V or beyond, we no longer hold the global JNI ref + // to the death recipient objects. This is to prevent the memory leak which can happen when + // the death recipient object internally has a strong reference to the proxy object. Under + // the old behavior, you were unable to kill the binder service by dropping all references + // to the proxy object - because it is still strong referenced from JNI (here). The only way + // to cut the strong reference was to call unlinkDeath(), but it was easy to forget. + // + // Now, the strong reference to the death recipient is held in the Java-side proxy object. + // See BinderProxy.mDeathRecipients. From JNI, only the weak reference is kept. An + // implication of this is that you may not receive binderDied() if you drop all references + // to the proxy object before the service dies. This should be okay for most cases because + // you normally are not interested in the death of a binder service which you don't have any + // reference to. If however you want to get binderDied() regardless of the proxy object's + // lifecycle, keep a strong reference to the death recipient object by yourself. +#ifdef BINDER_DEATH_RECIPIENT_WEAK_FROM_JNI + if (target_sdk_is_at_least_vic()) { + mObjectWeak = env->NewWeakGlobalRef(object); + } else +#endif + { + mObject = env->NewGlobalRef(object); + } // These objects manage their own lifetimes so are responsible for final bookkeeping. // The list holds a strong reference to this object. LOGDEATH("Adding JDR %p to DRL %p", this, list.get()); @@ -573,26 +606,49 @@ public: void binderDied(const wp<IBinder>& who) { LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this); - if (mObject != NULL) { - JNIEnv* env = javavm_to_jnienv(mVM); - ScopedLocalRef<jobject> jBinderProxy(env, javaObjectForIBinder(env, who.promote())); - env->CallStaticVoidMethod(gBinderProxyOffsets.mClass, - gBinderProxyOffsets.mSendDeathNotice, mObject, - jBinderProxy.get()); - if (env->ExceptionCheck()) { - jthrowable excep = env->ExceptionOccurred(); - binder_report_exception(env, excep, - "*** Uncaught exception returned from death notification!"); - } + if (mObject == NULL && mObjectWeak == NULL) { + return; + } + JNIEnv* env = javavm_to_jnienv(mVM); + ScopedLocalRef<jobject> jBinderProxy(env, javaObjectForIBinder(env, who.promote())); + + // Hold a local reference to the recipient. This may fail if the recipient is weakly + // referenced, in which case we can't deliver the death notice. + ScopedLocalRef<jobject> jRecipient(env, + env->NewLocalRef(mObject != NULL ? mObject + : mObjectWeak)); + if (jRecipient.get() == NULL) { + ALOGW("Binder died, but death recipient is already garbage collected. If your target " + "sdk level is at or above 35, this can happen when you dropped all references to " + "the binder service before it died. If you want to get a death notice for a " + "binder service which you have no reference to, keep a strong reference to the " + "death recipient by yourself."); + return; + } - // Serialize with our containing DeathRecipientList so that we can't - // delete the global ref on mObject while the list is being iterated. + if (mFired) { + ALOGW("Received multiple death notices for the same binder object. Binder driver bug?"); + return; + } + mFired = true; + + env->CallStaticVoidMethod(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mSendDeathNotice, + jRecipient.get(), jBinderProxy.get()); + if (env->ExceptionCheck()) { + jthrowable excep = env->ExceptionOccurred(); + binder_report_exception(env, excep, + "*** Uncaught exception returned from death notification!"); + } + + // Demote from strong ref (if exists) to weak after binderDied() has been delivered, to + // allow the DeathRecipient and BinderProxy to be GC'd if no longer needed. Do this in sync + // with our containing DeathRecipientList so that we can't delete the global ref on mObject + // while the list is being iterated. + if (mObject != NULL) { sp<DeathRecipientList> list = mList.promote(); if (list != NULL) { AutoMutex _l(list->lock()); - // Demote from strong ref to weak after binderDied() has been delivered, - // to allow the DeathRecipient and BinderProxy to be GC'd if no longer needed. mObjectWeak = env->NewWeakGlobalRef(mObject); env->DeleteGlobalRef(mObject); mObject = NULL; @@ -659,9 +715,19 @@ protected: private: JavaVM* const mVM; - jobject mObject; // Initial strong ref to Java-side DeathRecipient. Cleared on binderDied(). - jweak mObjectWeak; // Weak ref to the same Java-side DeathRecipient after binderDied(). + + // If target sdk version < 35, the Java-side DeathRecipient is strongly referenced from mObject + // upon linkToDeath() and then after binderDied() is called, the strong reference is demoted to + // a weak reference (mObjectWeak). + // If target sdk version >= 35, the strong reference is never made here (i.e. mObject == NULL + // always). Instead, the strong reference to the Java-side DeathRecipient is made in + // BinderProxy.mDeathRecipients. In the native world, only the weak reference is kept. + jobject mObject; + jweak mObjectWeak; wp<DeathRecipientList> mList; + + // Whether binderDied was called or not. + bool mFired = false; }; // ---------------------------------------------------------------------------- @@ -1435,17 +1501,19 @@ static jobject android_os_BinderProxy_getExtension(JNIEnv* env, jobject obj) { // ---------------------------------------------------------------------------- +// clang-format off static const JNINativeMethod gBinderProxyMethods[] = { /* name, signature, funcPtr */ {"pingBinder", "()Z", (void*)android_os_BinderProxy_pingBinder}, {"isBinderAlive", "()Z", (void*)android_os_BinderProxy_isBinderAlive}, {"getInterfaceDescriptor", "()Ljava/lang/String;", (void*)android_os_BinderProxy_getInterfaceDescriptor}, {"transactNative", "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact}, - {"linkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath}, - {"unlinkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath}, + {"linkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath}, + {"unlinkToDeathNative", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath}, {"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer}, {"getExtension", "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension}, }; +// clang-format on const char* const kBinderProxyPathName = "android/os/BinderProxy"; diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp index 69fc515444b2..41c65aec3144 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -38,6 +38,7 @@ static struct { jmethodID dispatchVsync; jmethodID dispatchHotplug; + jmethodID dispatchHotplugConnectionError; jmethodID dispatchModeChanged; jmethodID dispatchFrameRateOverrides; @@ -89,6 +90,7 @@ private: void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count, VsyncEventData vsyncEventData) override; void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override; + void dispatchHotplugConnectionError(nsecs_t timestamp, int errorCode) override; void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId, nsecs_t renderPeriod) override; void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId, @@ -230,6 +232,22 @@ void NativeDisplayEventReceiver::dispatchHotplug(nsecs_t timestamp, PhysicalDisp mMessageQueue->raiseAndClearException(env, "dispatchHotplug"); } +void NativeDisplayEventReceiver::dispatchHotplugConnectionError(nsecs_t timestamp, + int connectionError) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + + ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal)); + if (receiverObj.get()) { + ALOGV("receiver %p ~ Invoking hotplug dispatchHotplugConnectionError handler.", this); + env->CallVoidMethod(receiverObj.get(), + gDisplayEventReceiverClassInfo.dispatchHotplugConnectionError, + timestamp, connectionError); + ALOGV("receiver %p ~ Returned from hotplug dispatchHotplugConnectionError handler.", this); + } + + mMessageQueue->raiseAndClearException(env, "dispatchHotplugConnectionError"); +} + void NativeDisplayEventReceiver::dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId, nsecs_t renderPeriod) { JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -354,8 +372,12 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { gDisplayEventReceiverClassInfo.dispatchVsync = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJI)V"); - gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env, - gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V"); + gDisplayEventReceiverClassInfo.dispatchHotplug = + GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", + "(JJZ)V"); + gDisplayEventReceiverClassInfo.dispatchHotplugConnectionError = + GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, + "dispatchHotplugConnectionError", "(JI)V"); gDisplayEventReceiverClassInfo.dispatchModeChanged = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchModeChanged", "(JJIJ)V"); diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 4ff30f061332..4e073ca3863e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4830,7 +4830,7 @@ <string translatable="false" name="config_deviceSpecificInputMethodManagerService"></string> <!-- Component name of media projection permission dialog --> - <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.mediaprojection.MediaProjectionPermissionActivity</string> + <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.mediaprojection.permission.MediaProjectionPermissionActivity</string> <!-- Corner radius of system dialogs --> <dimen name="config_dialogCornerRadius">28dp</dimen> @@ -5227,6 +5227,28 @@ non-zero. --> <integer name="config_defaultPeakRefreshRate">0</integer> + <!-- External display peak refresh rate for the given device. Change this value if you want to + prevent the framework from using higher refresh rates, even if display modes with higher + refresh rates are available from hardware composer. Only has an effect if this value and + config_externalDisplayPeakWidth and config_externalDisplayPeakHeight are non-zero. --> + <integer name="config_externalDisplayPeakRefreshRate">0</integer> + + <!-- External display peak width for the given device. Change this value if you want + to prevent the framework from using higher resolution, even if display modes with higher + resolutions are available from hardware composer. Only has an effect if this value and + config_externalDisplayPeakRefreshRate and config_externalDisplayPeakHeight are non-zero.--> + <integer name="config_externalDisplayPeakWidth">0</integer> + + <!-- External display peak height for the given device. Change this value if you want + to prevent the framework from using higher resolution, even if display modes with higher + resolutions are available from hardware composer. Only has an effect if this value and + config_externalDisplayPeakRefreshRate and config_externalDisplayPeakWidth are non-zero. --> + <integer name="config_externalDisplayPeakHeight">0</integer> + + <!-- Enable synchronization of the displays refresh rates by applying the default low refresh + rate. --> + <bool name="config_refreshRateSynchronizationEnabled">false</bool> + <!-- The display uses different gamma curves for different refresh rates. It's hard for panel vendors to tune the curves to have exact same brightness for different refresh rate. So flicker could be observed at switch time. The issue is worse at the gamma lower end. diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 878e6b306571..71d696ea4554 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -172,28 +172,17 @@ <integer name="config_satellite_nb_iot_inactivity_timeout_millis">180000</integer> <java-symbol type="integer" name="config_satellite_nb_iot_inactivity_timeout_millis" /> - <!-- Telephony config for services supported by satellite providers. The format of each config - string in the array is as follows: "PLMN_1:service_1,service_2,..." - where PLMN is the satellite PLMN of a provider and service is an integer with the - following value: - 1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE} - 2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA} - 3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS} - 4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO} - 5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY} - 6 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_MMS} - Example of a config string: "10011:2,3" - - The PLMNs not configured in this array will be ignored and will not be used for satellite - scanning. --> - <string-array name="config_satellite_services_supported_by_providers" translatable="false"> - </string-array> - <java-symbol type="array" name="config_satellite_services_supported_by_providers" /> + <!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem + to identify providers that should be ignored if the carrier config + carrier_supported_satellite_services_per_provider_bundle does not support them. + --> + <string-array name="config_satellite_providers" translatable="false"></string-array> + <java-symbol type="array" name="config_satellite_providers" /> - <!-- The identifier of the satellite's eSIM profile preloaded on the device. The identifier is - composed of MCC and MNC of the satellite PLMN with the format "mccmnc". --> - <string name="config_satellite_esim_identifier" translatable="false"></string> - <java-symbol type="string" name="config_satellite_esim_identifier" /> + <!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC + of the satellite PLMN with the format "mccmnc". --> + <string name="config_satellite_sim_identifier" translatable="false"></string> + <java-symbol type="string" name="config_satellite_sim_identifier" /> <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks will not perform handover if the target transport is out of service, or VoPS not diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 83fb0986a19f..b0eee1cecc89 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4231,6 +4231,10 @@ <!-- For high refresh rate displays --> <java-symbol type="integer" name="config_defaultRefreshRate" /> <java-symbol type="integer" name="config_defaultPeakRefreshRate" /> + <java-symbol type="integer" name="config_externalDisplayPeakRefreshRate" /> + <java-symbol type="integer" name="config_externalDisplayPeakWidth" /> + <java-symbol type="integer" name="config_externalDisplayPeakHeight" /> + <java-symbol type="bool" name="config_refreshRateSynchronizationEnabled" /> <java-symbol type="integer" name="config_defaultRefreshRateInZone" /> <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" /> <java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" /> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 48dc167438da..7f3e01432dd9 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -69,6 +69,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.annotation.Nullable; +import android.compat.testing.PlatformCompatChangeRule; import android.content.Context; import android.content.Intent; import android.content.LocusId; @@ -95,16 +96,20 @@ import android.util.Pair; import android.widget.RemoteViews; import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.util.ContrastColorUtil; import junit.framework.Assert; +import libcore.junit.util.compat.CoreCompatChangeRule; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import java.util.List; @@ -116,6 +121,9 @@ public class NotificationTest { private Context mContext; + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + @Before public void setUp() { mContext = InstrumentationRegistry.getContext(); @@ -1777,6 +1785,42 @@ public class NotificationTest { assertThat(recoveredExtender.getColor()).isEqualTo(1234); } + @Test + @CoreCompatChangeRule.EnableCompatChanges({Notification.WEARABLE_EXTENDER_BACKGROUND_BLOCKED}) + public void wearableBackgroundBlockEnabled_wearableBackgroundSet_valueRemainsNull() { + Notification.WearableExtender extender = new Notification.WearableExtender(); + Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888); + extender.setBackground(bitmap); + Notification notif = + new Notification.Builder(mContext, "test id") + .setSmallIcon(1) + .setContentTitle("test_title") + .extend(extender) + .build(); + + Notification.WearableExtender result = new Notification.WearableExtender(notif); + Assert.assertNull(result.getBackground()); + } + + @Test + @CoreCompatChangeRule.DisableCompatChanges({Notification.WEARABLE_EXTENDER_BACKGROUND_BLOCKED}) + public void wearableBackgroundBlockDisabled_wearableBackgroundSet_valueKeepsBitmap() { + Notification.WearableExtender extender = new Notification.WearableExtender(); + Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888); + extender.setBackground(bitmap); + Notification notif = + new Notification.Builder(mContext, "test id") + .setSmallIcon(1) + .setContentTitle("test_title") + .extend(extender) + .build(); + + Notification.WearableExtender result = new Notification.WearableExtender(notif); + Bitmap resultBitmap = result.getBackground(); + assertNotNull(resultBitmap); + Assert.assertEquals(bitmap, resultBitmap); + } + private void assertValid(Notification.Colors c) { // Assert that all colors are populated assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID); diff --git a/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java b/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java new file mode 100644 index 000000000000..ecd75a8370ce --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/DestroyActivityItemTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.servertransaction; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.app.ActivityThread.ActivityClientRecord; +import android.app.ClientTransactionHandler; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.util.ArrayMap; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link DestroyActivityItem}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:DestroyActivityItemTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class DestroyActivityItemTest { + + @Mock + private ClientTransactionHandler mHandler; + @Mock + private PendingTransactionActions mPendingActions; + @Mock + private IBinder mActivityToken; + + // Can't mock final class. + private ActivityClientRecord mActivityClientRecord; + + private ArrayMap<IBinder, DestroyActivityItem> mActivitiesToBeDestroyed; + private DestroyActivityItem mItem; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mItem = DestroyActivityItem.obtain( + mActivityToken, false /* finished */, 123 /* configChanges */); + mActivityClientRecord = new ActivityClientRecord(); + mActivitiesToBeDestroyed = new ArrayMap<>(); + + doReturn(mActivitiesToBeDestroyed).when(mHandler).getActivitiesToBeDestroyed(); + } + + @Test + public void testPreExecute() { + mItem.preExecute(mHandler); + + assertEquals(1, mActivitiesToBeDestroyed.size()); + assertEquals(mItem, mActivitiesToBeDestroyed.get(mActivityToken)); + } + + @Test + public void testPostExecute() { + mItem.preExecute(mHandler); + mItem.postExecute(mHandler, mPendingActions); + + assertTrue(mActivitiesToBeDestroyed.isEmpty()); + } + + @Test + public void testExecute() { + mItem.execute(mHandler, mActivityClientRecord, mPendingActions); + + verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */, + eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any()); + } +} diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java index a1a2bdbe0f15..44a4d580dbc0 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java @@ -28,6 +28,7 @@ import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.inOrder; @@ -247,7 +248,7 @@ public class TransactionExecutorTests { @Test public void testDoNotLaunchDestroyedActivity() { - final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed = new ArrayMap<>(); + final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>(); when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed); // Assume launch transaction is still in queue, so there is no client record. when(mTransactionHandler.getActivityClient(any())).thenReturn(null); @@ -259,7 +260,7 @@ public class TransactionExecutorTests { DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */)); destroyTransaction.preExecute(mTransactionHandler); // The activity should be added to to-be-destroyed container. - assertEquals(1, mTransactionHandler.getActivitiesToBeDestroyed().size()); + assertEquals(1, activitiesToBeDestroyed.size()); // A previous queued launch transaction runs on main thread (execute). final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */); @@ -274,7 +275,7 @@ public class TransactionExecutorTests { // After the destroy transaction has been executed, the token should be removed. mExecutor.execute(destroyTransaction); - assertEquals(0, mTransactionHandler.getActivitiesToBeDestroyed().size()); + assertTrue(activitiesToBeDestroyed.isEmpty()); } @Test diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java index 5d922961aa8b..49ed3a83e3d4 100644 --- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java +++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java @@ -18,13 +18,20 @@ package android.graphics.drawable; import static com.google.common.truth.Truth.assertThat; +import android.app.IUriGrantsManager; +import android.content.ContentProvider; +import android.content.Intent; +import android.content.pm.ParceledListSlice; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.RecordingCanvas; import android.graphics.Region; +import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.Parcel; +import android.os.RemoteException; import android.test.AndroidTestCase; import android.util.Log; @@ -34,6 +41,7 @@ import com.android.frameworks.coretests.R; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; @@ -457,6 +465,81 @@ public class IconTest extends AndroidTestCase { assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE); } + @SmallTest + public void testLoadSafeDrawable_loadSuccessful() throws FileNotFoundException { + int uid = 12345; + String packageName = "test_pkg"; + + final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) + .getBitmap(); + final File dir = getContext().getExternalFilesDir(null); + final File file1 = new File(dir, "file1-original.png"); + bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1)); + + final Icon im1 = Icon.createWithFilePath(file1.toString()); + + TestableIUriGrantsManager ugm = + new TestableIUriGrantsManager(/* rejectCheckRequests */ false); + + Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant( + getContext(), ugm, uid, packageName); + assertThat(loadedDrawable).isNotNull(); + + assertThat(ugm.mRequests.size()).isEqualTo(1); + TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0); + assertThat(r.mCallingUid).isEqualTo(uid); + assertThat(r.mPackageName).isEqualTo(packageName); + assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION); + assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri())); + assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri())); + + final Bitmap test1 = Bitmap.createBitmap(loadedDrawable.getIntrinsicWidth(), + loadedDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + loadedDrawable.setBounds(0, 0, loadedDrawable.getIntrinsicWidth(), + loadedDrawable.getIntrinsicHeight()); + loadedDrawable.draw(new Canvas(test1)); + + bit1.compress(Bitmap.CompressFormat.PNG, 100, + new FileOutputStream(new File(dir, "bitmap1-original.png"))); + test1.compress(Bitmap.CompressFormat.PNG, 100, + new FileOutputStream(new File(dir, "bitmap1-test.png"))); + if (!equalBitmaps(bit1, test1)) { + findBitmapDifferences(bit1, test1); + fail("bitmap1 differs, check " + dir); + } + } + + @SmallTest + public void testLoadSafeDrawable_grantRejected_nullDrawable() throws FileNotFoundException { + int uid = 12345; + String packageName = "test_pkg"; + + final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape)) + .getBitmap(); + final File dir = getContext().getExternalFilesDir(null); + final File file1 = new File(dir, "file1-original.png"); + bit1.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(file1)); + + final Icon im1 = Icon.createWithFilePath(file1.toString()); + + TestableIUriGrantsManager ugm = + new TestableIUriGrantsManager(/* rejectCheckRequests */ true); + + Drawable loadedDrawable = im1.loadDrawableCheckingUriGrant( + getContext(), ugm, uid, packageName); + + assertThat(ugm.mRequests.size()).isEqualTo(1); + TestableIUriGrantsManager.CheckRequest r = ugm.mRequests.get(0); + assertThat(r.mCallingUid).isEqualTo(uid); + assertThat(r.mPackageName).isEqualTo(packageName); + assertThat(r.mMode).isEqualTo(Intent.FLAG_GRANT_READ_URI_PERMISSION); + assertThat(r.mUri).isEqualTo(ContentProvider.getUriWithoutUserId(im1.getUri())); + assertThat(r.mUserId).isEqualTo(ContentProvider.getUserIdFromUri(im1.getUri())); + + assertThat(loadedDrawable).isNull(); + } + + // ======== utils ======== static final char[] GRADIENT = " .:;+=xX$#".toCharArray(); @@ -541,4 +624,77 @@ public class IconTest extends AndroidTestCase { } L(sb.toString()); } -} + + private static class TestableIUriGrantsManager extends IUriGrantsManager.Stub { + + final ArrayList<CheckRequest> mRequests = new ArrayList<>(); + final boolean mRejectCheckRequests; + + TestableIUriGrantsManager(boolean rejectCheckRequests) { + this.mRejectCheckRequests = rejectCheckRequests; + } + + @Override + public void takePersistableUriPermission(Uri uri, int i, String s, int i1) + throws RemoteException { + + } + + @Override + public void releasePersistableUriPermission(Uri uri, int i, String s, int i1) + throws RemoteException { + + } + + @Override + public void grantUriPermissionFromOwner(IBinder iBinder, int i, String s, Uri uri, int i1, + int i2, int i3) throws RemoteException { + + } + + @Override + public ParceledListSlice getGrantedUriPermissions(String s, int i) throws RemoteException { + return null; + } + + @Override + public void clearGrantedUriPermissions(String s, int i) throws RemoteException { + + } + + @Override + public ParceledListSlice getUriPermissions(String s, boolean b, boolean b1) + throws RemoteException { + return null; + } + + @Override + public int checkGrantUriPermission_ignoreNonSystem( + int uid, String packageName, Uri uri, int mode, int userId) + throws RemoteException { + CheckRequest r = new CheckRequest(uid, packageName, uri, mode, userId); + mRequests.add(r); + if (mRejectCheckRequests) { + throw new SecurityException(); + } else { + return uid; + } + } + + static class CheckRequest { + final int mCallingUid; + final String mPackageName; + final Uri mUri; + final int mMode; + final int mUserId; + + CheckRequest(int callingUid, String packageName, Uri uri, int mode, int userId) { + this.mCallingUid = callingUid; + this.mPackageName = packageName; + this.mUri = uri; + this.mMode = mode; + this.mUserId = userId; + } + } + } +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java index 6e73b9fa35d5..4839dd27b283 100644 --- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java @@ -503,6 +503,7 @@ public class EditorInfoTest { + "prefix: hintLocales=null\n" + "prefix: supportedHandwritingGestureTypes=(none)\n" + "prefix: supportedHandwritingGesturePreviewTypes=(none)\n" + + "prefix: isStylusHandwritingEnabled=false\n" + "prefix: contentMimeTypes=null\n"); } @@ -521,6 +522,9 @@ public class EditorInfoTest { info.setSupportedHandwritingGestures(Arrays.asList(SelectGesture.class)); info.setSupportedHandwritingGesturePreviews( Stream.of(SelectGesture.class).collect(Collectors.toSet())); + if (Flags.editorinfoHandwritingEnabled()) { + info.setStylusHandwritingEnabled(true); + } info.packageName = "android.view.inputmethod"; info.autofillId = new AutofillId(123); info.fieldId = 456; @@ -544,6 +548,8 @@ public class EditorInfoTest { + "prefix2: hintLocales=[en,es,zh]\n" + "prefix2: supportedHandwritingGestureTypes=SELECT\n" + "prefix2: supportedHandwritingGesturePreviewTypes=SELECT\n" + + "prefix2: isStylusHandwritingEnabled=" + + Flags.editorinfoHandwritingEnabled() + "\n" + "prefix2: contentMimeTypes=[image/png]\n" + "prefix2: targetInputMethodUserId=10\n"); } @@ -565,6 +571,7 @@ public class EditorInfoTest { + "prefix: hintLocales=null\n" + "prefix: supportedHandwritingGestureTypes=(none)\n" + "prefix: supportedHandwritingGesturePreviewTypes=(none)\n" + + "prefix: isStylusHandwritingEnabled=false\n" + "prefix: contentMimeTypes=null\n"); } diff --git a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java index a8b40325a713..a5bbeb58bc08 100644 --- a/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java +++ b/core/tests/coretests/src/android/window/flags/WindowFlagsTest.java @@ -17,6 +17,7 @@ package android.window.flags; import static com.android.window.flags.Flags.syncWindowConfigUpdateFlag; +import static com.android.window.flags.Flags.taskFragmentSystemOrganizerFlag; import android.platform.test.annotations.Presubmit; @@ -42,4 +43,10 @@ public class WindowFlagsTest { // No crash when accessing the flag. syncWindowConfigUpdateFlag(); } + + @Test + public void testTaskFragmentSystemOrganizerFlag() { + // No crash when accessing the flag. + taskFragmentSystemOrganizerFlag(); + } } diff --git a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java index e082c25fa499..a3399070ebcd 100644 --- a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java +++ b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; @@ -36,6 +37,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -45,6 +47,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public class PackageMonitorTest { private static final String FAKE_PACKAGE_NAME = "com.android.internal.content.fakeapp"; + private static final String FAKE_EXTRA_REASON = "android.intent.extra.fakereason"; private static final int FAKE_PACKAGE_UID = 123; private static final int FAKE_USER_ID = 0; private static final int WAIT_CALLBACK_CALLED_IN_MS = 300; @@ -65,7 +68,7 @@ public class PackageMonitorTest { spyPackageMonitor.register(mMockContext, UserHandle.ALL, mMockHandler); assertThat(spyPackageMonitor.getRegisteredHandler()).isEqualTo(mMockHandler); - verify(mMockContext, times(2)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(), + verify(mMockContext, times(1)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(), eq(null), eq(mMockHandler)); assertThrows(IllegalStateException.class, @@ -136,19 +139,6 @@ public class PackageMonitorTest { } @Test - public void testPackageMonitorDoHandlePackageEventUserStop() throws Exception { - PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); - - Intent intent = new Intent(Intent.ACTION_USER_STOPPED); - intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); - spyPackageMonitor.doHandlePackageEvent(intent); - - verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); - verify(spyPackageMonitor, times(1)).onHandleUserStop(eq(intent), eq(FAKE_USER_ID)); - verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); - } - - @Test public void testPackageMonitorDoHandlePackageEventExternalApplicationAvailable() throws Exception { PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); @@ -245,6 +235,7 @@ public class PackageMonitorTest { intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null)); intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID); + intent.putExtra(Intent.EXTRA_REASON, FAKE_EXTRA_REASON); String [] packageList = new String[]{FAKE_PACKAGE_NAME}; intent.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, packageList); spyPackageMonitor.doHandlePackageEvent(intent); @@ -253,6 +244,20 @@ public class PackageMonitorTest { verify(spyPackageMonitor, times(1)) .onPackageChanged(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID), eq(packageList)); verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME)); + + ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(spyPackageMonitor, times(1)).onPackageChangedWithExtras(eq(FAKE_PACKAGE_NAME), + argumentCaptor.capture()); + + Bundle capturedExtras = argumentCaptor.getValue(); + Bundle expectedExtras = intent.getExtras(); + assertThat(capturedExtras.getInt(Intent.EXTRA_USER_HANDLE)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_USER_HANDLE)); + assertThat(capturedExtras.getInt(Intent.EXTRA_UID)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_UID)); + assertThat(capturedExtras.getInt(Intent.EXTRA_REASON)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REASON)); + verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); } @@ -272,6 +277,21 @@ public class PackageMonitorTest { verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); verify(spyPackageMonitor, times(1)) .onPackageUpdateStarted(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + + ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(spyPackageMonitor, times(1)).onPackageDisappearedWithExtras(eq(FAKE_PACKAGE_NAME), + argumentCaptor.capture()); + Bundle capturedExtras = argumentCaptor.getValue(); + Bundle expectedExtras = intent.getExtras(); + assertThat(capturedExtras.getInt(Intent.EXTRA_USER_HANDLE)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_USER_HANDLE)); + assertThat(capturedExtras.getInt(Intent.EXTRA_UID)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_UID)); + assertThat(capturedExtras.getInt(Intent.EXTRA_REPLACING)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REPLACING)); + assertThat(capturedExtras.getInt(Intent.EXTRA_REMOVED_FOR_ALL_USERS)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REMOVED_FOR_ALL_USERS)); + verify(spyPackageMonitor, times(1)) .onPackageDisappeared(eq(FAKE_PACKAGE_NAME), eq(PackageMonitor.PACKAGE_UPDATING)); verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); @@ -295,6 +315,21 @@ public class PackageMonitorTest { .onPackageRemoved(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); verify(spyPackageMonitor, times(1)) .onPackageRemovedAllUsers(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + + ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(spyPackageMonitor, times(1)).onPackageDisappearedWithExtras(eq(FAKE_PACKAGE_NAME), + argumentCaptor.capture()); + Bundle capturedExtras = argumentCaptor.getValue(); + Bundle expectedExtras = intent.getExtras(); + assertThat(capturedExtras.getInt(Intent.EXTRA_USER_HANDLE)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_USER_HANDLE)); + assertThat(capturedExtras.getInt(Intent.EXTRA_UID)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_UID)); + assertThat(capturedExtras.getInt(Intent.EXTRA_REPLACING)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REPLACING)); + assertThat(capturedExtras.getInt(Intent.EXTRA_REMOVED_FOR_ALL_USERS)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REMOVED_FOR_ALL_USERS)); + verify(spyPackageMonitor, times(1)).onPackageDisappeared(eq(FAKE_PACKAGE_NAME), eq(PackageMonitor.PACKAGE_PERMANENT_CHANGE)); verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); @@ -316,6 +351,19 @@ public class PackageMonitorTest { verify(spyPackageMonitor, times(1)) .onPackageUpdateFinished(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME)); + + ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(spyPackageMonitor, times(1)).onPackageAppearedWithExtras(eq(FAKE_PACKAGE_NAME), + argumentCaptor.capture()); + Bundle capturedExtras = argumentCaptor.getValue(); + Bundle expectedExtras = intent.getExtras(); + assertThat(capturedExtras.getInt(Intent.EXTRA_USER_HANDLE)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_USER_HANDLE)); + assertThat(capturedExtras.getInt(Intent.EXTRA_UID)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_UID)); + assertThat(capturedExtras.getInt(Intent.EXTRA_REPLACING)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REPLACING)); + verify(spyPackageMonitor, times(1)) .onPackageAppeared(eq(FAKE_PACKAGE_NAME), eq(PackageMonitor.PACKAGE_UPDATING)); verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); @@ -336,6 +384,19 @@ public class PackageMonitorTest { verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); verify(spyPackageMonitor, times(1)) .onPackageAdded(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + + ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(spyPackageMonitor, times(1)).onPackageAppearedWithExtras(eq(FAKE_PACKAGE_NAME), + argumentCaptor.capture()); + Bundle capturedExtras = argumentCaptor.getValue(); + Bundle expectedExtras = intent.getExtras(); + assertThat(capturedExtras.getInt(Intent.EXTRA_USER_HANDLE)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_USER_HANDLE)); + assertThat(capturedExtras.getInt(Intent.EXTRA_UID)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_UID)); + assertThat(capturedExtras.getInt(Intent.EXTRA_REPLACING)) + .isEqualTo(expectedExtras.getInt(Intent.EXTRA_REPLACING)); + verify(spyPackageMonitor, times(1)).onPackageAppeared(eq(FAKE_PACKAGE_NAME), eq(PackageMonitor.PACKAGE_PERMANENT_CHANGE)); verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); diff --git a/core/tests/vibrator/TEST_MAPPING b/core/tests/vibrator/TEST_MAPPING index 2f3afa6f6399..54a5ff1d675d 100644 --- a/core/tests/vibrator/TEST_MAPPING +++ b/core/tests/vibrator/TEST_MAPPING @@ -4,7 +4,6 @@ "name": "FrameworksVibratorCoreTests", "options": [ {"exclude-annotation": "androidx.test.filters.LargeTest"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index dfe5012a2b4d..dd82fed03087 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -16,6 +16,7 @@ package android.graphics; +import android.annotation.FloatRange; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; @@ -24,8 +25,10 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.Trace; import android.view.Surface; import android.view.TextureView; +import android.view.flags.Flags; import java.lang.ref.WeakReference; @@ -79,6 +82,7 @@ public class SurfaceTexture { private final Looper mCreatorLooper; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private Handler mOnFrameAvailableHandler; + private Handler mOnSetFrameRateHandler; /** * These fields are used by native code, do not access or modify. @@ -100,6 +104,21 @@ public class SurfaceTexture { } /** + * Callback interface for being notified that a producer set a frame rate + * @hide + */ + public interface OnSetFrameRateListener { + /** + * Called when the producer sets a frame rate + * @hide + */ + void onSetFrameRate(SurfaceTexture surfaceTexture, + @FloatRange(from = 0.0) float frameRate, + @Surface.FrameRateCompatibility int compatibility, + @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy); + } + + /** * Exception thrown when a SurfaceTexture couldn't be created or resized. * * @deprecated No longer thrown. {@link android.view.Surface.OutOfResourcesException} @@ -224,6 +243,48 @@ public class SurfaceTexture { } } + private static class SetFrameRateArgs { + SetFrameRateArgs(@FloatRange(from = 0.0) float frameRate, + @Surface.FrameRateCompatibility int compatibility, + @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) { + this.mFrameRate = frameRate; + this.mCompatibility = compatibility; + this.mChangeFrameRateStrategy = changeFrameRateStrategy; + } + final float mFrameRate; + final int mCompatibility; + final int mChangeFrameRateStrategy; + } + + /** + * Register a callback to be invoked when the producer sets a frame rate using + * Surface.setFrameRate. + * @hide + */ + public void setOnSetFrameRateListener(@Nullable final OnSetFrameRateListener listener, + @Nullable Handler handler) { + if (listener != null) { + Looper looper = handler != null ? handler.getLooper() : + mCreatorLooper != null ? mCreatorLooper : Looper.getMainLooper(); + mOnSetFrameRateHandler = new Handler(looper, null, true /*async*/) { + @Override + public void handleMessage(Message msg) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "onSetFrameRateHandler"); + try { + SetFrameRateArgs args = (SetFrameRateArgs) msg.obj; + listener.onSetFrameRate(SurfaceTexture.this, + args.mFrameRate, args.mCompatibility, + args.mChangeFrameRateStrategy); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + } + }; + } else { + mOnSetFrameRateHandler = null; + } + } + /** * Set the default size of the image buffers. The image producer may override the buffer size, * in which case the producer-set buffer size will be used, not the default size set by this @@ -418,6 +479,35 @@ public class SurfaceTexture { } /** + * This method is invoked from native code only. + * @hide + */ + @SuppressWarnings({"UnusedDeclaration"}) + private static void postOnSetFrameRateEventFromNative(WeakReference<SurfaceTexture> weakSelf, + @FloatRange(from = 0.0) float frameRate, + @Surface.FrameRateCompatibility int compatibility, + @Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "postOnSetFrameRateEventFromNative"); + try { + if (Flags.toolkitSetFrameRate()) { + SurfaceTexture st = weakSelf.get(); + if (st != null) { + Handler handler = st.mOnSetFrameRateHandler; + if (handler != null) { + Message msg = new Message(); + msg.obj = new SetFrameRateArgs(frameRate, compatibility, + changeFrameRateStrategy); + handler.sendMessage(msg); + } + } + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + + } + + /** * Returns {@code true} if the SurfaceTexture is single-buffered. * @hide */ diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java index 708feeb9e421..5509f000aca5 100644 --- a/graphics/java/android/graphics/drawable/Icon.java +++ b/graphics/java/android/graphics/drawable/Icon.java @@ -24,9 +24,12 @@ import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.IUriGrantsManager; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.ColorStateList; @@ -44,10 +47,13 @@ import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.RequiresPermission; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; @@ -530,6 +536,46 @@ public final class Icon implements Parcelable { return loadDrawable(context); } + /** + * Load a drawable, but in the case of URI types, it will check if the passed uid has a grant + * to load the resource. The check will be performed using the permissions of the passed uid, + * and not those of the caller. + * <p> + * This should be called for {@link Icon} objects that come from a not trusted source and may + * contain a URI. + * + * After the check, if passed, {@link #loadDrawable} will be called. If failed, this will + * return {@code null}. + * + * @see #loadDrawable + * + * @hide + */ + @Nullable + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + public Drawable loadDrawableCheckingUriGrant( + Context context, + IUriGrantsManager iugm, + int callingUid, + String packageName + ) { + if (getType() == TYPE_URI || getType() == TYPE_URI_ADAPTIVE_BITMAP) { + try { + iugm.checkGrantUriPermission_ignoreNonSystem( + callingUid, + packageName, + ContentProvider.getUriWithoutUserId(getUri()), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(getUri()) + ); + } catch (SecurityException | RemoteException e) { + Log.e(TAG, "Failed to get URI permission for: " + getUri(), e); + return null; + } + } + return loadDrawable(context); + } + /** @hide */ public static final int MIN_ASHMEM_ICON_SIZE = 128 * (1 << 10); diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 395624145cd5..96c257b304a0 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -16,6 +16,7 @@ package android.security.keystore; +import android.annotation.FlaggedApi; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -34,7 +35,10 @@ import java.security.KeyPairGenerator; import java.security.Signature; import java.security.cert.Certificate; import java.security.spec.AlgorithmParameterSpec; +import java.util.Collections; import java.util.Date; +import java.util.HashSet; +import java.util.Set; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; @@ -300,6 +304,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final Date mKeyValidityForConsumptionEnd; private final @KeyProperties.PurposeEnum int mPurposes; private final @KeyProperties.DigestEnum String[] mDigests; + private final @NonNull @KeyProperties.DigestEnum Set<String> mMgf1Digests; private final @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; private final @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; private final @KeyProperties.BlockModeEnum String[] mBlockModes; @@ -345,6 +350,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu Date keyValidityForConsumptionEnd, @KeyProperties.PurposeEnum int purposes, @KeyProperties.DigestEnum String[] digests, + @KeyProperties.DigestEnum Set<String> mgf1Digests, @KeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, @KeyProperties.SignaturePaddingEnum String[] signaturePaddings, @KeyProperties.BlockModeEnum String[] blockModes, @@ -404,6 +410,9 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd); mPurposes = purposes; mDigests = ArrayUtils.cloneIfNotEmpty(digests); + // No need to copy the input parameter because the Builder class passes in an immutable + // collection. + mMgf1Digests = mgf1Digests != null ? mgf1Digests : Collections.emptySet(); mEncryptionPaddings = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(encryptionPaddings)); mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings)); @@ -566,7 +575,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu /** * Returns the set of digest algorithms (e.g., {@code SHA-256}, {@code SHA-384} with which the - * key can be used or {@code null} if not specified. + * key can be used. * * <p>See {@link KeyProperties}.{@code DIGEST} constants. * @@ -594,6 +603,40 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Returns the set of digests that can be used by the MGF1 mask generation function + * (e.g., {@code SHA-256}, {@code SHA-384}) with the key. Useful with the {@code RSA-OAEP} + * scheme. + * If not explicitly specified during key generation, the default {@code SHA-1} digest is + * used and may be specified when using the key. + * + * <p>See {@link KeyProperties}.{@code DIGEST} constants. + * + * @throws IllegalStateException if this set has not been specified. + * + * @see #isMgf1DigestsSpecified() + */ + @NonNull + @FlaggedApi("MGF1_DIGEST_SETTER") + public @KeyProperties.DigestEnum Set<String> getMgf1Digests() { + if (mMgf1Digests.isEmpty()) { + throw new IllegalStateException("Mask generation function (MGF) not specified"); + } + return new HashSet(mMgf1Digests); + } + + /** + * Returns {@code true} if the set of digests for the MGF1 mask generation function, + * with which the key can be used, has been specified. Useful with the {@code RSA-OAEP} scheme. + * + * @see #getMgf1Digests() + */ + @NonNull + @FlaggedApi("MGF1_DIGEST_SETTER") + public boolean isMgf1DigestsSpecified() { + return !mMgf1Digests.isEmpty(); + } + + /** * Returns the set of padding schemes (e.g., {@code PKCS7Padding}, {@code OEAPPadding}, * {@code PKCS1Padding}, {@code NoPadding}) with which the key can be used when * encrypting/decrypting. Attempts to use the key with any other padding scheme will be @@ -913,6 +956,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private Date mKeyValidityForOriginationEnd; private Date mKeyValidityForConsumptionEnd; private @KeyProperties.DigestEnum String[] mDigests; + private @NonNull @KeyProperties.DigestEnum Set<String> mMgf1Digests = + Collections.emptySet(); private @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; private @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; private @KeyProperties.BlockModeEnum String[] mBlockModes; @@ -983,6 +1028,9 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu if (sourceSpec.isDigestsSpecified()) { mDigests = sourceSpec.getDigests(); } + if (sourceSpec.isMgf1DigestsSpecified()) { + mMgf1Digests = sourceSpec.getMgf1Digests(); + } mEncryptionPaddings = sourceSpec.getEncryptionPaddings(); mSignaturePaddings = sourceSpec.getSignaturePaddings(); mBlockModes = sourceSpec.getBlockModes(); @@ -1230,6 +1278,30 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Sets the set of hash functions (e.g., {@code SHA-256}, {@code SHA-384}) which could be + * used by the mask generation function MGF1 (which is used for certain operations with + * the key). Attempts to use the key with any other digest for the mask generation + * function will be rejected. + * + * <p>This can only be specified for signing/verification keys and RSA encryption/decryption + * keys used with RSA OAEP padding scheme because these operations involve a mask generation + * function (MGF1) with a digest. + * The default digest for MGF1 is {@code SHA-1}, which will be specified during key creation + * time if no digests have been explicitly provided. + * When using the key, the caller may not specify any digests that were not provided during + * key creation time. The caller may specify the default digest, {@code SHA-1}, if no + * digests were explicitly provided during key creation (but it is not necessary to do so). + * + * <p>See {@link KeyProperties}.{@code DIGEST} constants. + */ + @NonNull + @FlaggedApi("MGF1_DIGEST_SETTER") + public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) { + mMgf1Digests = Set.of(mgf1Digests); + return this; + } + + /** * Sets the set of padding schemes (e.g., {@code PKCS7Padding}, {@code OAEPPadding}, * {@code PKCS1Padding}, {@code NoPadding}) with which the key can be used when * encrypting/decrypting. Attempts to use the key with any other padding scheme will be @@ -1782,6 +1854,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mKeyValidityForConsumptionEnd, mPurposes, mDigests, + mMgf1Digests, mEncryptionPaddings, mSignaturePaddings, mBlockModes, diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 5ab21bc5f489..c1e3bab5d37c 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -16,6 +16,7 @@ package android.security.keystore; +import android.annotation.FlaggedApi; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,7 +31,10 @@ import java.security.Key; import java.security.KeyStore.ProtectionParameter; import java.security.Signature; import java.security.cert.Certificate; +import java.util.Collections; import java.util.Date; +import java.util.HashSet; +import java.util.Set; import javax.crypto.Cipher; import javax.crypto.Mac; @@ -223,6 +227,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private final @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; private final @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; private final @KeyProperties.DigestEnum String[] mDigests; + private final @NonNull @KeyProperties.DigestEnum Set<String> mMgf1Digests; private final @KeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; @@ -247,6 +252,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { @KeyProperties.EncryptionPaddingEnum String[] encryptionPaddings, @KeyProperties.SignaturePaddingEnum String[] signaturePaddings, @KeyProperties.DigestEnum String[] digests, + @KeyProperties.DigestEnum Set<String> mgf1Digests, @KeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, @@ -271,6 +277,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mSignaturePaddings = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(signaturePaddings)); mDigests = ArrayUtils.cloneIfNotEmpty(digests); + mMgf1Digests = mgf1Digests; mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes)); mRandomizedEncryptionRequired = randomizedEncryptionRequired; mUserAuthenticationRequired = userAuthenticationRequired; @@ -381,6 +388,40 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { } /** + * Returns the set of digests that can be used by the MGF1 mask generation function + * (e.g., {@code SHA-256}, {@code SHA-384}) with the key. Useful with the {@code RSA-OAEP} + * scheme. + * If not explicitly specified during key generation, the default {@code SHA-1} digest is + * used and may be specified. + * + * <p>See {@link KeyProperties}.{@code DIGEST} constants. + * + * @throws IllegalStateException if this set has not been specified. + * + * @see #isMgf1DigestsSpecified() + */ + @NonNull + @FlaggedApi("MGF1_DIGEST_SETTER") + public @KeyProperties.DigestEnum Set<String> getMgf1Digests() { + if (mMgf1Digests.isEmpty()) { + throw new IllegalStateException("Mask generation function (MGF) not specified"); + } + return new HashSet(mMgf1Digests); + } + + /** + * Returns {@code true} if the set of digests for the MGF1 mask generation function, + * with which the key can be used, has been specified. Useful with the {@code RSA-OAEP} scheme. + * + * @see #getMgf1Digests() + */ + @NonNull + @FlaggedApi("MGF1_DIGEST_SETTER") + public boolean isMgf1DigestsSpecified() { + return !mMgf1Digests.isEmpty(); + } + + /** * Gets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be used * when encrypting/decrypting. Attempts to use the key with any other block modes will be * rejected. @@ -588,6 +629,8 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private @KeyProperties.EncryptionPaddingEnum String[] mEncryptionPaddings; private @KeyProperties.SignaturePaddingEnum String[] mSignaturePaddings; private @KeyProperties.DigestEnum String[] mDigests; + private @NonNull @KeyProperties.DigestEnum Set<String> mMgf1Digests = + Collections.emptySet(); private @KeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; @@ -739,6 +782,30 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { } /** + * Sets the set of hash functions (e.g., {@code SHA-256}, {@code SHA-384}) which could be + * used by the mask generation function MGF1 (which is used for certain operations with + * the key). Attempts to use the key with any other digest for the mask generation + * function will be rejected. + * + * <p>This can only be specified for signing/verification keys and RSA encryption/decryption + * keys used with RSA OAEP padding scheme because these operations involve a mask generation + * function (MGF1) with a digest. + * The default digest for MGF1 is {@code SHA-1}, which will be specified during key import + * time if no digests have been explicitly provided. + * When using the key, the caller may not specify any digests that were not provided during + * key import time. The caller may specify the default digest, {@code SHA-1}, if no + * digests were explicitly provided during key import (but it is not necessary to do so). + * + * <p>See {@link KeyProperties}.{@code DIGEST} constants. + */ + @NonNull + @FlaggedApi("MGF1_DIGEST_SETTER") + public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) { + mMgf1Digests = Set.of(mgf1Digests); + return this; + } + + /** * Sets the set of block modes (e.g., {@code GCM}, {@code CBC}) with which the key can be * used when encrypting/decrypting. Attempts to use the key with any other block modes will * be rejected. @@ -1141,6 +1208,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mEncryptionPaddings, mSignaturePaddings, mDigests, + mMgf1Digests, mBlockModes, mRandomizedEncryptionRequired, mUserAuthenticationRequired, diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index 9356eb85bd8a..ceba04efa65d 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -23,7 +23,11 @@ import java.math.BigInteger; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.ECGenParameterSpec; import java.security.spec.RSAKeyGenParameterSpec; +import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.List; +import java.util.Set; import javax.security.auth.x500.X500Principal; @@ -91,6 +95,11 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { } else { out.writeStringArray(null); } + if (mSpec.isMgf1DigestsSpecified()) { + out.writeStringList(List.copyOf(mSpec.getMgf1Digests())); + } else { + out.writeStringList(null); + } out.writeStringArray(mSpec.getEncryptionPaddings()); out.writeStringArray(mSpec.getSignaturePaddings()); out.writeStringArray(mSpec.getBlockModes()); @@ -153,6 +162,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { final Date keyValidityForOriginationEnd = readDateOrNull(in); final Date keyValidityForConsumptionEnd = readDateOrNull(in); final String[] digests = in.createStringArray(); + final ArrayList<String> mgf1Digests = in.createStringArrayList(); final String[] encryptionPaddings = in.createStringArray(); final String[] signaturePaddings = in.createStringArray(); final String[] blockModes = in.createStringArray(); @@ -191,6 +201,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { keyValidityForConsumptionEnd, purposes, digests, + mgf1Digests != null ? Set.copyOf(mgf1Digests) : Collections.emptySet(), encryptionPaddings, signaturePaddings, blockModes, diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java index 9ac0f6d304f6..101a10e3d312 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java @@ -24,6 +24,7 @@ import android.os.StrictMode; import android.security.KeyStoreException; import android.security.KeyStoreOperation; import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; import android.security.keystore.KeyStoreCryptoOperation; import android.system.keystore2.Authorization; @@ -71,7 +72,7 @@ import javax.crypto.spec.SecretKeySpec; */ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation { private static final String TAG = "AndroidKeyStoreCipherSpiBase"; - public static final String DEFAULT_MGF1_DIGEST = "SHA-1"; + public static final String DEFAULT_MGF1_DIGEST = KeyProperties.DIGEST_SHA1; // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after // doFinal finishes. diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 1398da3f5ef2..ed4b485f3927 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -188,6 +188,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private int[] mKeymasterEncryptionPaddings; private int[] mKeymasterSignaturePaddings; private int[] mKeymasterDigests; + private int[] mKeymasterMgf1Digests; private Long mRSAPublicExponent; @@ -323,6 +324,21 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } else { mKeymasterDigests = EmptyArray.INT; } + if (spec.isMgf1DigestsSpecified()) { + // User-specified digests: Add all of them and do _not_ add the SHA-1 + // digest by default (stick to what the user provided). + Set<String> mgfDigests = spec.getMgf1Digests(); + mKeymasterMgf1Digests = new int[mgfDigests.size()]; + int offset = 0; + for (String digest : mgfDigests) { + mKeymasterMgf1Digests[offset] = KeyProperties.Digest.toKeymaster(digest); + offset++; + } + } else { + // No user-specified digests: Add the SHA-1 default. + mKeymasterMgf1Digests = new int[]{ + KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)}; + } // Check that user authentication related parameters are acceptable. This method // will throw an IllegalStateException if there are issues (e.g., secure lock screen @@ -544,6 +560,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mKeymasterEncryptionPaddings = null; mKeymasterSignaturePaddings = null; mKeymasterDigests = null; + mKeymasterMgf1Digests = null; mKeySizeBits = 0; mSpec = null; mRSAPublicExponent = null; @@ -831,24 +848,11 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato KeymasterDefs.KM_TAG_PADDING, padding )); if (padding == KeymasterDefs.KM_PAD_RSA_OAEP) { - final boolean[] hasDefaultMgf1DigestBeenAdded = {false}; - ArrayUtils.forEach(mKeymasterDigests, (digest) -> { + ArrayUtils.forEach(mKeymasterMgf1Digests, (mgf1Digest) -> { params.add(KeyStore2ParameterUtils.makeEnum( - KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, digest + KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mgf1Digest )); - hasDefaultMgf1DigestBeenAdded[0] |= - digest.equals(KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)); }); - /* Because of default MGF1 digest is SHA-1. It has to be added in Key - * characteristics. Otherwise, crypto operations will fail with Incompatible - * MGF1 digest. - */ - if (!hasDefaultMgf1DigestBeenAdded[0]) { - params.add(KeyStore2ParameterUtils.makeEnum( - KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, - KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST) - )); - } } }); ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index 273dff1cb8b3..ddbd93e458fd 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -526,25 +526,22 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { padding )); if (padding == KeymasterDefs.KM_PAD_RSA_OAEP) { - if (spec.isDigestsSpecified()) { - boolean hasDefaultMgf1DigestBeenAdded = false; - for (String digest : spec.getDigests()) { + if (spec.isMgf1DigestsSpecified()) { + for (String mgf1Digest : spec.getMgf1Digests()) { importArgs.add(KeyStore2ParameterUtils.makeEnum( KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, - KeyProperties.Digest.toKeymaster(digest) + KeyProperties.Digest.toKeymaster(mgf1Digest) )); - hasDefaultMgf1DigestBeenAdded |= digest.equals(DEFAULT_MGF1_DIGEST); } + } else { /* Because of default MGF1 digest is SHA-1. It has to be added in Key * characteristics. Otherwise, crypto operations will fail with Incompatible * MGF1 digest. */ - if (!hasDefaultMgf1DigestBeenAdded) { - importArgs.add(KeyStore2ParameterUtils.makeEnum( - KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, - KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST) - )); - } + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, + KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST) + )); } } } diff --git a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java index 2ae61ab3b38d..d4e2dbc81509 100644 --- a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java +++ b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java @@ -17,6 +17,7 @@ package android.security; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -101,6 +102,7 @@ public final class ParcelableKeyGenParameterSpecTest { assertThat(spec.getKeyValidityForOriginationEnd(), is(KEY_VALIDITY_FOR_ORIG_END)); assertThat(spec.getKeyValidityForConsumptionEnd(), is(KEY_VALIDITY_FOR_CONSUMPTION_END)); assertThat(spec.getDigests(), is(new String[] {DIGEST})); + assertThat(spec.isMgf1DigestsSpecified(), is(false)); assertThat(spec.getEncryptionPaddings(), is(new String[] {ENCRYPTION_PADDING})); assertThat(spec.getSignaturePaddings(), is(new String[] {SIGNATURE_PADDING})); assertThat(spec.getBlockModes(), is(new String[] {BLOCK_MODE})); @@ -189,4 +191,19 @@ public final class ParcelableKeyGenParameterSpecTest { ECGenParameterSpec parcelSpec = (ECGenParameterSpec) fromParcel.getAlgorithmParameterSpec(); assertEquals(parcelSpec.getName(), ecSpec.getName()); } + + @Test + public void testParcelingMgf1Digests() { + String[] mgf1Digests = + new String[] {KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA256}; + + ParcelableKeyGenParameterSpec spec = new ParcelableKeyGenParameterSpec( + new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES) + .setMgf1Digests(mgf1Digests) + .build()); + Parcel parcel = parcelForReading(spec); + KeyGenParameterSpec fromParcel = + ParcelableKeyGenParameterSpec.CREATOR.createFromParcel(parcel).getSpec(); + assertArrayEquals(fromParcel.getMgf1Digests().toArray(), mgf1Digests); + } } diff --git a/keystore/tests/src/android/security/keystore/KeyGenParameterSpecTest.java b/keystore/tests/src/android/security/keystore/KeyGenParameterSpecTest.java index ddbb1d8c097c..da5e8bf84191 100644 --- a/keystore/tests/src/android/security/keystore/KeyGenParameterSpecTest.java +++ b/keystore/tests/src/android/security/keystore/KeyGenParameterSpecTest.java @@ -16,9 +16,12 @@ package android.security.keystore; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertThrows; import android.security.ParcelableKeyGenParameterSpecTest; @@ -61,4 +64,54 @@ public final class KeyGenParameterSpecTest { assertEquals(copiedSpec.getAttestationChallenge(), null); } + + @Test + public void testMgf1DigestsNotSpecifiedByDefault() { + KeyGenParameterSpec spec = ParcelableKeyGenParameterSpecTest.configureDefaultSpec(); + assertThat(spec.isMgf1DigestsSpecified(), is(false)); + assertThrows(IllegalStateException.class, () -> { + spec.getMgf1Digests(); + }); + } + + @Test + public void testMgf1DigestsCanBeSpecified() { + String[] mgf1Digests = + new String[] {KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA256}; + KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES) + .setMgf1Digests(mgf1Digests) + .build(); + assertThat(spec.isMgf1DigestsSpecified(), is(true)); + assertThat(spec.getMgf1Digests(), containsInAnyOrder(mgf1Digests)); + + KeyGenParameterSpec copiedSpec = new KeyGenParameterSpec.Builder(spec).build(); + assertThat(copiedSpec.isMgf1DigestsSpecified(), is(true)); + assertThat(copiedSpec.getMgf1Digests(), containsInAnyOrder(mgf1Digests)); + } + + @Test + public void testMgf1DigestsAreNotModified() { + String[] mgf1Digests = + new String[] {KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA256}; + KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES) + .setMgf1Digests(mgf1Digests); + + KeyGenParameterSpec firstSpec = builder.build(); + assertArrayEquals(mgf1Digests, firstSpec.getMgf1Digests().toArray()); + + String[] otherDigests = new String[] {KeyProperties.DIGEST_SHA224}; + KeyGenParameterSpec secondSpec = builder.setMgf1Digests(otherDigests).build(); + assertThat(secondSpec.getMgf1Digests(), containsInAnyOrder(otherDigests)); + + // Now check that the first spec created hasn't changed. + assertThat(firstSpec.getMgf1Digests(), containsInAnyOrder(mgf1Digests)); + } + + @Test + public void testEmptyMgf1DigestsCanBeSet() { + KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(ALIAS, KEY_PURPOSES) + .setMgf1Digests(new String[] {}).build(); + + assertThat(spec.isMgf1DigestsSpecified(), is(false)); + } } diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 7faf3803b11a..705e38731a0c 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -435,10 +435,10 @@ <dimen name="desktop_mode_handle_menu_windowing_pill_height">52dp</dimen> <!-- The height of the handle menu's "More Actions" pill in desktop mode, but not freeform. --> - <dimen name="desktop_mode_handle_menu_more_actions_pill_height">156dp</dimen> + <dimen name="desktop_mode_handle_menu_more_actions_pill_height">104dp</dimen> <!-- The height of the handle menu's "More Actions" pill in freeform desktop windowing mode. --> - <dimen name="desktop_mode_handle_menu_more_actions_pill_freeform_height">104dp</dimen> + <dimen name="desktop_mode_handle_menu_more_actions_pill_freeform_height">52dp</dimen> <!-- The top margin of the handle menu in desktop mode. --> <dimen name="desktop_mode_handle_menu_margin_top">4dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt index cc37bd3a4589..d45e1265daac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RelativeTouchListener.kt @@ -112,7 +112,7 @@ abstract class RelativeTouchListener : View.OnTouchListener { viewPositionOnTouchDown.set(v.translationX, v.translationY) performedLongClick = false - v.handler.postDelayed({ + v.handler?.postDelayed({ if (v.isLongClickable) { performedLongClick = v.performLongClick() } @@ -122,7 +122,7 @@ abstract class RelativeTouchListener : View.OnTouchListener { MotionEvent.ACTION_MOVE -> { if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) { movedEnough = true - v.handler.removeCallbacksAndMessages(null) + v.handler?.removeCallbacksAndMessages(null) } if (movedEnough) { @@ -138,7 +138,7 @@ abstract class RelativeTouchListener : View.OnTouchListener { } else if (!performedLongClick) { v.performClick() } else { - v.handler.removeCallbacksAndMessages(null) + v.handler?.removeCallbacksAndMessages(null) } velocityTracker.clear() @@ -146,7 +146,7 @@ abstract class RelativeTouchListener : View.OnTouchListener { } MotionEvent.ACTION_CANCEL -> { - v.handler.removeCallbacksAndMessages(null) + v.handler?.removeCallbacksAndMessages(null) velocityTracker.clear() movedEnough = false } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java index 5eeb3b650074..b141bebbe8b1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java @@ -18,12 +18,16 @@ package com.android.wm.shell.compatui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.IdRes; import android.annotation.NonNull; import android.content.Context; import android.util.AttributeSet; import android.view.View; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; +import android.view.animation.PathInterpolator; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; @@ -36,14 +40,29 @@ import com.android.wm.shell.R; */ public class UserAspectRatioSettingsLayout extends LinearLayout { + private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); + + private static final Interpolator PATH_INTERPOLATOR = + new PathInterpolator(0.2f, 0f, 0f, 1f); + private static final float ALPHA_FULL_TRANSPARENT = 0f; private static final float ALPHA_FULL_OPAQUE = 1f; - private static final long VISIBILITY_ANIMATION_DURATION_MS = 50; + private static final float SCALE_START = 0.8f; + + private static final float SCALE_END = 1f; + + private static final long FADE_ANIMATION_DURATION_MS = 167; + + private static final long SCALE_ANIMATION_DURATION_MS = 300; private static final String ALPHA_PROPERTY_NAME = "alpha"; + private static final String SCALE_X_PROPERTY_NAME = "scaleX"; + + private static final String SCALE_Y_PROPERTY_NAME = "scaleY"; + private UserAspectRatioSettingsWindowManager mWindowManager; public UserAspectRatioSettingsLayout(Context context) { @@ -88,7 +107,7 @@ public class UserAspectRatioSettingsLayout extends LinearLayout { if (show) { showItem(view); } else { - view.setVisibility(visibility); + hideItem(view); } } @@ -121,16 +140,40 @@ public class UserAspectRatioSettingsLayout extends LinearLayout { } private void showItem(@NonNull View view) { - view.setVisibility(View.VISIBLE); + final AnimatorSet animatorSet = new AnimatorSet(); final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME, ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE); - fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS); - fadeIn.addListener(new AnimatorListenerAdapter() { + fadeIn.setDuration(FADE_ANIMATION_DURATION_MS); + fadeIn.setInterpolator(LINEAR_INTERPOLATOR); + final ObjectAnimator scaleY = + ObjectAnimator.ofFloat(view, SCALE_Y_PROPERTY_NAME, SCALE_START, SCALE_END); + final ObjectAnimator scaleX = + ObjectAnimator.ofFloat(view, SCALE_X_PROPERTY_NAME, SCALE_START, SCALE_END); + scaleX.setDuration(SCALE_ANIMATION_DURATION_MS); + scaleX.setInterpolator(PATH_INTERPOLATOR); + scaleY.setDuration(SCALE_ANIMATION_DURATION_MS); + scaleY.setInterpolator(PATH_INTERPOLATOR); + animatorSet.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationStart(Animator animation) { view.setVisibility(View.VISIBLE); } }); - fadeIn.start(); + animatorSet.playTogether(fadeIn, scaleY, scaleX); + animatorSet.start(); + } + + private void hideItem(@NonNull View view) { + final ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME, + ALPHA_FULL_OPAQUE, ALPHA_FULL_TRANSPARENT); + fadeOut.setDuration(FADE_ANIMATION_DURATION_MS); + fadeOut.setInterpolator(LINEAR_INTERPOLATOR); + fadeOut.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(View.GONE); + } + }); + fadeOut.start(); } } 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 f8dd208f96db..09ba4f79326e 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 @@ -37,6 +37,7 @@ import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_FRONT +import android.window.RemoteTransition import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction @@ -65,7 +66,9 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.ShellSharedConstants +import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.transition.Transitions.TransitionHandler import com.android.wm.shell.util.KtProtoLog import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.MoveToDesktopAnimator @@ -139,20 +142,22 @@ class DesktopTasksController( } /** Show all tasks, that are part of the desktop, on top of launcher */ - fun showDesktopApps(displayId: Int) { + fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition? = null) { KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: showDesktopApps") val wct = WindowContainerTransaction() - // TODO(b/278084491): pass in display id bringDesktopAppsToFront(displayId, wct) - // Execute transaction if there are pending operations - if (!wct.isEmpty) { - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - // TODO(b/268662477): add animation for the transition - transitions.startTransition(TRANSIT_NONE, wct, null /* handler */) - } else { - shellTaskOrganizer.applyTransaction(wct) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + // TODO(b/255649902): ensure remote transition is supplied once state is introduced + val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT + val handler = remoteTransition?.let { + OneShotRemoteHandler(transitions.mainExecutor, remoteTransition) + } + transitions.startTransition(transitionType, wct, handler).also { t -> + handler?.setTransition(t) } + } else { + shellTaskOrganizer.applyTransaction(wct) } } @@ -1093,11 +1098,11 @@ class DesktopTasksController( controller = null } - override fun showDesktopApps(displayId: Int) { + override fun showDesktopApps(displayId: Int, remoteTransition: RemoteTransition?) { ExecutorUtils.executeRemoteCallWithTaskPermission( controller, "showDesktopApps" - ) { c -> c.showDesktopApps(displayId) } + ) { c -> c.showDesktopApps(displayId, remoteTransition) } } override fun stashDesktopApps(displayId: Int) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index 47edfd455f5a..6bdaf1eadb8a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -17,6 +17,7 @@ package com.android.wm.shell.desktopmode; import android.app.ActivityManager.RunningTaskInfo; +import android.window.RemoteTransition; import com.android.wm.shell.desktopmode.IDesktopTaskListener; /** @@ -25,7 +26,7 @@ import com.android.wm.shell.desktopmode.IDesktopTaskListener; interface IDesktopMode { /** Show apps on the desktop on the given display */ - void showDesktopApps(int displayId); + void showDesktopApps(int displayId, in RemoteTransition remoteTransition); /** Stash apps on the desktop to allow launching another app from home screen */ void stashDesktopApps(int displayId); 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 3d825f072a6a..4ea14f473c39 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 @@ -720,6 +720,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.setDivideRatio(snapPosition); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); + wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, + false /* reparentLeafTaskIfRelaunch */); setRootForceTranslucent(false, wct); // Make sure the launch options will put tasks in the corresponding split roots @@ -767,6 +769,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.setDivideRatio(snapPosition); updateWindowBounds(mSplitLayout, wct); wct.reorder(mRootTaskInfo.token, true); + wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, + false /* reparentLeafTaskIfRelaunch */); setRootForceTranslucent(false, wct); options1 = options1 != null ? options1 : new Bundle(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java index 6ea6516a96f5..72fc8686f648 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java @@ -30,7 +30,7 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCR import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK; import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING; import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH; -import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_ICON; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN; import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS; import android.window.StartingWindowInfo; @@ -52,7 +52,8 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor final boolean processRunning = (parameter & TYPE_PARAMETER_PROCESS_RUNNING) != 0; final boolean allowTaskSnapshot = (parameter & TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT) != 0; final boolean activityCreated = (parameter & TYPE_PARAMETER_ACTIVITY_CREATED) != 0; - final boolean allowIcon = (parameter & TYPE_PARAMETER_ALLOW_ICON) != 0; + final boolean isSolidColorSplashScreen = + (parameter & TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN) != 0; final boolean legacySplashScreen = ((parameter & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN) != 0); final boolean activityDrawn = (parameter & TYPE_PARAMETER_ACTIVITY_DRAWN) != 0; @@ -66,13 +67,13 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor + "processRunning=%b, " + "allowTaskSnapshot=%b, " + "activityCreated=%b, " - + "allowIcon=%b, " + + "isSolidColorSplashScreen=%b, " + "legacySplashScreen=%b, " + "activityDrawn=%b, " + "windowless=%b, " + "topIsHome=%b", newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated, - allowIcon, legacySplashScreen, activityDrawn, windowlessSurface, + isSolidColorSplashScreen, legacySplashScreen, activityDrawn, windowlessSurface, topIsHome); if (windowlessSurface) { @@ -80,7 +81,7 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor } if (!topIsHome) { if (!processRunning || newTask || (taskSwitch && !activityCreated)) { - return getSplashscreenType(allowIcon, legacySplashScreen); + return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen); } } @@ -94,18 +95,18 @@ public class PhoneStartingWindowTypeAlgorithm implements StartingWindowTypeAlgor } } if (!activityDrawn && !topIsHome) { - return getSplashscreenType(allowIcon, legacySplashScreen); + return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen); } } return STARTING_WINDOW_TYPE_NONE; } - private static int getSplashscreenType(boolean allowIcon, boolean legacySplashScreen) { - if (allowIcon) { - return legacySplashScreen ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN + private static int getSplashscreenType(boolean solidColorSplashScreen, + boolean legacySplashScreen) { + return solidColorSplashScreen + ? STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN + : legacySplashScreen + ? STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN : STARTING_WINDOW_TYPE_SPLASH_SCREEN; - } else { - return STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN; - } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java index 0d77a2e4610c..ef8393c3b5b1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java @@ -29,12 +29,16 @@ import android.content.pm.ShortcutInfo; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; +import android.os.Handler; +import android.os.Looper; import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.ViewTreeObserver; +import com.android.internal.annotations.VisibleForTesting; + import java.util.concurrent.Executor; /** @@ -74,6 +78,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, private final TaskViewTaskController mTaskViewTaskController; private Region mObscuredTouchRegion; private Insets mCaptionInsets; + private Handler mHandler; public TaskView(Context context, TaskViewTaskController taskViewTaskController) { super(context, null, 0, 0, true /* disableBackgroundLayer */); @@ -81,6 +86,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, // TODO(b/266736992): Think about a better way to set the TaskViewBase on the // TaskViewTaskController and vice-versa mTaskViewTaskController.setTaskViewBase(this); + mHandler = Handler.getMain(); getHolder().addCallback(this); } @@ -117,14 +123,16 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { onLocationChanged(); if (taskInfo.taskDescription != null) { - setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor()); + final int bgColor = taskInfo.taskDescription.getBackgroundColor(); + runOnViewThread(() -> setResizeBackgroundColor(bgColor)); } } @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { if (taskInfo.taskDescription != null) { - setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor()); + final int bgColor = taskInfo.taskDescription.getBackgroundColor(); + runOnViewThread(() -> setResizeBackgroundColor(bgColor)); } } @@ -143,7 +151,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, @Override public void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) { - setResizeBackgroundColor(t, bgColor); + runOnViewThread(() -> setResizeBackgroundColor(t, bgColor)); } /** @@ -272,12 +280,14 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, protected void onAttachedToWindow() { super.onAttachedToWindow(); getViewTreeObserver().addOnComputeInternalInsetsListener(this); + mHandler = getHandler(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + mHandler = Handler.getMain(); } /** Returns the task info for the task in the TaskView. */ @@ -285,4 +295,24 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, public ActivityManager.RunningTaskInfo getTaskInfo() { return mTaskViewTaskController.getTaskInfo(); } + + /** + * Sets the handler, only for testing. + */ + @VisibleForTesting + void setHandler(Handler viewHandler) { + mHandler = viewHandler; + } + + /** + * Ensures that the given runnable runs on the view's thread. + */ + private void runOnViewThread(Runnable r) { + if (mHandler.getLooper().isCurrentThread()) { + r.run(); + } else { + // If this call is not from the same thread as the view, then post it + mHandler.post(r); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index de03f5826925..e0635ac2e19a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -322,6 +322,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final Runnable onAnimFinish = () -> { if (!animations.isEmpty()) return; mAnimations.remove(transition); + info.releaseAllSurfaces(); finishCallback.onTransitionFinished(null /* wct */); }; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java index f82b212c344c..a7a11dee80f2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java @@ -163,6 +163,9 @@ class HandleMenu { final ImageButton splitscreenBtn = windowingPillView.findViewById( R.id.split_screen_button); final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button); + // TODO: Remove once implemented. + floatingBtn.setVisibility(View.GONE); + final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button); fullscreenBtn.setOnClickListener(mOnClickListener); splitscreenBtn.setOnClickListener(mOnClickListener); @@ -196,6 +199,9 @@ class HandleMenu { } final Button selectBtn = moreActionsPillView.findViewById(R.id.select_button); selectBtn.setOnClickListener(mOnClickListener); + final Button screenshotBtn = moreActionsPillView.findViewById(R.id.screenshot_button); + // TODO: Remove once implemented. + screenshotBtn.setVisibility(View.GONE); } /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt new file mode 100644 index 000000000000..2cd08a4a58a6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip.apps + +import android.platform.test.annotations.Postsubmit +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.NetflixAppHelper +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.RequiresDevice +import org.junit.Assume +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test entering pip from Netflix app by interacting with the app UI + * + * To run this test: `atest WMShellFlickerTests:NetflixEnterPipTest` + * + * Actions: + * ``` + * Launch Netflix and start playing a video + * Go home to enter PiP + * ``` + * + * Notes: + * ``` + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited from [PipTransition] + * 2. Part of the test setup occurs automatically via + * [android.tools.device.flicker.legacy.runner.TransitionRunner], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition(flicker) { + override val standardAppHelper: NetflixAppHelper = NetflixAppHelper(instrumentation) + + override val defaultEnterPip: FlickerBuilder.() -> Unit = { + setup { + standardAppHelper.launchViaIntent( + wmHelper, + NetflixAppHelper.getNetflixWatchVideoIntent("70184207"), + ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME, + NetflixAppHelper.WATCH_ACTIVITY) + ) + standardAppHelper.waitForVideoPlaying() + } + } + + override val defaultTeardown: FlickerBuilder.() -> Unit = { + teardown { + standardAppHelper.exit(wmHelper) + } + } + + override val thisTransition: FlickerBuilder.() -> Unit = { + transitions { + tapl.goHomeFromImmersiveFullscreenApp() + } + } + + @Postsubmit + @Test + override fun pipOverlayLayerAppearThenDisappear() { + // Netflix uses source rect hint, so PiP overlay is never present + } + + @Postsubmit + @Test + override fun focusChanges() { + // in gestural nav the focus goes to different activity on swipe up with auto enter PiP + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) + super.focusChanges() + } + + companion object { + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring repetitions, screen + * orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams() = + LegacyFlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0), + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) + ) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt index b444bad8322e..e37d806c7a14 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt @@ -19,15 +19,10 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit -import org.junit.Rule import org.junit.Test open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt index e2ab989af027..2a50912e0a5c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt @@ -19,15 +19,10 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit -import org.junit.Rule import org.junit.Test open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt index 22b81028dd1d..d5da1a8b558c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider -import org.junit.Rule import org.junit.Test open class DismissSplitScreenByDividerGesturalNavLandscape : DismissSplitScreenByDivider(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt index 3fb014f9aac9..7fdcb9be62ee 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider -import org.junit.Rule import org.junit.Test open class DismissSplitScreenByDividerGesturalNavPortrait : DismissSplitScreenByDivider(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt index ea1f9426bb81..308e954b86c1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome -import org.junit.Rule import org.junit.Test open class DismissSplitScreenByGoHomeGesturalNavLandscape : DismissSplitScreenByGoHome(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt index 8f23a790081d..39e75bd25a71 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome -import org.junit.Rule import org.junit.Test open class DismissSplitScreenByGoHomeGesturalNavPortrait : DismissSplitScreenByGoHome(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt index b0f39e592744..e18da17175c0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt @@ -19,15 +19,10 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize -import org.junit.Rule import org.junit.Test open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt index 6ce874641c6f..00d60e756ffa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt @@ -19,15 +19,10 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize -import org.junit.Rule import org.junit.Test open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt index 9f74edf0a79a..d7efbc8c0fd4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps -import org.junit.Rule import org.junit.Test open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape : EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt index 1e4055e2a50b..4eece3f62d10 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps -import org.junit.Rule import org.junit.Test open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait : EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt index c3b8132f4a50..d96b056d8753 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification -import org.junit.Rule import org.junit.Test open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape : EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt index 7756d048efbb..809b690e0861 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification -import org.junit.Rule import org.junit.Test open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait : EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt index c72aa5aaeca6..bbdf2d728494 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut -import org.junit.Rule import org.junit.Test open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape : EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt index cc88f27b9045..5c29fd8fe57e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut -import org.junit.Rule import org.junit.Test open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait : EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt index 87b38b133357..a7398ebf56e8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar -import org.junit.Rule import org.junit.Test open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape : EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt index ca347ed25f08..eae88ad4ad09 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar -import org.junit.Rule import org.junit.Test open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait : EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt index 65978192e8e1..7e8ee04a28fa 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview -import org.junit.Rule import org.junit.Test open class EnterSplitScreenFromOverviewGesturalNavLandscape : EnterSplitScreenFromOverview(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt index 6df31fc9419b..9295c330b879 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview -import org.junit.Rule import org.junit.Test open class EnterSplitScreenFromOverviewGesturalNavPortrait : EnterSplitScreenFromOverview(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt index 02596c5f6202..4b59e9fbd866 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider -import org.junit.Rule import org.junit.Test open class SwitchAppByDoubleTapDividerGesturalNavLandscape : SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt index 9d579f60eaaa..5ff36d4aabbb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider -import org.junit.Rule import org.junit.Test open class SwitchAppByDoubleTapDividerGesturalNavPortrait : SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt index da853420a705..c0cb7219437b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp -import org.junit.Rule import org.junit.Test open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape : SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt index 1ae2c9eb8a1a..8c140884aa50 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp -import org.junit.Rule import org.junit.Test open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait : SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt index b1b562577acc..7b6614b81c11 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome -import org.junit.Rule import org.junit.Test open class SwitchBackToSplitFromHomeGesturalNavLandscape : SwitchBackToSplitFromHome(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt index 08c437eda71c..5df5be9daa8b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome -import org.junit.Rule import org.junit.Test open class SwitchBackToSplitFromHomeGesturalNavPortrait : SwitchBackToSplitFromHome(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt index efbf86d028ee..9d63003bf2a1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent -import org.junit.Rule import org.junit.Test open class SwitchBackToSplitFromRecentGesturalNavLandscape : SwitchBackToSplitFromRecent(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt index f7072fae2e7d..9fa04b208ad1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent -import org.junit.Rule import org.junit.Test open class SwitchBackToSplitFromRecentGesturalNavPortrait : SwitchBackToSplitFromRecent(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt index d80d1120017e..9386aa2b2cf0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs -import org.junit.Rule import org.junit.Test open class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt index 30ec37a41dde..5ef21672bfe0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt @@ -19,16 +19,11 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs -import org.junit.Rule import org.junit.Test open class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt index 1e086d2f5a9b..9caab9b5182a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt @@ -18,18 +18,13 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.BlockJUnit4ClassRunner @RunWith(BlockJUnit4ClassRunner::class) open class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt index 932f89217277..bf484e5cef98 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt @@ -18,18 +18,13 @@ package com.android.wm.shell.flicker.service.splitscreen.platinum import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit -import android.tools.device.flicker.rules.FlickerServiceRule import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.BlockJUnit4ClassRunner @RunWith(BlockJUnit4ClassRunner::class) open class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() { - @get:Rule - val flickerServiceRule = FlickerServiceRule(enabled = true, failTestOnFaasFailure = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index 2d9304705738..02c9d306f4bf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -35,7 +35,7 @@ import android.window.TransitionInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.wm.shell.TransitionInfoBuilder; +import com.android.wm.shell.transition.TransitionInfoBuilder; import org.junit.Before; import org.junit.Test; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index 270dbc49835f..83d9f654376c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -39,7 +39,7 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.wm.shell.TransitionInfoBuilder; +import com.android.wm.shell.transition.TransitionInfoBuilder; import org.junit.Before; import org.junit.Test; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java index 0dc16f44340f..cb29a21e2f5b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/BubbleOverflowTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell; +package com.android.wm.shell.bubbles; import static com.google.common.truth.Truth.assertThat; @@ -27,10 +27,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; -import com.android.wm.shell.bubbles.BubbleController; -import com.android.wm.shell.bubbles.BubbleOverflow; -import com.android.wm.shell.bubbles.BubbleStackView; -import com.android.wm.shell.bubbles.TestableBubblePositioner; +import com.android.wm.shell.ShellTestCase; import org.junit.Before; import org.junit.Test; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java index 9655f97cb7cc..f8eb50b978a5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java @@ -38,7 +38,7 @@ import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; -import com.android.wm.shell.TransitionInfoBuilder; +import com.android.wm.shell.transition.TransitionInfoBuilder; import org.junit.Before; import org.junit.Test; 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 c6cccc059e89..dea161786da2 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 @@ -28,10 +28,10 @@ import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager import android.view.WindowManager.TRANSIT_CHANGE -import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.DisplayAreaInfo +import android.window.RemoteTransition import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER @@ -43,6 +43,7 @@ import com.android.wm.shell.MockToken import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.transition.TestRemoteTransition import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.DisplayController @@ -57,8 +58,10 @@ import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS +import com.android.wm.shell.transition.Transitions.TransitionHandler import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage @@ -69,6 +72,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.eq +import org.mockito.ArgumentMatchers.isA import org.mockito.ArgumentMatchers.isNull import org.mockito.Mock import org.mockito.Mockito @@ -174,9 +178,10 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskHidden(task1) markTaskHidden(task2) - controller.showDesktopApps(DEFAULT_DISPLAY) + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - val wct = getLatestWct(expectTransition = TRANSIT_NONE) + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) assertThat(wct.hierarchyOps).hasSize(3) // Expect order to be from bottom: home, task1, task2 wct.assertReorderAt(index = 0, homeTask) @@ -192,9 +197,10 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskVisible(task1) markTaskVisible(task2) - controller.showDesktopApps(DEFAULT_DISPLAY) + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - val wct = getLatestWct(expectTransition = TRANSIT_NONE) + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) assertThat(wct.hierarchyOps).hasSize(3) // Expect order to be from bottom: home, task1, task2 wct.assertReorderAt(index = 0, homeTask) @@ -210,9 +216,10 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskHidden(task1) markTaskVisible(task2) - controller.showDesktopApps(DEFAULT_DISPLAY) + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - val wct = getLatestWct(expectTransition = TRANSIT_NONE) + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) assertThat(wct.hierarchyOps).hasSize(3) // Expect order to be from bottom: home, task1, task2 wct.assertReorderAt(index = 0, homeTask) @@ -224,9 +231,10 @@ class DesktopTasksControllerTest : ShellTestCase() { fun showDesktopApps_noActiveTasks_reorderHomeToTop() { val homeTask = setUpHomeTask() - controller.showDesktopApps(DEFAULT_DISPLAY) + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - val wct = getLatestWct(expectTransition = TRANSIT_NONE) + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) assertThat(wct.hierarchyOps).hasSize(1) wct.assertReorderAt(index = 0, homeTask) } @@ -240,9 +248,10 @@ class DesktopTasksControllerTest : ShellTestCase() { markTaskHidden(taskDefaultDisplay) markTaskHidden(taskSecondDisplay) - controller.showDesktopApps(DEFAULT_DISPLAY) + controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - val wct = getLatestWct(expectTransition = TRANSIT_NONE) + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) assertThat(wct.hierarchyOps).hasSize(2) // Expect order to be from bottom: home, task wct.assertReorderAt(index = 0, homeTaskDefaultDisplay) @@ -373,7 +382,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFreeformTask() task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN controller.moveToFullscreen(task) - val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + val wct = getLatestWct(type = TRANSIT_CHANGE) assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_UNDEFINED) } @@ -383,7 +392,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFreeformTask() task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM controller.moveToFullscreen(task) - val wct = getLatestWct(expectTransition = TRANSIT_CHANGE) + val wct = getLatestWct(type = TRANSIT_CHANGE) assertThat(wct.changes[task.token.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FULLSCREEN) } @@ -401,7 +410,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToFullscreen(taskDefaultDisplay) - with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + with(getLatestWct(type = TRANSIT_CHANGE)) { assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder()) assertThat(changes.keys).doesNotContain(taskSecondDisplay.token.asBinder()) } @@ -414,7 +423,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveTaskToFront(task1) - val wct = getLatestWct(expectTransition = TRANSIT_TO_FRONT) + val wct = getLatestWct(type = TRANSIT_TO_FRONT) assertThat(wct.hierarchyOps).hasSize(1) wct.assertReorderAt(index = 0, task1) } @@ -439,7 +448,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFreeformTask(displayId = DEFAULT_DISPLAY) controller.moveToNextDisplay(task.taskId) - with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + with(getLatestWct(type = TRANSIT_CHANGE)) { assertThat(hierarchyOps).hasSize(1) assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder()) assertThat(hierarchyOps[0].isReparent).isTrue() @@ -461,7 +470,7 @@ class DesktopTasksControllerTest : ShellTestCase() { val task = setUpFreeformTask(displayId = SECOND_DISPLAY) controller.moveToNextDisplay(task.taskId) - with(getLatestWct(expectTransition = TRANSIT_CHANGE)) { + with(getLatestWct(type = TRANSIT_CHANGE)) { assertThat(hierarchyOps).hasSize(1) assertThat(hierarchyOps[0].container).isEqualTo(task.token.asBinder()) assertThat(hierarchyOps[0].isReparent).isTrue() @@ -747,11 +756,16 @@ class DesktopTasksControllerTest : ShellTestCase() { } private fun getLatestWct( - @WindowManager.TransitionType expectTransition: Int = TRANSIT_OPEN + @WindowManager.TransitionType type: Int = TRANSIT_OPEN, + handlerClass: Class<out TransitionHandler>? = null ): WindowContainerTransaction { val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) if (ENABLE_SHELL_TRANSITIONS) { - verify(transitions).startTransition(eq(expectTransition), arg.capture(), isNull()) + if (handlerClass == null) { + verify(transitions).startTransition(eq(type), arg.capture(), isNull()) + } else { + verify(transitions).startTransition(eq(type), arg.capture(), isA(handlerClass)) + } } else { verify(shellTaskOrganizer).applyTransaction(arg.capture()) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java index 69f664a3a89d..499e339bc682 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java @@ -37,8 +37,8 @@ import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; -import com.android.wm.shell.TransitionInfoBuilder; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.TransitionInfoBuilder; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 5efd9ad97a3e..ebc284b1b77e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -47,11 +47,8 @@ import static org.mockito.Mockito.spy; import android.annotation.NonNull; import android.app.ActivityManager; import android.os.IBinder; -import android.os.RemoteException; import android.view.SurfaceControl; import android.view.SurfaceSession; -import android.window.IRemoteTransition; -import android.window.IRemoteTransitionFinishedCallback; import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -66,7 +63,6 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; -import com.android.wm.shell.TransitionInfoBuilder; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; @@ -77,6 +73,8 @@ import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.common.split.SplitLayout; import com.android.wm.shell.transition.DefaultMixedHandler; +import com.android.wm.shell.transition.TestRemoteTransition; +import com.android.wm.shell.transition.TransitionInfoBuilder; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -205,7 +203,7 @@ public class SplitTransitionTests extends ShellTestCase { // Make sure split-screen is now visible assertTrue(mStageCoordinator.isSplitScreenVisible()); - assertTrue(testRemote.mCalled); + assertTrue(testRemote.isCalled()); } @Test @@ -468,24 +466,4 @@ public class SplitTransitionTests extends ShellTestCase { return out; } - class TestRemoteTransition extends IRemoteTransition.Stub { - boolean mCalled = false; - final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction(); - - @Override - public void startAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction startTransaction, - IRemoteTransitionFinishedCallback finishCallback) - throws RemoteException { - mCalled = true; - finishCallback.onTransitionFinished(mRemoteFinishWCT, null /* sct */); - } - - @Override - public void mergeAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { - } - } - } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java index 0088051928fb..4afb29ecd98c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java @@ -43,6 +43,8 @@ import android.content.Context; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; +import android.os.Handler; +import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.SurfaceControl; @@ -88,6 +90,10 @@ public class TaskViewTest extends ShellTestCase { SyncTransactionQueue mSyncQueue; @Mock Transitions mTransitions; + @Mock + Looper mViewLooper; + @Mock + Handler mViewHandler; SurfaceSession mSession; SurfaceControl mLeash; @@ -105,6 +111,8 @@ public class TaskViewTest extends ShellTestCase { .build(); mContext = getContext(); + doReturn(true).when(mViewLooper).isCurrentThread(); + doReturn(mViewLooper).when(mViewHandler).getLooper(); mTaskInfo = new ActivityManager.RunningTaskInfo(); mTaskInfo.token = mToken; @@ -132,6 +140,7 @@ public class TaskViewTest extends ShellTestCase { mTaskViewTaskController = spy(new TaskViewTaskController(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue)); mTaskView = new TaskView(mContext, mTaskViewTaskController); + mTaskView.setHandler(mViewHandler); mTaskView.setListener(mExecutor, mViewListener); } @@ -646,4 +655,17 @@ public class TaskViewTest extends ShellTestCase { assertThat(mTaskViewTaskController.getTaskInfo()).isNull(); } + + @Test + public void testOnTaskInfoChangedOnSameUiThread() { + mTaskViewTaskController.onTaskInfoChanged(mTaskInfo); + verify(mViewHandler, never()).post(any()); + } + + @Test + public void testOnTaskInfoChangedOnDifferentUiThread() { + doReturn(false).when(mViewLooper).isCurrentThread(); + mTaskViewTaskController.onTaskInfoChanged(mTaskInfo); + verify(mViewHandler).post(any()); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index a57a7bf4c659..d5986780d5d8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -94,7 +94,6 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; -import com.android.wm.shell.TransitionInfoBuilder; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java new file mode 100644 index 000000000000..39ab23877c68 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.transition; + +import android.os.IBinder; +import android.os.RemoteException; +import android.view.SurfaceControl; +import android.window.IRemoteTransition; +import android.window.IRemoteTransitionFinishedCallback; +import android.window.TransitionInfo; +import android.window.WindowContainerTransaction; + +/** + * {@link IRemoteTransition} for testing purposes. + * Stores info about + * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction, + * IRemoteTransitionFinishedCallback)} being called. + */ +public class TestRemoteTransition extends IRemoteTransition.Stub { + private boolean mCalled = false; + final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction(); + + @Override + public void startAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction startTransaction, + IRemoteTransitionFinishedCallback finishCallback) + throws RemoteException { + mCalled = true; + finishCallback.onTransitionFinished(mRemoteFinishWCT, null /* sct */); + } + + @Override + public void mergeAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + } + + /** + * Check whether this remote transition + * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction, + * IRemoteTransitionFinishedCallback)} is called + */ + public boolean isCalled() { + return mCalled; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java index a658375ca38a..834385832e4a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TransitionInfoBuilder.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TransitionInfoBuilder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell; +package com.android.wm.shell.transition; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index e1dd145edcfa..ff1eedb8eacb 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -143,6 +143,7 @@ cc_defaults { "libcrypto", "libsync", "libui", + "aconfig_text_flags_c_lib", ], static_libs: [ "libEGL_blobCache", @@ -712,11 +713,13 @@ cc_test { ], static_libs: [ + "libflagtest", "libgmock", "libhwui_static", ], shared_libs: [ "libmemunreachable", + "server_configurable_flags", ], srcs: [ "tests/unit/main.cpp", @@ -756,6 +759,7 @@ cc_test { "tests/unit/TestUtilsTests.cpp", "tests/unit/ThreadBaseTests.cpp", "tests/unit/TypefaceTests.cpp", + "tests/unit/UnderlineTest.cpp", "tests/unit/VectorDrawableTests.cpp", "tests/unit/WebViewFunctorManagerTests.cpp", ], diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h new file mode 100644 index 000000000000..ffb329d9f8e6 --- /dev/null +++ b/libs/hwui/FeatureFlags.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef ANDROID_HWUI_FEATURE_FLAGS_H +#define ANDROID_HWUI_FEATURE_FLAGS_H + +#ifdef __ANDROID__ +#include <com_android_text_flags.h> +#endif // __ANDROID__ + +namespace android { + +namespace text_feature { + +inline bool fix_double_underline() { +#ifdef __ANDROID__ + return com_android_text_flags_fix_double_underline(); +#else + return true; +#endif // __ANDROID__ +} + +} // namespace text_feature + +} // namespace android + +#endif // ANDROID_HWUI_FEATURE_FLAGS_H diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h index 764d1efcc8f4..69fda34afc78 100644 --- a/libs/hwui/Mesh.h +++ b/libs/hwui/Mesh.h @@ -166,11 +166,12 @@ public: #endif mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset, ib, mIndexCount, mIndexOffset, mBuilder->fUniforms, - mBounds) + SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds) .mesh; } else { mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset, - mBuilder->fUniforms, mBounds) + mBuilder->fUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(), + mBounds) .mesh; } mIsDirty = false; diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 71f47e92e055..ff0d8d74831c 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -539,7 +539,7 @@ struct DrawSkMesh final : Op { if (!cpuMesh.indexBuffer()) { gpuMesh = SkMesh::Make(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(), cpuMesh.vertexOffset(), cpuMesh.refUniforms(), - cpuMesh.bounds()) + SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds()) .mesh; } else { sk_sp<SkMesh::IndexBuffer> ib = @@ -547,7 +547,8 @@ struct DrawSkMesh final : Op { gpuMesh = SkMesh::MakeIndexed(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(), cpuMesh.vertexOffset(), ib, cpuMesh.indexCount(), cpuMesh.indexOffset(), - cpuMesh.refUniforms(), cpuMesh.bounds()) + cpuMesh.refUniforms(), + SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds()) .mesh; } diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 8394c3cd4175..31fc929dfcdf 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -47,6 +47,7 @@ #include <utility> #include "CanvasProperty.h" +#include "FeatureFlags.h" #include "Mesh.h" #include "NinePatchUtils.h" #include "VectorDrawable.h" @@ -795,7 +796,9 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai sk_sp<SkTextBlob> textBlob(builder.make()); applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); }); - drawTextDecorations(x, y, totalAdvance, paintCopy); + if (!text_feature::fix_double_underline()) { + drawTextDecorations(x, y, totalAdvance, paintCopy); + } } void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 2351797ac787..80b6c0385fca 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -16,17 +16,18 @@ #include "Canvas.h" +#include <SkFontMetrics.h> +#include <SkRRect.h> + +#include "FeatureFlags.h" #include "MinikinUtils.h" #include "Paint.h" #include "Properties.h" #include "RenderNode.h" #include "Typeface.h" -#include "pipeline/skia/SkiaRecordingCanvas.h" - +#include "hwui/DrawTextFunctor.h" #include "hwui/PaintFilter.h" - -#include <SkFontMetrics.h> -#include <SkRRect.h> +#include "pipeline/skia/SkiaRecordingCanvas.h" namespace android { @@ -34,13 +35,6 @@ Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::Rende return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height); } -static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness, - const Paint& paint, Canvas* canvas) { - const SkScalar strokeWidth = fmax(thickness, 1.0f); - const SkScalar bottom = top + strokeWidth; - canvas->drawRect(left, top, right, bottom, paint); -} - void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) { // paint has already been filtered by our caller, so we can ignore any filter const bool strikeThru = paint.isStrikeThru(); @@ -72,73 +66,6 @@ void Canvas::drawTextDecorations(float x, float y, float length, const Paint& pa } } -static void simplifyPaint(int color, Paint* paint) { - paint->setColor(color); - paint->setShader(nullptr); - paint->setColorFilter(nullptr); - paint->setLooper(nullptr); - paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize()); - paint->setStrokeJoin(SkPaint::kRound_Join); - paint->setLooper(nullptr); -} - -class DrawTextFunctor { -public: - DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x, - float y, float totalAdvance) - : layout(layout) - , canvas(canvas) - , paint(paint) - , x(x) - , y(y) - , totalAdvance(totalAdvance) {} - - void operator()(size_t start, size_t end) { - auto glyphFunc = [&](uint16_t* text, float* positions) { - for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) { - text[textIndex++] = layout.getGlyphId(i); - positions[posIndex++] = x + layout.getX(i); - positions[posIndex++] = y + layout.getY(i); - } - }; - - size_t glyphCount = end - start; - - if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) { - // high contrast draw path - int color = paint.getColor(); - int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color); - bool darken = channelSum < (128 * 3); - - // outline - gDrawTextBlobMode = DrawTextBlobMode::HctOutline; - Paint outlinePaint(paint); - simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint); - outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style); - canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance); - - // inner - gDrawTextBlobMode = DrawTextBlobMode::HctInner; - Paint innerPaint(paint); - simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint); - innerPaint.setStyle(SkPaint::kFill_Style); - canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance); - gDrawTextBlobMode = DrawTextBlobMode::Normal; - } else { - // standard draw path - canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance); - } - } - -private: - const minikin::Layout& layout; - Canvas* canvas; - const Paint& paint; - float x; - float y; - float totalAdvance; -}; - void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions, int glyphCount, const Paint& paint) { // Minikin modify skFont for auto-fakebold/auto-fakeitalic. @@ -182,6 +109,31 @@ void Canvas::drawText(const uint16_t* text, int textSize, int start, int count, DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance()); MinikinUtils::forFontRun(layout, &paint, f); + + if (text_feature::fix_double_underline()) { + Paint copied(paint); + PaintFilter* filter = getPaintFilter(); + if (filter != nullptr) { + filter->filterFullPaint(&copied); + } + const bool isUnderline = copied.isUnderline(); + const bool isStrikeThru = copied.isStrikeThru(); + if (isUnderline || isStrikeThru) { + const SkScalar left = x; + const SkScalar right = x + layout.getAdvance(); + if (isUnderline) { + const SkScalar top = y + f.getUnderlinePosition(); + drawStroke(left, right, top, f.getUnderlineThickness(), copied, this); + } + if (isStrikeThru) { + float textSize = paint.getSkFont().getSize(); + const float position = textSize * Paint::kStdStrikeThru_Top; + const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; + const SkScalar top = y + position; + drawStroke(left, right, top, thickness, copied, this); + } + } + } } void Canvas::drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight, diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 44ee31d34d23..9ec023b2c36f 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -285,7 +285,7 @@ protected: * totalAdvance: used to define width of text decorations (underlines, strikethroughs). */ virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x, - float y,float totalAdvance) = 0; + float y, float totalAdvance) = 0; virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, const Paint& paint, const SkPath& path, size_t start, size_t end) = 0; diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h new file mode 100644 index 000000000000..2e6e97634aec --- /dev/null +++ b/libs/hwui/hwui/DrawTextFunctor.h @@ -0,0 +1,141 @@ +/* + * 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. + */ + +#include <SkFontMetrics.h> +#include <SkRRect.h> + +#include "Canvas.h" +#include "FeatureFlags.h" +#include "MinikinUtils.h" +#include "Paint.h" +#include "Properties.h" +#include "RenderNode.h" +#include "Typeface.h" +#include "hwui/PaintFilter.h" +#include "pipeline/skia/SkiaRecordingCanvas.h" + +namespace android { + +static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness, + const Paint& paint, Canvas* canvas) { + const SkScalar strokeWidth = fmax(thickness, 1.0f); + const SkScalar bottom = top + strokeWidth; + canvas->drawRect(left, top, right, bottom, paint); +} + +static void simplifyPaint(int color, Paint* paint) { + paint->setColor(color); + paint->setShader(nullptr); + paint->setColorFilter(nullptr); + paint->setLooper(nullptr); + paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize()); + paint->setStrokeJoin(SkPaint::kRound_Join); + paint->setLooper(nullptr); +} + +class DrawTextFunctor { +public: + DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x, + float y, float totalAdvance) + : layout(layout) + , canvas(canvas) + , paint(paint) + , x(x) + , y(y) + , totalAdvance(totalAdvance) + , underlinePosition(0) + , underlineThickness(0) {} + + void operator()(size_t start, size_t end) { + auto glyphFunc = [&](uint16_t* text, float* positions) { + for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) { + text[textIndex++] = layout.getGlyphId(i); + positions[posIndex++] = x + layout.getX(i); + positions[posIndex++] = y + layout.getY(i); + } + }; + + size_t glyphCount = end - start; + + if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) { + // high contrast draw path + int color = paint.getColor(); + int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color); + bool darken = channelSum < (128 * 3); + + // outline + gDrawTextBlobMode = DrawTextBlobMode::HctOutline; + Paint outlinePaint(paint); + simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint); + outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style); + canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance); + + // inner + gDrawTextBlobMode = DrawTextBlobMode::HctInner; + Paint innerPaint(paint); + simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint); + innerPaint.setStyle(SkPaint::kFill_Style); + canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance); + gDrawTextBlobMode = DrawTextBlobMode::Normal; + } else { + // standard draw path + canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance); + } + + if (text_feature::fix_double_underline()) { + // Extract underline position and thickness. + if (paint.isUnderline()) { + SkFontMetrics metrics; + paint.getSkFont().getMetrics(&metrics); + const float textSize = paint.getSkFont().getSize(); + SkScalar position; + if (!metrics.hasUnderlinePosition(&position)) { + position = textSize * Paint::kStdUnderline_Top; + } + SkScalar thickness; + if (!metrics.hasUnderlineThickness(&thickness)) { + thickness = textSize * Paint::kStdUnderline_Thickness; + } + + // If multiple fonts are used, use the most bottom position and most thick stroke + // width as the underline position. This follows the CSS standard: + // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property + // <quote> + // The exact position and thickness of line decorations is UA-defined in this level. + // However, for underlines and overlines the UA must use a single thickness and + // position on each line for the decorations deriving from a single decorating box. + // </quote> + underlinePosition = std::max(underlinePosition, position); + underlineThickness = std::max(underlineThickness, thickness); + } + } + } + + float getUnderlinePosition() const { return underlinePosition; } + float getUnderlineThickness() const { return underlineThickness; } + +private: + const minikin::Layout& layout; + Canvas* canvas; + const Paint& paint; + float x; + float y; + float totalAdvance; + float underlinePosition; + float underlineThickness; +}; + +} // namespace android diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp index cec0ee7ee247..0fffee744be0 100644 --- a/libs/hwui/jni/Gainmap.cpp +++ b/libs/hwui/jni/Gainmap.cpp @@ -208,8 +208,6 @@ static void Gainmap_writeToParcel(JNIEnv* env, jobject, jlong nativeObject, jobj p.writeFloat(info.fDisplayRatioHdr); // base image type p.writeInt32(static_cast<int32_t>(info.fBaseImageType)); - // type - p.writeInt32(static_cast<int32_t>(info.fType)); #else doThrowRE(env, "Cannot use parcels outside of Android!"); #endif @@ -232,7 +230,6 @@ static void Gainmap_readFromParcel(JNIEnv* env, jobject, jlong nativeObject, job info.fDisplayRatioSdr = p.readFloat(); info.fDisplayRatioHdr = p.readFloat(); info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32()); - info.fType = static_cast<SkGainmapInfo::Type>(p.readInt32()); fromJava(nativeObject)->info = info; #else diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp index d1ebe6d9f576..1c3399a6650d 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.cpp +++ b/libs/hwui/renderthread/HintSessionWrapper.cpp @@ -72,6 +72,7 @@ void HintSessionWrapper::destroy() { mSessionValid = true; mHintSession = nullptr; } + mResetsSinceLastReport = 0; } bool HintSessionWrapper::init() { @@ -109,12 +110,13 @@ bool HintSessionWrapper::init() { tids.push_back(mUiThreadId); tids.push_back(mRenderThreadId); - // Use a placeholder target value to initialize, - // this will always be replaced elsewhere before it gets used - int64_t defaultTargetDurationNanos = 16666667; + // Use the cached target value if there is one, otherwise use a default. This is to ensure + // the cached target and target in PowerHAL are consistent, and that it updates correctly + // whenever there is a change. + int64_t targetDurationNanos = + mLastTargetWorkDuration == 0 ? kDefaultTargetDuration : mLastTargetWorkDuration; mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] { - return mBinding->createSession(manager, tids.data(), tids.size(), - defaultTargetDurationNanos); + return mBinding->createSession(manager, tids.data(), tids.size(), targetDurationNanos); }); return false; } diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h index 36e91ea33c75..41891cd80a42 100644 --- a/libs/hwui/renderthread/HintSessionWrapper.h +++ b/libs/hwui/renderthread/HintSessionWrapper.h @@ -65,6 +65,7 @@ private: static constexpr nsecs_t kResetHintTimeout = 100_ms; static constexpr int64_t kSanityCheckLowerBound = 100_us; static constexpr int64_t kSanityCheckUpperBound = 10_s; + static constexpr int64_t kDefaultTargetDuration = 16666667; // Allows easier stub when testing class HintSessionBinding { diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 94ed06c806e5..f76ea063842f 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -17,6 +17,7 @@ #include "RenderThread.h" #include <GrContextOptions.h> +#include <include/gpu/ganesh/gl/GrGLDirectContext.h> #include <android-base/properties.h> #include <dlfcn.h> #include <gl/GrGLInterface.h> @@ -286,7 +287,7 @@ void RenderThread::requireGlContext() { auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION)); auto size = glesVersion ? strlen(glesVersion) : -1; cacheManager().configureContext(&options, glesVersion, size); - sk_sp<GrDirectContext> grContext(GrDirectContext::MakeGL(std::move(glInterface), options)); + sk_sp<GrDirectContext> grContext(GrDirectContexts::MakeGL(std::move(glInterface), options)); LOG_ALWAYS_FATAL_IF(!grContext.get()); setGrContext(grContext); } diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp new file mode 100644 index 000000000000..db2be20936fb --- /dev/null +++ b/libs/hwui/tests/unit/UnderlineTest.cpp @@ -0,0 +1,153 @@ +/* + * 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. + */ + +#include <fcntl.h> +#include <flag_macros.h> +#include <gtest/gtest.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <utils/Log.h> + +#include "SkAlphaType.h" +#include "SkBitmap.h" +#include "SkData.h" +#include "SkFontMgr.h" +#include "SkImageInfo.h" +#include "SkRefCnt.h" +#include "SkStream.h" +#include "SkTypeface.h" +#include "SkiaCanvas.h" +#include "hwui/Bitmap.h" +#include "hwui/DrawTextFunctor.h" +#include "hwui/MinikinSkia.h" +#include "hwui/MinikinUtils.h" +#include "hwui/Paint.h" +#include "hwui/Typeface.h" + +using namespace android; + +namespace { + +constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf"; +constexpr char kJPFont[] = "/system/fonts/NotoSansCJK-Regular.ttc"; + +// The underline position and thickness are cames from post table. +constexpr float ROBOTO_POSITION_EM = 150.0 / 2048.0; +constexpr float ROBOTO_THICKNESS_EM = 100.0 / 2048.0; +constexpr float NOTO_CJK_POSITION_EM = 125.0 / 1000.0; +constexpr float NOTO_CJK_THICKNESS_EM = 50.0 / 1000.0; + +void unmap(const void* ptr, void* context) { + void* p = const_cast<void*>(ptr); + size_t len = reinterpret_cast<size_t>(context); + munmap(p, len); +} + +// Create a font family from a single font file. +std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) { + int fd = open(fileName, O_RDONLY); + LOG_ALWAYS_FATAL_IF(fd == -1, "Failed to open file %s", fileName); + struct stat st = {}; + LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", fileName); + void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + sk_sp<SkData> skData = + SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size)); + std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData)); + sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); + sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData))); + LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName); + std::shared_ptr<minikin::MinikinFont> font = + std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0, + std::vector<minikin::FontVariation>()); + std::vector<std::shared_ptr<minikin::Font>> fonts; + fonts.push_back(minikin::Font::Builder(font).build()); + return minikin::FontFamily::create(std::move(fonts)); +} + +// Create a typeface from roboto and NotoCJK. +Typeface* makeTypeface() { + return Typeface::createFromFamilies( + std::vector<std::shared_ptr<minikin::FontFamily>>( + {buildFamily(kRobotoVariable), buildFamily(kJPFont)}), + RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, nullptr /* fallback */); +} + +// Execute a text layout. +minikin::Layout doLayout(const std::vector<uint16_t> text, Paint paint, Typeface* typeface) { + return MinikinUtils::doLayout(&paint, minikin::Bidi::LTR, typeface, text.data(), text.size(), + 0 /* start */, text.size(), 0, text.size(), nullptr); +} + +DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) { + // Create canvas + SkImageInfo info = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType); + sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(info); + SkBitmap skBitmap; + bitmap->getSkBitmap(&skBitmap); + SkiaCanvas canvas(skBitmap); + + // Create minikin::Layout + std::unique_ptr<Typeface> typeface(makeTypeface()); + minikin::Layout layout = doLayout(text, *paint, typeface.get()); + + DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance()); + MinikinUtils::forFontRun(layout, paint, f); + return f; +} + +TEST_WITH_FLAGS(UnderlineTest, Roboto, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags, + fix_double_underline))) { + float textSize = 100; + Paint paint; + paint.getSkFont().setSize(textSize); + paint.setUnderline(true); + // the text is "abc" + DrawTextFunctor functor = processFunctor({0x0061, 0x0062, 0x0063}, &paint); + + EXPECT_EQ(ROBOTO_POSITION_EM * textSize, functor.getUnderlinePosition()); + EXPECT_EQ(ROBOTO_THICKNESS_EM * textSize, functor.getUnderlineThickness()); +} + +TEST_WITH_FLAGS(UnderlineTest, NotoCJK, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags, + fix_double_underline))) { + float textSize = 100; + Paint paint; + paint.getSkFont().setSize(textSize); + paint.setUnderline(true); + // The text is あいう in Japanese + DrawTextFunctor functor = processFunctor({0x3042, 0x3044, 0x3046}, &paint); + + EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition()); + EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness()); +} + +TEST_WITH_FLAGS(UnderlineTest, Mixture, + REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags, + fix_double_underline))) { + float textSize = 100; + Paint paint; + paint.getSkFont().setSize(textSize); + paint.setUnderline(true); + // The text is aいc. The only middle of the character is Japanese. + DrawTextFunctor functor = processFunctor({0x0061, 0x3044, 0x0063}, &paint); + + // We use the bottom, thicker line as underline. Here, use Noto's one. + EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition()); + EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness()); +} +} // namespace diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp index 4eabfb238f4a..8445032293dd 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -26,6 +26,21 @@ cc_test { srcs: [ "PointerController_test.cpp", ], + sanitize: { + hwaddress: true, + undefined: true, + all_undefined: true, + diag: { + undefined: true, + }, + }, + target: { + host: { + sanitize: { + address: true, + }, + }, + }, shared_libs: [ "libandroid_runtime", "libinputservice", diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 94faf4a65a1c..d9efd3c2fd83 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -148,6 +148,25 @@ void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t dis latestPointerDisplayId = displayId; } +class TestPointerController : public PointerController { +public: + TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener, + sp<PointerControllerPolicyInterface> policy, const sp<Looper>& looper, + SpriteController& spriteController) + : PointerController( + policy, looper, spriteController, + /*enabled=*/true, + [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { + // Register listener + registeredListener = listener; + }, + [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { + // Unregister listener + if (registeredListener == listener) registeredListener = nullptr; + }) {} + ~TestPointerController() override {} +}; + class PointerControllerTest : public Test { protected: PointerControllerTest(); @@ -159,6 +178,7 @@ protected: sp<MockPointerControllerPolicyInterface> mPolicy; std::unique_ptr<MockSpriteController> mSpriteController; std::shared_ptr<PointerController> mPointerController; + sp<android::gui::WindowInfosListener> mRegisteredListener; private: void loopThread(); @@ -181,11 +201,12 @@ PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<Moc EXPECT_CALL(*mSpriteController, createSprite()) .WillOnce(Return(mPointerSprite)); - mPointerController = - PointerController::create(mPolicy, mLooper, *mSpriteController, /*enabled=*/true); + mPointerController = std::make_unique<TestPointerController>(mRegisteredListener, mPolicy, + mLooper, *mSpriteController); } PointerControllerTest::~PointerControllerTest() { + mPointerController.reset(); mRunning.store(false, std::memory_order_relaxed); mThread.join(); } @@ -316,31 +337,16 @@ TEST_F(PointerControllerTest, notifiesPolicyWhenPointerDisplayChanges) { class PointerControllerWindowInfoListenerTest : public Test {}; -class TestPointerController : public PointerController { -public: - TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener, - const sp<Looper>& looper, SpriteController& spriteController) - : PointerController( - new MockPointerControllerPolicyInterface(), looper, spriteController, - /*enabled=*/true, - [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { - // Register listener - registeredListener = listener; - }, - [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { - // Unregister listener - if (registeredListener == listener) registeredListener = nullptr; - }) {} -}; - TEST_F(PointerControllerWindowInfoListenerTest, doesNotCrashIfListenerCalledAfterPointerControllerDestroyed) { sp<Looper> looper = new Looper(false); auto spriteController = NiceMock<MockSpriteController>(looper); sp<android::gui::WindowInfosListener> registeredListener; sp<android::gui::WindowInfosListener> localListenerCopy; + sp<MockPointerControllerPolicyInterface> policy = new MockPointerControllerPolicyInterface(); { - TestPointerController pointerController(registeredListener, looper, spriteController); + TestPointerController pointerController(registeredListener, policy, looper, + spriteController); ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered"; localListenerCopy = registeredListener; } diff --git a/location/Android.bp b/location/Android.bp index cfe0e494432c..eb7cd01111b2 100644 --- a/location/Android.bp +++ b/location/Android.bp @@ -39,3 +39,8 @@ java_sdk_library { ], }, } + +platform_compat_config { + name: "framework-location-compat-config", + src: ":framework-location", +} diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index b0cdb0554c11..230fb07f9f43 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -1167,7 +1167,10 @@ public final class AudioAttributes implements Parcelable { streamType); if (attributes != null) { mUsage = attributes.mUsage; - mContentType = attributes.mContentType; + // on purpose ignoring the content type: stream types are deprecated for + // playback, making assumptions about the content type is prone to + // interpretation errors for ambiguous types such as STREAM_TTS and STREAM_MUSIC + //mContentType = attributes.mContentType; mFlags = attributes.getAllFlags(); mMuteHapticChannels = attributes.areHapticChannelsMuted(); mIsContentSpatialized = attributes.isContentSpatialized(); @@ -1177,49 +1180,47 @@ public final class AudioAttributes implements Parcelable { mSource = attributes.mSource; } } - if (mContentType == CONTENT_TYPE_UNKNOWN) { - switch (streamType) { - case AudioSystem.STREAM_VOICE_CALL: - mContentType = CONTENT_TYPE_SPEECH; - break; - case AudioSystem.STREAM_SYSTEM_ENFORCED: - mFlags |= FLAG_AUDIBILITY_ENFORCED; - // intended fall through, attributes in common with STREAM_SYSTEM - case AudioSystem.STREAM_SYSTEM: - mContentType = CONTENT_TYPE_SONIFICATION; - break; - case AudioSystem.STREAM_RING: - mContentType = CONTENT_TYPE_SONIFICATION; - break; - case AudioSystem.STREAM_MUSIC: - mContentType = CONTENT_TYPE_MUSIC; - break; - case AudioSystem.STREAM_ALARM: - mContentType = CONTENT_TYPE_SONIFICATION; - break; - case AudioSystem.STREAM_NOTIFICATION: - mContentType = CONTENT_TYPE_SONIFICATION; - break; - case AudioSystem.STREAM_BLUETOOTH_SCO: - mContentType = CONTENT_TYPE_SPEECH; - mFlags |= FLAG_SCO; - break; - case AudioSystem.STREAM_DTMF: - mContentType = CONTENT_TYPE_SONIFICATION; - break; - case AudioSystem.STREAM_TTS: - mContentType = CONTENT_TYPE_SONIFICATION; - mFlags |= FLAG_BEACON; - break; - case AudioSystem.STREAM_ACCESSIBILITY: - mContentType = CONTENT_TYPE_SPEECH; - break; - case AudioSystem.STREAM_ASSISTANT: - mContentType = CONTENT_TYPE_SPEECH; - break; - default: - Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes"); - } + switch (streamType) { + case AudioSystem.STREAM_VOICE_CALL: + mContentType = CONTENT_TYPE_SPEECH; + break; + case AudioSystem.STREAM_SYSTEM_ENFORCED: + mFlags |= FLAG_AUDIBILITY_ENFORCED; + // intended fall through, attributes in common with STREAM_SYSTEM + case AudioSystem.STREAM_SYSTEM: + mContentType = CONTENT_TYPE_SONIFICATION; + break; + case AudioSystem.STREAM_RING: + mContentType = CONTENT_TYPE_SONIFICATION; + break; + case AudioSystem.STREAM_ALARM: + mContentType = CONTENT_TYPE_SONIFICATION; + break; + case AudioSystem.STREAM_NOTIFICATION: + mContentType = CONTENT_TYPE_SONIFICATION; + break; + case AudioSystem.STREAM_BLUETOOTH_SCO: + mContentType = CONTENT_TYPE_SPEECH; + mFlags |= FLAG_SCO; + break; + case AudioSystem.STREAM_DTMF: + mContentType = CONTENT_TYPE_SONIFICATION; + break; + case AudioSystem.STREAM_TTS: + mContentType = CONTENT_TYPE_SONIFICATION; + mFlags |= FLAG_BEACON; + break; + case AudioSystem.STREAM_ACCESSIBILITY: + mContentType = CONTENT_TYPE_SPEECH; + break; + case AudioSystem.STREAM_ASSISTANT: + mContentType = CONTENT_TYPE_SPEECH; + break; + case AudioSystem.STREAM_MUSIC: + // leaving as CONTENT_TYPE_UNKNOWN + break; + default: + Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes"); } if (mUsage == USAGE_UNKNOWN) { mUsage = usageForStreamType(streamType); diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING index a792498b8521..7aa9118e45ee 100644 --- a/media/java/android/media/projection/TEST_MAPPING +++ b/media/java/android/media/projection/TEST_MAPPING @@ -4,9 +4,6 @@ "name": "MediaProjectionTests", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index a4e5fb6a6165..196b5c3112c0 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -401,7 +401,9 @@ public class TvView extends ViewGroup { private void resetInternal() { mSessionCallback = null; - mPendingAppPrivateCommands.clear(); + synchronized (mPendingAppPrivateCommands) { + mPendingAppPrivateCommands.clear(); + } if (mSession != null) { setSessionSurface(null); removeSessionOverlayView(); @@ -691,7 +693,10 @@ public class TvView extends ViewGroup { } else { Log.w(TAG, "sendAppPrivateCommand - session not yet created (action \"" + action + "\" pending)"); - mPendingAppPrivateCommands.add(Pair.create(action, data)); + + synchronized (mPendingAppPrivateCommands) { + mPendingAppPrivateCommands.add(Pair.create(action, data)); + } } } @@ -1320,10 +1325,13 @@ public class TvView extends ViewGroup { mSession = session; if (session != null) { // Sends the pending app private commands first. - for (Pair<String, Bundle> command : mPendingAppPrivateCommands) { - mSession.sendAppPrivateCommand(command.first, command.second); + + synchronized (mPendingAppPrivateCommands) { + for (Pair<String, Bundle> command : mPendingAppPrivateCommands) { + mSession.sendAppPrivateCommand(command.first, command.second); + } + mPendingAppPrivateCommands.clear(); } - mPendingAppPrivateCommands.clear(); synchronized (sMainTvViewLock) { if (hasWindowFocus() && TvView.this == sMainTvView.get() diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 9924fae26194..09f09b94ac0d 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -2409,13 +2409,16 @@ public class Tuner implements AutoCloseable { @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) @Nullable public Descrambler openDescrambler() { + acquireTRMSLock("openDescrambler()"); mDemuxLock.lock(); try { - if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) { + // no need to unlock mDemuxLock (so pass null instead) as TRMS lock is already acquired + if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, null)) { return null; } return requestDescrambler(); } finally { + releaseTRMSLock(); mDemuxLock.unlock(); } } diff --git a/mime/Android.bp b/mime/Android.bp index a3ea65cb2efe..757862b998b4 100644 --- a/mime/Android.bp +++ b/mime/Android.bp @@ -12,7 +12,6 @@ // 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 @@ -125,6 +124,6 @@ java_genrule { srcs: [ "java-res/vendor.mime.types", ], - // strip comments normalize whitepace drop empty lines prepend ? to fields that are missing it - cmd: "awk '{gsub(/#.*$$/,\"\"); $$1=$$1; print;}' $(in) | grep ' ' | awk '{for(i=1;i<=NF;i++) { sub(/^\\??/, \"?\", $$i); }; print}' > $(out)", + // strip comments normalize whitepace drop empty lines prepend ? to fields that are missing it + cmd: "awk '{gsub(/#.*$$/,\"\"); $$1=$$1; print;}' $(in) | (grep ' ' || echo -n '') | awk '{for(i=1;i<=NF;i++) { sub(/^\\??/, \"?\", $$i); }; print}' > $(out)", } diff --git a/packages/CredentialManager/shared/Android.bp b/packages/CredentialManager/shared/Android.bp index 38d98a9d47f7..0d4af2a60672 100644 --- a/packages/CredentialManager/shared/Android.bp +++ b/packages/CredentialManager/shared/Android.bp @@ -12,6 +12,7 @@ android_library { manifest: "AndroidManifest.xml", srcs: ["src/**/*.kt"], static_libs: [ + "androidx.activity_activity-compose", "androidx.core_core-ktx", "androidx.credentials_credentials", "guava", diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ApiConstants.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ApiConstants.kt new file mode 100644 index 000000000000..6498ff7017fb --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ApiConstants.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0N + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager + +const val IS_AUTO_SELECTED_KEY = "IS_AUTO_SELECTED" diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt index defba8dacb7b..8986e5237f61 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt @@ -17,14 +17,25 @@ package com.android.credentialmanager import android.content.Intent +import android.content.pm.PackageManager import android.credentials.ui.RequestInfo import com.android.credentialmanager.ktx.requestInfo import com.android.credentialmanager.mapper.toGet import com.android.credentialmanager.mapper.toRequestCancel +import com.android.credentialmanager.mapper.toRequestClose import com.android.credentialmanager.model.Request -fun Intent.parse(): Request { - this.toRequestCancel()?.let { return it } +fun Intent.parse( + packageManager: PackageManager, + previousIntent: Intent? = null, +): Request { + this.toRequestClose(packageManager, previousIntent)?.let { closeRequest -> + return closeRequest + } + + this.toRequestCancel(packageManager)?.let { cancelRequest -> + return cancelRequest + } return when (requestInfo?.type) { RequestInfo.TYPE_CREATE -> { diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/activity/StartBalIntentSenderForResultContract.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/activity/StartBalIntentSenderForResultContract.kt new file mode 100644 index 000000000000..ef083fd46f6a --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/activity/StartBalIntentSenderForResultContract.kt @@ -0,0 +1,53 @@ +/* + * 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.credentialmanager.activity + +import android.app.ActivityOptions +import android.content.Context +import android.content.Intent +import androidx.activity.result.ActivityResult +import androidx.activity.result.IntentSenderRequest +import androidx.activity.result.contract.ActivityResultContract +import androidx.activity.result.contract.ActivityResultContracts + +/** + * A custom StartIntentSenderForResult contract implementation that attaches an [ActivityOptions] + * that opts in for background activity launch. + */ +class StartBalIntentSenderForResultContract : + ActivityResultContract<IntentSenderRequest, ActivityResult>() { + override fun createIntent(context: Context, input: IntentSenderRequest): Intent { + val activityOptionBundle = + ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ).toBundle() + return Intent( + ActivityResultContracts.StartIntentSenderForResult.ACTION_INTENT_SENDER_REQUEST + ).putExtra( + ActivityResultContracts.StartActivityForResult.EXTRA_ACTIVITY_OPTIONS_BUNDLE, + activityOptionBundle + ).putExtra( + ActivityResultContracts.StartIntentSenderForResult.EXTRA_INTENT_SENDER_REQUEST, + input + ) + } + + override fun parseResult( + resultCode: Int, + intent: Intent? + ): ActivityResult = ActivityResult(resultCode, intent) +}
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt index a4c20bfabb35..4533db674da2 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt @@ -18,10 +18,12 @@ package com.android.credentialmanager.ktx import android.content.Intent import android.credentials.ui.CancelUiRequest +import android.credentials.ui.Constants import android.credentials.ui.CreateCredentialProviderData import android.credentials.ui.GetCredentialProviderData import android.credentials.ui.ProviderData import android.credentials.ui.RequestInfo +import android.os.ResultReceiver val Intent.cancelUiRequest: CancelUiRequest? get() = this.extras?.getParcelable( @@ -46,3 +48,9 @@ val Intent.createCredentialProviderDataList: List<ProviderData> ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, CreateCredentialProviderData::class.java ) ?: emptyList() + +val Intent.resultReceiver: ResultReceiver? + get() = this.getParcelableExtra( + Constants.EXTRA_RESULT_RECEIVER, + ResultReceiver::class.java + ) diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt index 86a6d23a76a9..555a86f59377 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt @@ -17,13 +17,23 @@ package com.android.credentialmanager.mapper import android.content.Intent +import android.content.pm.PackageManager +import android.util.Log +import com.android.credentialmanager.TAG +import com.android.credentialmanager.ktx.appLabel import com.android.credentialmanager.ktx.cancelUiRequest import com.android.credentialmanager.model.Request -fun Intent.toRequestCancel(): Request.Cancel? = +fun Intent.toRequestCancel(packageManager: PackageManager): Request.Cancel? = this.cancelUiRequest?.let { cancelUiRequest -> - Request.Cancel( - showCancellationUi = cancelUiRequest.shouldShowCancellationUi(), - appPackageName = cancelUiRequest.appPackageName - ) + val appLabel = packageManager.appLabel(cancelUiRequest.appPackageName) + if (appLabel == null) { + Log.d(TAG, "Received UI cancel request with an invalid package name.") + null + } else { + Request.Cancel( + showCancellationUi = cancelUiRequest.shouldShowCancellationUi(), + appName = appLabel + ) + } } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt new file mode 100644 index 000000000000..6de3e7d60bf8 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt @@ -0,0 +1,47 @@ +/* + * 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.0N + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.mapper + +import android.content.Intent +import android.content.pm.PackageManager +import com.android.credentialmanager.ktx.requestInfo +import com.android.credentialmanager.model.Request + +fun Intent.toRequestClose( + packageManager: PackageManager, + previousIntent: Intent? = null, +): Request.Close? { + // Close request comes as "Cancel" request from Credential Manager API + val currentRequest = toRequestCancel(packageManager = packageManager) ?: return null + + if (currentRequest.showCancellationUi) { + // Current request is to Cancel and not to Close + return null + } + + previousIntent?.let { + val previousToken = previousIntent.requestInfo?.token + val currentToken = this.requestInfo?.token + + if (previousToken != currentToken) { + // Current cancellation is for a different request, don't close the current flow. + return null + } + } + + return Request.Close +}
\ No newline at end of file diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt index ed9d56344853..ee45fbb00ba6 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt @@ -1,22 +1,66 @@ +/* + * 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.0N + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.credentialmanager.mapper import android.content.Intent +import android.credentials.ui.Entry import android.credentials.ui.GetCredentialProviderData +import androidx.credentials.provider.PasswordCredentialEntry +import com.android.credentialmanager.factory.fromSlice import com.android.credentialmanager.ktx.getCredentialProviderDataList +import com.android.credentialmanager.ktx.requestInfo +import com.android.credentialmanager.ktx.resultReceiver +import com.android.credentialmanager.model.Password import com.android.credentialmanager.model.Request import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap -fun Intent.toGet() = Request.Get( - providers = ImmutableMap.copyOf( - getCredentialProviderDataList.associateBy { it.providerFlattenedComponentName } - ), - entries = ImmutableList.copyOf( - getCredentialProviderDataList.map { providerData -> - check(providerData is GetCredentialProviderData) { - "Invalid provider data type for GetCredentialRequest" +fun Intent.toGet(): Request.Get { + val credentialEntries = mutableListOf<Pair<String, Entry>>() + for (providerData in getCredentialProviderDataList) { + if (providerData is GetCredentialProviderData) { + for (credentialEntry in providerData.credentialEntries) { + credentialEntries.add( + Pair(providerData.providerFlattenedComponentName, credentialEntry) + ) } - providerData - }.flatMap { it.credentialEntries } + } + } + + val passwordEntries = mutableListOf<Password>() + for ((providerId, entry) in credentialEntries) { + val slice = fromSlice(entry.slice) + if (slice is PasswordCredentialEntry) { + passwordEntries.add( + Password( + providerId = providerId, + entry = entry, + passwordCredentialEntry = slice + ) + ) + } + } + + return Request.Get( + token = requestInfo?.token, + resultReceiver = this.resultReceiver, + providers = ImmutableMap.copyOf( + getCredentialProviderDataList.associateBy { it.providerFlattenedComponentName } + ), + passwordEntries = ImmutableList.copyOf(passwordEntries) ) -)
\ No newline at end of file +} diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt new file mode 100644 index 000000000000..2fe4fd5a12e4 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Password.kt @@ -0,0 +1,26 @@ +/* + * 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.0N + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.model + +import android.credentials.ui.Entry +import androidx.credentials.provider.PasswordCredentialEntry + +data class Password( + val providerId: String, + val entry: Entry, + val passwordCredentialEntry: PasswordCredentialEntry, +) diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt index bc073105efe1..6011a1c1933a 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt @@ -16,8 +16,9 @@ package com.android.credentialmanager.model -import android.credentials.ui.Entry import android.credentials.ui.ProviderData +import android.os.IBinder +import android.os.ResultReceiver import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap @@ -25,15 +26,33 @@ import com.google.common.collect.ImmutableMap * Represents the request made by the CredentialManager API. */ sealed class Request { + + /** + * Request to close the app without displaying a message to the user and without reporting + * anything back to the Credential Manager service. + */ + data object Close : Request() + + /** + * Request to close the app, displaying a message to the user. + */ data class Cancel( val showCancellationUi: Boolean, - val appPackageName: String? + val appName: String ) : Request() + /** + * Request to start the get credentials flow. + */ data class Get( + val token: IBinder?, + val resultReceiver: ResultReceiver?, val providers: ImmutableMap<String, ProviderData>, - val entries: ImmutableList<Entry>, + val passwordEntries: ImmutableList<Password>, ) : Request() + /** + * Request to start the create credentials flow. + */ data object Create : Request() } diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt new file mode 100644 index 000000000000..5ab5ab974720 --- /dev/null +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/RequestRepository.kt @@ -0,0 +1,45 @@ +/* + * 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.0N + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.repository + +import android.app.Application +import android.content.Intent +import android.util.Log +import com.android.credentialmanager.TAG +import com.android.credentialmanager.model.Request +import com.android.credentialmanager.parse +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class RequestRepository( + private val application: Application, +) { + + private val _requests = MutableStateFlow<Request?>(null) + val requests: StateFlow<Request?> = _requests + + suspend fun processRequest(intent: Intent, previousIntent: Intent? = null) { + val request = intent.parse( + packageManager = application.packageManager, + previousIntent = previousIntent + ) + + Log.d(TAG, "Request parsed: $request") + + _requests.value = request + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 943c2b4b3a5b..ba88484518aa 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -16,23 +16,183 @@ package com.android.credentialmanager.autofill +import android.app.assist.AssistStructure +import android.content.Context +import android.credentials.GetCredentialRequest +import android.credentials.CredentialManager +import android.credentials.GetCandidateCredentialsResponse +import android.credentials.CredentialOption +import android.credentials.GetCandidateCredentialsException +import android.os.Bundle import android.os.CancellationSignal +import android.os.OutcomeReceiver +import android.service.autofill.FillRequest import android.service.autofill.AutofillService +import android.service.autofill.FillResponse import android.service.autofill.FillCallback -import android.service.autofill.FillRequest import android.service.autofill.SaveRequest import android.service.autofill.SaveCallback +import android.util.Log +import org.json.JSONObject +import java.util.concurrent.Executors class CredentialAutofillService : AutofillService() { + + companion object { + private const val TAG = "CredAutofill" + + private const val CRED_HINT_PREFIX = "credential=" + private const val REQUEST_DATA_KEY = "requestData" + private const val CANDIDATE_DATA_KEY = "candidateQueryData" + private const val SYS_PROVIDER_REQ_KEY = "isSystemProviderRequired" + private const val CRED_OPTIONS_KEY = "credentialOptions" + private const val TYPE_KEY = "type" + } + + private val credentialManager: CredentialManager = + getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager + override fun onFillRequest( request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback ) { + val context = request.fillContexts + val structure = context[context.size - 1].structure + val callingPackage = structure.activityComponent.packageName + Log.i(TAG, "onFillRequest called for $callingPackage") + + val getCredRequest: GetCredentialRequest? = getCredManRequest(structure) + if (getCredRequest == null) { + callback.onFailure("No credential manager request found") + return + } + + val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse, + GetCandidateCredentialsException> { + override fun onResult(result: GetCandidateCredentialsResponse) { + Log.i(TAG, "getCandidateCredentials onResponse") + val fillResponse: FillResponse? = convertToFillResponse(result, request) + callback.onSuccess(fillResponse) + } + + override fun onError(error: GetCandidateCredentialsException) { + Log.i(TAG, "getCandidateCredentials onError") + callback.onFailure("error received from credential manager ${error.message}") + } + } + + credentialManager.getCandidateCredentials( + getCredRequest, + callingPackage, + CancellationSignal(), + Executors.newSingleThreadExecutor(), + outcome + ) + } + + private fun convertToFillResponse( + getCredResponse: GetCandidateCredentialsResponse, + filLRequest: FillRequest + ): FillResponse? { TODO("Not yet implemented") } override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { TODO("Not yet implemented") } + + private fun getCredManRequest(structure: AssistStructure): GetCredentialRequest? { + val credentialOptions: MutableList<CredentialOption> = mutableListOf() + traverseStructure(structure, credentialOptions) + + if (credentialOptions.isNotEmpty()) { + return GetCredentialRequest.Builder(Bundle.EMPTY) + .setCredentialOptions(credentialOptions) + .build() + } + return null + } + + private fun traverseStructure( + structure: AssistStructure, + cmRequests: MutableList<CredentialOption> + ) { + val windowNodes: List<AssistStructure.WindowNode> = + structure.run { + (0 until windowNodeCount).map { getWindowNodeAt(it) } + } + + windowNodes.forEach { windowNode: AssistStructure.WindowNode -> + traverseNode(windowNode.rootViewNode, cmRequests) + } + } + + private fun traverseNode( + viewNode: AssistStructure.ViewNode?, + cmRequests: MutableList<CredentialOption> + ) { + val options = getCredentialOptionsFromViewNode(viewNode) + cmRequests.addAll(options) + + val children: List<AssistStructure.ViewNode>? = + viewNode?.run { + (0 until childCount).map { getChildAt(it) } + } + + children?.forEach { childNode: AssistStructure.ViewNode -> + traverseNode(childNode, cmRequests) + } + } + + private fun getCredentialOptionsFromViewNode(viewNode: AssistStructure.ViewNode?): + List<CredentialOption> { + // TODO(b/293945193) Replace with isCredential check from viewNode + val credentialHints: MutableList<String> = mutableListOf() + if (viewNode != null && viewNode.autofillHints != null) { + for (hint in viewNode.autofillHints!!) { + if (hint.startsWith(CRED_HINT_PREFIX)) { + credentialHints.add(hint.substringAfter(CRED_HINT_PREFIX)) + } + } + } + + val credentialOptions: MutableList<CredentialOption> = mutableListOf() + for (credentialHint in credentialHints) { + convertJsonToCredentialOption(credentialHint).let { credentialOptions.addAll(it) } + } + return credentialOptions + } + + private fun convertJsonToCredentialOption(jsonString: String): List<CredentialOption> { + // TODO(b/302000646) Move this logic to jetpack so that is consistent + // with building the json + val credentialOptions: MutableList<CredentialOption> = mutableListOf() + + val json = JSONObject(jsonString) + val options = json.getJSONArray(CRED_OPTIONS_KEY) + for (i in 0 until options.length()) { + val option = options.getJSONObject(i) + + credentialOptions.add(CredentialOption( + option.getString(TYPE_KEY), + convertJsonToBundle(option.getJSONObject(REQUEST_DATA_KEY)), + convertJsonToBundle(option.getJSONObject(CANDIDATE_DATA_KEY)), + option.getBoolean(SYS_PROVIDER_REQ_KEY), + )) + } + return credentialOptions + } + + private fun convertJsonToBundle(json: JSONObject): Bundle { + val result = Bundle() + json.keys().forEach { + val v = json.get(it) + when (v) { + is String -> result.putString(it, v) + is Boolean -> result.putBoolean(it, v) + } + } + return result + } }
\ No newline at end of file diff --git a/packages/CredentialManager/wear/Android.bp b/packages/CredentialManager/wear/Android.bp index c0dff168969d..e5f5cc255733 100644 --- a/packages/CredentialManager/wear/Android.bp +++ b/packages/CredentialManager/wear/Android.bp @@ -37,6 +37,7 @@ android_app { "androidx.lifecycle_lifecycle-extensions", "androidx.lifecycle_lifecycle-livedata", "androidx.lifecycle_lifecycle-runtime-ktx", + "androidx.lifecycle_lifecycle-runtime-compose", "androidx.lifecycle_lifecycle-viewmodel-compose", "androidx.wear.compose_compose-foundation", "androidx.wear.compose_compose-material", diff --git a/packages/CredentialManager/wear/AndroidManifest.xml b/packages/CredentialManager/wear/AndroidManifest.xml index 90248734ca7f..b480ac30d2cb 100644 --- a/packages/CredentialManager/wear/AndroidManifest.xml +++ b/packages/CredentialManager/wear/AndroidManifest.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> + <!-- /* * Copyright (c) 2023 Google Inc. @@ -21,25 +22,27 @@ <uses-feature android:name="android.hardware.type.watch" /> - <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/> - <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> - <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/> + <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR" /> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> + <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS" /> <application - android:allowBackup="true" - android:dataExtractionRules="@xml/data_extraction_rules" - android:fullBackupContent="@xml/backup_rules" - android:label="@string/app_name" - android:supportsRtl="true"> + android:name=".CredentialSelectorApp" + android:allowBackup="true" + android:dataExtractionRules="@xml/data_extraction_rules" + android:fullBackupContent="@xml/backup_rules" + android:label="@string/app_name" + android:supportsRtl="true"> + <!-- Activity called by GMS has to be exactly: + com.android.credentialmanager.CredentialSelectorActivity --> <activity - android:name=".ui.CredentialSelectorActivity" + android:name=".CredentialSelectorActivity" + android:excludeFromRecents="true" android:exported="true" - android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR" - android:launchMode="singleTop" android:label="@string/app_name" - android:excludeFromRecents="true"> - </activity> - </application> + android:launchMode="singleTop" + android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR" /> + </application> </manifest> diff --git a/packages/CredentialManager/wear/res/values/themes.xml b/packages/CredentialManager/wear/res/values/themes.xml deleted file mode 100644 index 22329e9ff2ce..000000000000 --- a/packages/CredentialManager/wear/res/values/themes.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> -<resources> - <style name="Theme.CredentialSelector" parent="@*android:style/ThemeOverlay.DeviceDefault.Accent.DayNight"> - <item name="android:windowContentOverlay">@null</item> - <item name="android:windowNoTitle">true</item> - <item name="android:windowBackground">@android:color/transparent</item> - <item name="android:windowIsTranslucent">true</item> - </style> -</resources>
\ No newline at end of file diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt new file mode 100644 index 000000000000..273d0b120972 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -0,0 +1,100 @@ +/* + * 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.0N + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager + +import android.content.Intent +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.wear.compose.material.MaterialTheme +import com.android.credentialmanager.ui.WearApp +import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen +import com.google.android.horologist.annotations.ExperimentalHorologistApi +import com.google.android.horologist.compose.layout.belowTimeTextPreview +import kotlinx.coroutines.launch + +class CredentialSelectorActivity : ComponentActivity() { + + private val viewModel: CredentialSelectorViewModel by viewModels { + CredentialSelectorViewModel.Factory + } + + @OptIn(ExperimentalHorologistApi::class) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setTheme(android.R.style.Theme_DeviceDefault) + + // TODO: b/301027810 due to this issue with compose in Main platform, we are implementing a + // workaround. Once the issue is fixed, remove the "else" bracket and leave only the + // contents of the "if" bracket. + if (false) { + setContent { + MaterialTheme { + WearApp( + viewModel = viewModel, + onCloseApp = ::finish, + ) + } + } + } else { + // TODO: b/301027810 Remove the content of this "else" bracket fully once issue is fixed + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.uiState.collect { uiState -> + when (uiState) { + CredentialSelectorUiState.Idle -> { + // Don't display anything, assuming that there should be minimal latency + // to parse the Credential Manager intent and define the state of the + // app. If latency is big, then a "loading" screen should be displayed + // to the user. + } + + is CredentialSelectorUiState.Get -> { + setContent { + MaterialTheme { + SinglePasswordScreen( + columnState = belowTimeTextPreview(), + onCloseApp = ::finish, + ) + } + } + } + + else -> finish() + } + } + } + } + } + + viewModel.onNewIntent(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + + val previousIntent = getIntent() + setIntent(intent) + + viewModel.onNewIntent(intent, previousIntent) + } +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt new file mode 100644 index 000000000000..7c81fd06c4e2 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0N + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager + +import android.app.Application +import com.android.credentialmanager.di.inject +import com.android.credentialmanager.repository.RequestRepository + +class CredentialSelectorApp : Application() { + + lateinit var requestRepository: RequestRepository + + override fun onCreate() { + super.onCreate() + + inject() + } +}
\ No newline at end of file diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt new file mode 100644 index 000000000000..d557dc071db7 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.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.0N + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager + +import android.content.Intent +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import com.android.credentialmanager.model.Request +import com.android.credentialmanager.repository.RequestRepository +import com.android.credentialmanager.ui.mappers.toGet +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +class CredentialSelectorViewModel( + private val requestRepository: RequestRepository, +) : ViewModel() { + + val uiState: StateFlow<CredentialSelectorUiState> = requestRepository.requests + .map { request -> + when (request) { + null -> CredentialSelectorUiState.Idle + is Request.Cancel -> CredentialSelectorUiState.Cancel(request.appName) + Request.Close -> CredentialSelectorUiState.Close + Request.Create -> CredentialSelectorUiState.Create + is Request.Get -> request.toGet() + } + } + .stateIn( + viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = CredentialSelectorUiState.Idle, + ) + + fun onNewIntent(intent: Intent, previousIntent: Intent? = null) { + viewModelScope.launch { + requestRepository.processRequest(intent = intent, previousIntent = previousIntent) + } + } + + companion object { + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun <T : ViewModel> create( + modelClass: Class<T>, + extras: CreationExtras + ): T { + val application = checkNotNull(extras[APPLICATION_KEY]) + + return CredentialSelectorViewModel( + requestRepository = (application as CredentialSelectorApp).requestRepository, + ) as T + } + } + } +} + +sealed class CredentialSelectorUiState { + data object Idle : CredentialSelectorUiState() + sealed class Get : CredentialSelectorUiState() { + data object SingleProviderSinglePasskey : Get() + data object SingleProviderSinglePassword : Get() + + // TODO: b/301206470 add the remaining states + } + + data object Create : CredentialSelectorUiState() + data class Cancel(val appName: String) : CredentialSelectorUiState() + data object Close : CredentialSelectorUiState() +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt new file mode 100644 index 000000000000..a11017b919bf --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt @@ -0,0 +1,17 @@ +package com.android.credentialmanager.di + +import android.app.Application +import com.android.credentialmanager.CredentialSelectorApp +import com.android.credentialmanager.repository.RequestRepository + +// TODO b/301601582 add Hilt for dependency injection + +fun CredentialSelectorApp.inject() { + requestRepository = requestRepository(application = this) +} + +private fun requestRepository( + application: Application, +): RequestRepository = RequestRepository( + application = application, +) diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt deleted file mode 100644 index 53122ba7ccdc..000000000000 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt +++ /dev/null @@ -1,87 +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.0N - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.credentialmanager.ui - -import android.content.Intent -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.wear.compose.material.MaterialTheme -import kotlinx.coroutines.launch - -class CredentialSelectorActivity : ComponentActivity() { - - private val viewModel: CredentialSelectorViewModel by viewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setTheme(android.R.style.Theme_DeviceDefault) - - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.uiState.collect { uiState -> - when (uiState) { - CredentialSelectorUiState.Idle -> { - // Don't display anything, assuming that there should be minimal latency - // to parse the Credential Manager intent and define the state of the - // app. If latency is big, then a "loading" screen should be displayed - // to the user. - } - - is CredentialSelectorUiState.Get -> { - setContent { - MaterialTheme { - WearApp() - } - } - } - - CredentialSelectorUiState.Create -> { - // TODO: b/301206624 - Implement create flow - finish() - } - - is CredentialSelectorUiState.Cancel -> { - // TODO: b/300422310 - Implement cancel with message flow - finish() - } - - CredentialSelectorUiState.Finish -> { - finish() - } - } - } - } - } - - viewModel.onNewIntent(intent) - } - - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - - val previousIntent = getIntent() - setIntent(intent) - - viewModel.onNewIntent(intent, previousIntent) - } -} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt deleted file mode 100644 index d22d5d1a28a3..000000000000 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorViewModel.kt +++ /dev/null @@ -1,123 +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.0N - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.credentialmanager.ui - -import android.app.Application -import android.content.Intent -import android.util.Log -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.viewModelScope -import com.android.credentialmanager.TAG -import com.android.credentialmanager.parse -import com.android.credentialmanager.ktx.appLabel -import com.android.credentialmanager.ktx.requestInfo -import com.android.credentialmanager.mapper.toGet -import com.android.credentialmanager.ui.model.PasskeyUiModel -import com.android.credentialmanager.ui.model.PasswordUiModel -import com.android.credentialmanager.model.Request -import com.android.credentialmanager.ui.mapper.toGet -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch - -class CredentialSelectorViewModel( - private val application: Application -) : AndroidViewModel(application = application) { - - private val _uiState = - MutableStateFlow<CredentialSelectorUiState>(CredentialSelectorUiState.Idle) - val uiState: StateFlow<CredentialSelectorUiState> = _uiState - - fun onNewIntent(intent: Intent, previousIntent: Intent? = null) { - viewModelScope.launch { - val request = intent.parse() - if (shouldFinishActivity(request = request, previousIntent = previousIntent)) { - _uiState.value = CredentialSelectorUiState.Finish - } else { - when (request) { - is Request.Cancel -> { - request.appPackageName?.let { appPackageName -> - application.packageManager.appLabel(appPackageName)?.let { appLabel -> - _uiState.value = CredentialSelectorUiState.Cancel(appLabel) - } ?: run { - Log.d(TAG, - "Received UI cancel request with an invalid package name.") - _uiState.value = CredentialSelectorUiState.Finish - } - } ?: run { - Log.d(TAG, "Received UI cancel request with an invalid package name.") - _uiState.value = CredentialSelectorUiState.Finish - } - } - - Request.Create -> { - _uiState.value = CredentialSelectorUiState.Create - } - - is Request.Get -> { - _uiState.value = request.toGet() - } - } - } - } - } - - /** - * Check if backend requested the UI activity to be cancelled. Different from the other - * finishing flows, this one does not report anything back to the Credential Manager service - * backend. - */ - private fun shouldFinishActivity(request: Request, previousIntent: Intent? = null): Boolean { - if (request !is Request.Cancel) { - return false - } else { - Log.d( - TAG, "Received UI cancellation intent. Should show cancellation" + - " ui = ${request.showCancellationUi}") - - previousIntent?.let { - val previousUiRequest = previousIntent.parse() - - if (previousUiRequest is Request.Cancel) { - val previousToken = previousIntent.requestInfo?.token - val currentToken = previousIntent.requestInfo?.token - - if (previousToken != currentToken) { - // Cancellation was for a different request, don't cancel the current UI. - return false - } - } - } - - return !request.showCancellationUi - } - } -} - -sealed class CredentialSelectorUiState { - data object Idle : CredentialSelectorUiState() - sealed class Get : CredentialSelectorUiState() { - data class SingleProviderSinglePasskey(val passkeyUiModel: PasskeyUiModel) : Get() - data class SingleProviderSinglePassword(val passwordUiModel: PasswordUiModel) : Get() - - // TODO: b/301206470 add the remaining states - } - - data object Create : CredentialSelectorUiState() - data class Cancel(val appName: String) : CredentialSelectorUiState() - data object Finish : CredentialSelectorUiState() -} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Navigation.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Navigation.kt new file mode 100644 index 000000000000..da5697dab8d4 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Navigation.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0N + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.ui + +import androidx.navigation.NavController + +fun NavController.navigateToLoading() { + navigate(Screen.Loading.route) +} + +fun NavController.navigateToSinglePasswordScreen() { + navigate(Screen.SinglePasswordScreen.route) +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt index 7d1a49b07718..c3919a03316f 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt @@ -19,5 +19,7 @@ package com.android.credentialmanager.ui sealed class Screen( val route: String, ) { - data object Main : Screen("main") + data object Loading : Screen("loading") + + data object SinglePasswordScreen : Screen("singlePasswordScreen") } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt index 19ea9ede9d98..7e0ea3077559 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt @@ -19,28 +19,94 @@ package com.android.credentialmanager.ui import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavController import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState -import com.android.credentialmanager.ui.screens.MainScreen +import com.android.credentialmanager.CredentialSelectorUiState +import com.android.credentialmanager.CredentialSelectorViewModel +import com.android.credentialmanager.ui.screens.LoadingScreen +import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.navscaffold.WearNavScaffold import com.google.android.horologist.compose.navscaffold.composable +import com.google.android.horologist.compose.navscaffold.scrollable @Composable -fun WearApp() { +fun WearApp( + viewModel: CredentialSelectorViewModel, + onCloseApp: () -> Unit, +) { val navController = rememberSwipeDismissableNavController() val swipeToDismissBoxState = rememberSwipeToDismissBoxState() val navHostState = rememberSwipeDismissableNavHostState(swipeToDismissBoxState = swipeToDismissBoxState) + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + WearNavScaffold( - startDestination = Screen.Main.route, + startDestination = Screen.Loading.route, navController = navController, state = navHostState, ) { - composable(Screen.Main.route) { - MainScreen() + composable(Screen.Loading.route) { + LoadingScreen() + } + + scrollable(Screen.SinglePasswordScreen.route) { + SinglePasswordScreen( + columnState = it.columnState, + onCloseApp = onCloseApp, + ) + } + } + + when (val state = uiState) { + CredentialSelectorUiState.Idle -> { + if (navController.currentDestination?.route != Screen.Loading.route) { + navController.navigateToLoading() + } + } + + is CredentialSelectorUiState.Get -> { + handleGetNavigation( + navController = navController, + state = state, + onCloseApp = onCloseApp, + ) + } + + CredentialSelectorUiState.Create -> { + // TODO: b/301206624 - Implement create flow + onCloseApp() + } + + is CredentialSelectorUiState.Cancel -> { + // TODO: b/300422310 - Implement cancel with message flow + onCloseApp() + } + + CredentialSelectorUiState.Close -> { + onCloseApp() + } + } +} + +private fun handleGetNavigation( + navController: NavController, + state: CredentialSelectorUiState.Get, + onCloseApp: () -> Unit, +) { + when (state) { + is CredentialSelectorUiState.Get.SingleProviderSinglePassword -> { + navController.navigateToSinglePasswordScreen() + } + + else -> { + // TODO: b/301206470 - Implement other get flows + onCloseApp() } } } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt deleted file mode 100644 index 5ceec1783c84..000000000000 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mapper/CredentialSelectorUiStateGetMapper.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.android.credentialmanager.ui.mapper - -import androidx.credentials.provider.CustomCredentialEntry -import androidx.credentials.provider.PasswordCredentialEntry -import androidx.credentials.provider.PublicKeyCredentialEntry -import com.android.credentialmanager.ui.CredentialSelectorUiState -import com.android.credentialmanager.factory.fromSlice -import com.android.credentialmanager.ui.model.PasswordUiModel -import com.android.credentialmanager.model.Request - -fun Request.Get.toGet(): CredentialSelectorUiState.Get { - if (this.providers.isEmpty()) { - throw IllegalStateException("Invalid GetCredential request with empty list of providers.") - } - - if (this.entries.isEmpty()) { - throw IllegalStateException("Invalid GetCredential request with empty list of entries.") - } - - if (this.providers.size == 1) { - if (this.entries.size == 1) { - val slice = this.entries.first().slice - when (val credentialEntry = fromSlice(slice)) { - is PasswordCredentialEntry -> { - return CredentialSelectorUiState.Get.SingleProviderSinglePassword( - PasswordUiModel(credentialEntry.displayName.toString()) - ) - } - - is PublicKeyCredentialEntry -> { - TODO("b/301206470 - to be implemented") - } - - is CustomCredentialEntry -> { - TODO("b/301206470 - to be implemented") - } - - else -> { - throw IllegalStateException( - "Encountered unrecognized credential entry (${slice.spec?.type}) for " + - "GetCredential request with single account" - ) - } - } - } else { - TODO("b/301206470 - to be implemented") - } - } else { - TODO("b/301206470 - to be implemented") - } -}
\ No newline at end of file diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt new file mode 100644 index 000000000000..f2f878e8ac2f --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0N + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.ui.mappers + +import com.android.credentialmanager.model.Request +import com.android.credentialmanager.CredentialSelectorUiState + +fun Request.Get.toGet(): CredentialSelectorUiState.Get { + // TODO: b/301206470 returning a hard coded state for MVP + if (true) return CredentialSelectorUiState.Get.SingleProviderSinglePassword + + return if (providers.size == 1) { + if (passwordEntries.size == 1) { + CredentialSelectorUiState.Get.SingleProviderSinglePassword + } else { + TODO() // b/301206470 - Implement other get flows + } + } else { + TODO() // b/301206470 - Implement other get flows + } +} diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/LoadingScreen.kt index 94a671efc393..b3ab0c4212db 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/LoadingScreen.kt @@ -16,17 +16,15 @@ package com.android.credentialmanager.ui.screens -import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.wear.compose.material.Text @Composable -fun MainScreen( +fun LoadingScreen( modifier: Modifier = Modifier ) { - Box(modifier = modifier, contentAlignment = Alignment.Center) { - Text("This is a placeholder for the main screen.") - } + // Don't display anything, assuming that there should be minimal latency + // to parse the Credential Manager intent and define the state of the + // app. If latency is big, then a "loading" screen should be displayed + // to the user. } diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt index f344ad0bd22d..85327833429d 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SingleAccountScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/SingleAccountScreen.kt @@ -16,7 +16,7 @@ @file:OptIn(ExperimentalHorologistApi::class) -package com.android.credentialmanager.ui.screens +package com.android.credentialmanager.ui.screens.single import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt index c8f871e46c83..c9b0230e74b9 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasskeyScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt @@ -16,7 +16,7 @@ @file:OptIn(ExperimentalHorologistApi::class) -package com.android.credentialmanager.ui.screens +package com.android.credentialmanager.ui.screens.single.passkey import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable @@ -27,6 +27,7 @@ import com.android.credentialmanager.R import com.android.credentialmanager.ui.components.AccountRow import com.android.credentialmanager.ui.components.DialogButtonsRow import com.android.credentialmanager.ui.components.SignInHeader +import com.android.credentialmanager.ui.screens.single.SingleAccountScreen import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.ScalingLazyColumnState import com.google.android.horologist.compose.layout.belowTimeTextPreview diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt index d863d3c68ceb..c885ec47024a 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/SinglePasswordScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt @@ -16,17 +16,26 @@ @file:OptIn(ExperimentalHorologistApi::class) -package com.android.credentialmanager.ui.screens +package com.android.credentialmanager.ui.screens.single.password +import android.util.Log +import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import com.android.credentialmanager.R +import com.android.credentialmanager.TAG +import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract import com.android.credentialmanager.ui.components.DialogButtonsRow import com.android.credentialmanager.ui.components.PasswordRow import com.android.credentialmanager.ui.components.SignInHeader +import com.android.credentialmanager.ui.screens.single.SingleAccountScreen import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.ScalingLazyColumnState import com.google.android.horologist.compose.layout.belowTimeTextPreview @@ -34,6 +43,61 @@ import com.google.android.horologist.compose.tools.WearPreview @Composable fun SinglePasswordScreen( + columnState: ScalingLazyColumnState, + onCloseApp: () -> Unit, + modifier: Modifier = Modifier, + viewModel: SinglePasswordScreenViewModel = + viewModel(factory = SinglePasswordScreenViewModel.Factory), +) { + viewModel.initialize() + + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + when (val state = uiState) { + SinglePasswordScreenUiState.Idle -> { + // TODO: b/301206470 implement latency version of the screen + } + + is SinglePasswordScreenUiState.Loaded -> { + val model = state.passwordUiModel + SinglePasswordScreen( + email = model.email, + onCancelClick = viewModel::onCancelClick, + onOKClick = viewModel::onOKClick, + columnState = columnState, + modifier = modifier + ) + } + + is SinglePasswordScreenUiState.PasswordSelected -> { + val launcher = rememberLauncherForActivityResult( + StartBalIntentSenderForResultContract() + ) { + viewModel.onPasswordInfoRetrieved(it.resultCode, it.data) + } + + SideEffect { + launcher.launch(state.intentSenderRequest) + } + } + + SinglePasswordScreenUiState.Cancel -> { + // TODO: b/301206470 implement navigation for when user taps cancel + } + + SinglePasswordScreenUiState.Error -> { + // TODO: b/301206470 implement navigation for when there is an error to load screen + } + + SinglePasswordScreenUiState.Completed -> { + Log.d(TAG, "Received signal to finish the activity.") + onCloseApp() + } + } +} + +@Composable +fun SinglePasswordScreen( email: String, onCancelClick: () -> Unit, onOKClick: () -> Unit, diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt new file mode 100644 index 000000000000..9b0662257ef6 --- /dev/null +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt @@ -0,0 +1,154 @@ +/* + * 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.0N + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.ui.screens.single.password + +import android.content.Intent +import android.credentials.ui.BaseDialogResult +import android.credentials.ui.ProviderPendingIntentResponse +import android.credentials.ui.UserSelectionDialogResult +import android.os.Bundle +import android.util.Log +import androidx.activity.result.IntentSenderRequest +import androidx.annotation.MainThread +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import com.android.credentialmanager.CredentialSelectorApp +import com.android.credentialmanager.IS_AUTO_SELECTED_KEY +import com.android.credentialmanager.TAG +import com.android.credentialmanager.model.Password +import com.android.credentialmanager.model.Request +import com.android.credentialmanager.repository.RequestRepository +import com.android.credentialmanager.ui.model.PasswordUiModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +class SinglePasswordScreenViewModel( + private val requestRepository: RequestRepository, +) : ViewModel() { + + private var initializeCalled = false + + private lateinit var requestGet: Request.Get + private lateinit var password: Password + + private val _uiState = + MutableStateFlow<SinglePasswordScreenUiState>(SinglePasswordScreenUiState.Idle) + val uiState: StateFlow<SinglePasswordScreenUiState> = _uiState + + @MainThread + fun initialize() { + if (initializeCalled) return + initializeCalled = true + + viewModelScope.launch { + val request = requestRepository.requests.first() + Log.d(TAG, "request: $request") + + if (request !is Request.Get) { + _uiState.value = SinglePasswordScreenUiState.Error + } else { + requestGet = request + if (requestGet.passwordEntries.isEmpty()) { + Log.d(TAG, "Empty passwordEntries") + _uiState.value = SinglePasswordScreenUiState.Error + } else { + password = requestGet.passwordEntries.first() + _uiState.value = SinglePasswordScreenUiState.Loaded( + PasswordUiModel( + email = password.passwordCredentialEntry.username.toString(), + ) + ) + } + } + } + } + + fun onCancelClick() { + _uiState.value = SinglePasswordScreenUiState.Cancel + } + + fun onOKClick() { + // TODO: b/301206470 move this code to shared module + val entryIntent = password.entry.frameworkExtrasIntent + entryIntent?.putExtra(IS_AUTO_SELECTED_KEY, false) + val intentSenderRequest = IntentSenderRequest.Builder( + pendingIntent = password.passwordCredentialEntry.pendingIntent + ).setFillInIntent(entryIntent).build() + + _uiState.value = SinglePasswordScreenUiState.PasswordSelected( + intentSenderRequest = intentSenderRequest + ) + } + + fun onPasswordInfoRetrieved( + resultCode: Int? = null, + resultData: Intent? = null, + ) { + // TODO: b/301206470 move this code to shared module + Log.d(TAG, "credential selected: {provider=${password.providerId}" + + ", key=${password.entry.key}, subkey=${password.entry.subkey}}") + + val userSelectionDialogResult = UserSelectionDialogResult( + requestGet.token, + password.providerId, + password.entry.key, + password.entry.subkey, + if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null + ) + val resultDataBundle = Bundle() + UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle) + requestGet.resultReceiver?.send( + BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION, + resultDataBundle + ) + + _uiState.value = SinglePasswordScreenUiState.Completed + } + + companion object { + val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun <T : ViewModel> create( + modelClass: Class<T>, + extras: CreationExtras + ): T { + val application = checkNotNull(extras[APPLICATION_KEY]) + + return SinglePasswordScreenViewModel( + requestRepository = (application as CredentialSelectorApp).requestRepository, + ) as T + } + } + } +} + +sealed class SinglePasswordScreenUiState { + data object Idle : SinglePasswordScreenUiState() + data class Loaded(val passwordUiModel: PasswordUiModel) : SinglePasswordScreenUiState() + data class PasswordSelected( + val intentSenderRequest: IntentSenderRequest + ) : SinglePasswordScreenUiState() + + data object Cancel : SinglePasswordScreenUiState() + data object Error : SinglePasswordScreenUiState() + data object Completed : SinglePasswordScreenUiState() +} diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index f9ea7735681f..49bd9d994088 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -345,6 +345,8 @@ <string name="accessibility_wifi_three_bars">Wifi three bars.</string> <!-- Content description of the WIFI signal when it is full for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_wifi_signal_full">Wifi signal full.</string> + <!-- Content description of the WIFI signal when the WIFI is connected using the signal from a different device owned by the user. For accessibility (not shown on the screen) [CHAR LIMIT=NONE] --> + <string name="accessibility_wifi_other_device">Connected to your device.</string> <!-- Content description of the Wi-Fi security type. This message indicates this is an open Wi-Fi (no password needed) [CHAR LIMIT=NONE] --> <string name="accessibility_wifi_security_type_none">Open network</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java index ee65ef4e92b6..ce466dfbf19c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java +++ b/packages/SettingsLib/src/com/android/settingslib/AccessibilityContentDescriptions.java @@ -50,6 +50,7 @@ public class AccessibilityContentDescriptions { }; public static final int WIFI_NO_CONNECTION = R.string.accessibility_no_wifi; + public static final int WIFI_OTHER_DEVICE_CONNECTION = R.string.accessibility_wifi_other_device; public static final int NO_CALLING = R.string.accessibility_no_calling; diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml index a95da3033eae..32048ca6344c 100644 --- a/packages/SettingsLib/tests/integ/AndroidManifest.xml +++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml @@ -41,7 +41,7 @@ </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.settingslib" + android:targetPackage="com.android.settingslib.tests.integ" android:label="Tests for SettingsLib"> </instrumentation> </manifest> diff --git a/packages/SettingsLib/tests/integ/AndroidTest.xml b/packages/SettingsLib/tests/integ/AndroidTest.xml index b5d09475269e..d0aee8822a72 100644 --- a/packages/SettingsLib/tests/integ/AndroidTest.xml +++ b/packages/SettingsLib/tests/integ/AndroidTest.xml @@ -21,7 +21,7 @@ <option name="test-suite-tag" value="apct" /> <option name="test-tag" value="SettingsLibTests" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="com.android.settingslib" /> + <option name="package" value="com.android.settingslib.tests.integ" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> <option name="hidden-api-checks" value="false"/> </test> diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 1c335445bf80..f6f75de1f24e 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -143,6 +143,7 @@ public class SecureSettings { Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, + Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED, @@ -243,6 +244,7 @@ public class SecureSettings { Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING, Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED, - Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED + Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED, + Settings.Secure.HUB_MODE_TUTORIAL_STATE }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 301fd6f87aa8..8d13f01f1d03 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -212,6 +212,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.VOLUME_HUSH_GESTURE, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put( Secure.ENABLED_NOTIFICATION_LISTENERS, @@ -389,5 +390,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.DND_CONFIGS_MIGRATED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.HUB_MODE_TUTORIAL_STATE, NON_NEGATIVE_INTEGER_VALIDATOR); } } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 5e7e044947fd..ee05f2d9101b 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -235,6 +235,9 @@ filegroup { srcs: [ /* Status bar fakes */ "tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt", + "tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt", + "tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt", + "tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt", "tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt", "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt", @@ -360,6 +363,8 @@ filegroup { "tests/src/com/android/systemui/qs/pipeline/data/**/*Test.kt", "tests/src/com/android/systemui/qs/pipeline/domain/**/*Test.kt", "tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt", + "tests/src/com/android/systemui/qs/tiles/base/**/*.kt", + "tests/src/com/android/systemui/qs/tiles/viewmodel/**/*.kt", ], path: "tests/src", } diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index af6fa86bacf4..0623d4a4b7fb 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -69,7 +69,7 @@ "exclude-annotation": "org.junit.Ignore" }, { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "include-filter": "android.permissionui.cts.CameraMicIndicatorsPermissionTest" @@ -131,5 +131,22 @@ } ] } + ], + // v2/sysui/suite/test-mapping-sysui-screenshot-test + "sysui-screenshot-test": [ + { + "name": "SystemUIGoogleScreenshotTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.Postsubmit" + } + ] + } ] } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt index 0cc259ab7015..a62c9840add1 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt @@ -151,7 +151,7 @@ internal fun Modifier.element( element.lastAlpha = alpha } } - .testTag(key.name) + .testTag(key.testTag) } private fun shouldDrawElement( @@ -167,7 +167,8 @@ private fun shouldDrawElement( state.fromScene == state.toScene || !layoutImpl.isTransitionReady(state) || state.fromScene !in element.sceneValues || - state.toScene !in element.sceneValues + state.toScene !in element.sceneValues || + !isSharedElementEnabled(layoutImpl, state, element.key) ) { return true } @@ -191,6 +192,26 @@ private fun shouldDrawElement( } } +private fun isSharedElementEnabled( + layoutImpl: SceneTransitionLayoutImpl, + transition: TransitionState.Transition, + element: ElementKey, +): Boolean { + val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene) + val sharedInFromScene = spec.transformations(element, transition.fromScene).shared + val sharedInToScene = spec.transformations(element, transition.toScene).shared + + // The sharedElement() transformation must either be null or be the same in both scenes. + if (sharedInFromScene != sharedInToScene) { + error( + "Different sharedElement() transformations matched $element (from=$sharedInFromScene " + + "to=$sharedInToScene)" + ) + } + + return sharedInFromScene?.enabled ?: true +} + /** * Chain the [com.android.compose.animation.scene.transformation.ModifierTransformation] applied * throughout the current transition, if any. @@ -213,7 +234,7 @@ private fun Modifier.modifierTransformations( return layoutImpl.transitions .transitionSpec(fromScene, state.toScene) - .transformations(element.key) + .transformations(element.key, scene.key) .modifier .fold(this) { modifier, transformation -> with(transformation) { @@ -407,17 +428,20 @@ private inline fun <T> computeValue( // The element is shared: interpolate between the value in fromScene and the value in toScene. // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared // elements follow the finger direction. - if (fromValues != null && toValues != null) { + val isSharedElement = fromValues != null && toValues != null + if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { return lerp( - sceneValue(fromValues), - sceneValue(toValues), + sceneValue(fromValues!!), + sceneValue(toValues!!), transitionProgress, ) } val transformation = transformation( - layoutImpl.transitions.transitionSpec(fromScene, toScene).transformations(element.key) + layoutImpl.transitions + .transitionSpec(fromScene, toScene) + .transformations(element.key, scene.key) ) // If there is no transformation explicitly associated to this element value, let's use // the value given by the system (like the current position and size given by the layout @@ -426,12 +450,21 @@ private inline fun <T> computeValue( // Get the transformed value, i.e. the target value at the beginning (for entering elements) or // end (for leaving elements) of the transition. + val sceneValues = + checkNotNull( + when { + isSharedElement && scene.key == fromScene -> fromValues + isSharedElement -> toValues + else -> fromValues ?: toValues + } + ) + val targetValue = transformation.transform( layoutImpl, scene, element, - fromValues ?: toValues!!, + sceneValues, state, idleValue, ) @@ -440,7 +473,7 @@ private inline fun <T> computeValue( val rangeProgress = transformation.range?.progress(transitionProgress) ?: transitionProgress // Interpolate between the value at rest and the value before entering/after leaving. - val isEntering = fromValues == null + val isEntering = scene.key == toScene return if (isEntering) { lerp(targetValue, idleValue, rangeProgress) } else { diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt new file mode 100644 index 000000000000..98dbb67d7c66 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt @@ -0,0 +1,37 @@ +/* + * 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.compose.animation.scene + +/** An interface to match one or more elements. */ +interface ElementMatcher { + /** Whether the element with key [key] in scene [scene] matches this matcher. */ + fun matches(key: ElementKey, scene: SceneKey): Boolean +} + +/** + * Returns an [ElementMatcher] that matches elements in [scene] also matching [this] + * [ElementMatcher]. + */ +fun ElementMatcher.inScene(scene: SceneKey): ElementMatcher { + val delegate = this + val matcherScene = scene + return object : ElementMatcher { + override fun matches(key: ElementKey, scene: SceneKey): Boolean { + return scene == matcherScene && delegate.matches(key, scene) + } + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt index f7ebe2fc6d34..b7acc48e2865 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt @@ -16,6 +16,8 @@ package com.android.compose.animation.scene +import androidx.annotation.VisibleForTesting + /** * A base class to create unique keys, associated to an [identity] that is used to check the * equality of two key instances. @@ -41,6 +43,7 @@ class SceneKey( name: String, identity: Any = Object(), ) : Key(name, identity) { + @VisibleForTesting val testTag: String = "scene:$name" /** The unique [ElementKey] identifying this scene's root element. */ val rootElementKey = ElementKey(name, identity) @@ -61,7 +64,9 @@ class ElementKey( */ val isBackground: Boolean = false, ) : Key(name, identity), ElementMatcher { - override fun matches(key: ElementKey): Boolean { + @VisibleForTesting val testTag: String = "element:$name" + + override fun matches(key: ElementKey, scene: SceneKey): Boolean { return key == this } @@ -73,7 +78,9 @@ class ElementKey( /** Matches any element whose [key identity][ElementKey.identity] matches [predicate]. */ fun withIdentity(predicate: (Any) -> Boolean): ElementMatcher { return object : ElementMatcher { - override fun matches(key: ElementKey): Boolean = predicate(key.identity) + override fun matches(key: ElementKey, scene: SceneKey): Boolean { + return predicate(key.identity) + } } } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt index b44c8efc7ee2..3985233bd197 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onPlaced +import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.IntSize import androidx.compose.ui.zIndex @@ -45,7 +46,9 @@ internal class Scene( @Composable fun Content(modifier: Modifier = Modifier) { - Box(modifier.zIndex(zIndex).onPlaced { size = it.size }) { scope.content() } + Box(modifier.zIndex(zIndex).onPlaced { size = it.size }.testTag(key.testTag)) { + scope.content() + } } override fun toString(): String { diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 350b9c2550c8..b3a7a8e9f874 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -158,26 +158,14 @@ internal class SceneTransitionLayoutImpl( BackHandler { onChangeScene(backScene) } } - Box( - Modifier.drawWithContent { - drawContent() - - // At this point, all scenes in scenesToCompose are fully laid out so they - // are marked as ready. This is necessary because the animation code needs - // to know the position and size of the elements in each scenes they are in, - // so [readyScenes] will be used to decide whether the transition is ready - // (see isTransitionReady() below). - // - // We can't do that in a DisposableEffect or SideEffect because those are - // run between composition and layout. LaunchedEffect could work and might - // be better, but it looks like launched effects run a frame later than this - // code so doing this here seems better for performance. - scenesToCompose.fastForEach { readyScenes[it.key] = true } - } - ) { + Box { scenesToCompose.fastForEach { scene -> val key = scene.key key(key) { + // Mark this scene as ready once it has been composed, laid out and + // drawn the first time. We have to do this in a LaunchedEffect here + // because DisposableEffect runs between composition and layout. + LaunchedEffect(key) { readyScenes[key] = true } DisposableEffect(key) { onDispose { readyScenes.remove(key) } } scene.Content( diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt index f4e39023edfe..75dcb2e44c13 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -16,6 +16,7 @@ package com.android.compose.animation.scene +import androidx.annotation.VisibleForTesting import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.snap import androidx.compose.ui.geometry.Offset @@ -28,6 +29,7 @@ import com.android.compose.animation.scene.transformation.ModifierTransformation import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.RangedPropertyTransformation import com.android.compose.animation.scene.transformation.ScaleSize +import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.Transformation import com.android.compose.animation.scene.transformation.Translate import com.android.compose.ui.util.fastForEach @@ -35,11 +37,12 @@ import com.android.compose.ui.util.fastMap /** The transitions configuration of a [SceneTransitionLayout]. */ class SceneTransitions( - private val transitionSpecs: List<TransitionSpec>, + @get:VisibleForTesting val transitionSpecs: List<TransitionSpec>, ) { private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>() - internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec { + @VisibleForTesting + fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec { return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) } } @@ -97,7 +100,8 @@ data class TransitionSpec( val transformations: List<Transformation>, val spec: AnimationSpec<Float>, ) { - private val cache = mutableMapOf<ElementKey, ElementTransformations>() + // TODO(b/302300957): Make sure this cache does not infinitely grow. + private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>() internal fun reverse(): TransitionSpec { return copy( @@ -107,12 +111,18 @@ data class TransitionSpec( ) } - internal fun transformations(element: ElementKey): ElementTransformations { - return cache.getOrPut(element) { computeTransformations(element) } + internal fun transformations(element: ElementKey, scene: SceneKey): ElementTransformations { + return cache + .getOrPut(element) { mutableMapOf() } + .getOrPut(scene) { computeTransformations(element, scene) } } /** Filter [transformations] to compute the [ElementTransformations] of [element]. */ - private fun computeTransformations(element: ElementKey): ElementTransformations { + private fun computeTransformations( + element: ElementKey, + scene: SceneKey, + ): ElementTransformations { + var shared: SharedElementTransformation? = null val modifier = mutableListOf<ModifierTransformation>() var offset: PropertyTransformation<Offset>? = null var size: PropertyTransformation<IntSize>? = null @@ -126,16 +136,16 @@ data class TransitionSpec( is Translate, is EdgeTranslate, is AnchoredTranslate -> { - throwIfNotNull(offset, element, property = "offset") + throwIfNotNull(offset, element, name = "offset") offset = root as PropertyTransformation<Offset> } is ScaleSize, is AnchoredSize -> { - throwIfNotNull(size, element, property = "size") + throwIfNotNull(size, element, name = "size") size = root as PropertyTransformation<IntSize> } is Fade -> { - throwIfNotNull(alpha, element, property = "alpha") + throwIfNotNull(alpha, element, name = "alpha") alpha = root as PropertyTransformation<Float> } is RangedPropertyTransformation -> onPropertyTransformation(root, current.delegate) @@ -143,32 +153,37 @@ data class TransitionSpec( } transformations.fastForEach { transformation -> - if (!transformation.matcher.matches(element)) { + if (!transformation.matcher.matches(element, scene)) { return@fastForEach } when (transformation) { + is SharedElementTransformation -> { + throwIfNotNull(shared, element, name = "shared") + shared = transformation + } is ModifierTransformation -> modifier.add(transformation) is PropertyTransformation<*> -> onPropertyTransformation(transformation) } } - return ElementTransformations(modifier, offset, size, alpha) + return ElementTransformations(shared, modifier, offset, size, alpha) } private fun throwIfNotNull( - previous: PropertyTransformation<*>?, + previous: Transformation?, element: ElementKey, - property: String, + name: String, ) { if (previous != null) { - error("$element has multiple transformations for its $property property") + error("$element has multiple $name transformations") } } } /** The transformations of an element during a transition. */ internal class ElementTransformations( + val shared: SharedElementTransformation?, val modifier: List<ModifierTransformation>, val offset: PropertyTransformation<Offset>?, val size: PropertyTransformation<IntSize>?, diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt index fb12b90d7d3e..49669775fedd 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -116,6 +116,14 @@ interface TransitionBuilder : PropertyTransformationBuilder { ) /** + * Configure the shared transition when [matcher] is shared between two scenes. + * + * @param enabled whether the matched element(s) should actually be shared in this transition. + * Defaults to true. + */ + fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true) + + /** * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and * using the given [shape]. * @@ -127,6 +135,13 @@ interface TransitionBuilder : PropertyTransformationBuilder { * the result. */ fun punchHole(matcher: ElementMatcher, bounds: ElementKey, shape: Shape = RectangleShape) + + /** + * Adds the transformations in [builder] but in reversed order. This allows you to partially + * reuse the definition of the transition from scene `Foo` to scene `Bar` inside the definition + * of the transition from scene `Bar` to scene `Foo`. + */ + fun reversed(builder: TransitionBuilder.() -> Unit) } @TransitionDsl @@ -179,12 +194,6 @@ interface PropertyTransformationBuilder { fun anchoredSize(matcher: ElementMatcher, anchor: ElementKey) } -/** An interface to match one or more elements. */ -interface ElementMatcher { - /** Whether the element with key [key] matches this matcher. */ - fun matches(key: ElementKey): Boolean -} - /** The edge of a [SceneTransitionLayout]. */ enum class Edge { Left, diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt index 48d5638e8b4e..f1c27178391c 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -31,6 +31,7 @@ import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.PunchHole import com.android.compose.animation.scene.transformation.RangedPropertyTransformation import com.android.compose.animation.scene.transformation.ScaleSize +import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.Transformation import com.android.compose.animation.scene.transformation.TransformationRange import com.android.compose.animation.scene.transformation.Translate @@ -80,6 +81,7 @@ internal class TransitionBuilderImpl : TransitionBuilder { override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow) private var range: TransformationRange? = null + private var reversed = false private val durationMillis: Int by lazy { val spec = spec if (spec !is DurationBasedAnimationSpec) { @@ -93,6 +95,12 @@ internal class TransitionBuilderImpl : TransitionBuilder { transformations.add(PunchHole(matcher, bounds, shape)) } + override fun reversed(builder: TransitionBuilder.() -> Unit) { + reversed = true + builder() + reversed = false + } + override fun fractionRange( start: Float?, end: Float?, @@ -103,6 +111,10 @@ internal class TransitionBuilderImpl : TransitionBuilder { range = null } + override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) { + transformations.add(SharedElementTransformation(matcher, enabled)) + } + override fun timestampRange( startMillis: Int?, endMillis: Int?, @@ -122,11 +134,20 @@ internal class TransitionBuilderImpl : TransitionBuilder { } private fun transformation(transformation: PropertyTransformation<*>) { - if (range != null) { - transformations.add(RangedPropertyTransformation(transformation, range!!)) - } else { - transformations.add(transformation) - } + val transformation = + if (range != null) { + RangedPropertyTransformation(transformation, range!!) + } else { + transformation + } + + transformations.add( + if (reversed) { + transformation.reverse() + } else { + transformation + } + ) } override fun fade(matcher: ElementMatcher) { diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt index ce6749da2711..a65025423aee 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -30,6 +30,14 @@ sealed interface Transformation { */ val matcher: ElementMatcher + /** + * The range during which the transformation is applied. If it is `null`, then the + * transformation will be applied throughout the whole scene transition. + */ + // TODO(b/240432457): Move this back to PropertyTransformation. + val range: TransformationRange? + get() = null + /* * Reverse this transformation. This is called when we use Transition(from = A, to = B) when * animating from B to A and there is no Transition(from = B, to = A) defined. @@ -37,6 +45,11 @@ sealed interface Transformation { fun reverse(): Transformation = this } +internal class SharedElementTransformation( + override val matcher: ElementMatcher, + internal val enabled: Boolean, +) : Transformation + /** A transformation that is applied on the element during the whole transition. */ internal interface ModifierTransformation : Transformation { /** Apply the transformation to [element]. */ @@ -53,13 +66,6 @@ internal interface ModifierTransformation : Transformation { /** A transformation that changes the value of an element property, like its size or offset. */ internal sealed interface PropertyTransformation<T> : Transformation { /** - * The range during which the transformation is applied. If it is `null`, then the - * transformation will be applied throughout the whole scene transition. - */ - val range: TransformationRange? - get() = null - - /** * Transform [value], i.e. the value of the transformed property without this transformation. */ // TODO(b/290184746): Figure out a public API for custom transformations that don't have access @@ -92,8 +98,7 @@ internal class RangedPropertyTransformation<T>( } /** The progress-based range of a [PropertyTransformation]. */ -data class TransformationRange -private constructor( +data class TransformationRange( val start: Float, val end: Float, ) { @@ -133,6 +138,6 @@ private constructor( } companion object { - private const val BoundUnspecified = Float.MIN_VALUE + const val BoundUnspecified = Float.MIN_VALUE } } diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt index 8bd654585f29..328866ea76ca 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt @@ -224,7 +224,7 @@ class SceneTransitionLayoutTest { // In scene A, the shared element SharedFoo() is at the top end of the layout and has a size // of 50.dp. - var sharedFoo = rule.onNodeWithTag(TestElements.Foo.name, useUnmergedTree = true) + var sharedFoo = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) sharedFoo.assertWidthIsEqualTo(50.dp) sharedFoo.assertHeightIsEqualTo(50.dp) sharedFoo.assertPositionInRootIsEqualTo( @@ -250,7 +250,7 @@ class SceneTransitionLayoutTest { // We need to use onAllNodesWithTag().onFirst() here given that shared elements are // composed and laid out in both scenes (but drawn only in one). - sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.name).onFirst() + sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst() // In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of // 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we @@ -284,7 +284,7 @@ class SceneTransitionLayoutTest { val expectedLeft = 0.dp val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress - sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.name).onFirst() + sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst() assertThat((layoutState.transitionState as TransitionState.Transition).progress) .isEqualTo(interpolatedProgress) sharedFoo.assertWidthIsEqualTo(expectedSize) diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt index 275149a05abf..268057fd2f2c 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt @@ -23,7 +23,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.SemanticsNodeInteractionCollection +import androidx.compose.ui.test.hasParent +import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag @DslMarker annotation class TransitionTestDsl @@ -59,8 +63,21 @@ interface TransitionTestBuilder { @TransitionTestDsl interface TransitionTestAssertionScope { - /** Assert on [element]. */ - fun onElement(element: ElementKey): SemanticsNodeInteraction + /** + * Assert on [element]. + * + * Note that presence/value assertions on the returned [SemanticsNodeInteraction] will fail if 0 + * or more than 1 elements matched [element]. If you need to assert on a shared element that + * will be present multiple times in the layout during transitions, either specify the [scene] + * in which you are matching or use [onSharedElement] instead. + */ + fun onElement(element: ElementKey, scene: SceneKey? = null): SemanticsNodeInteraction + + /** + * Assert on a shared [element]. This will throw if [element] is not shared and present only in + * one scene during a transition. + */ + fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection } /** @@ -73,20 +90,22 @@ fun ComposeContentTestRule.testTransition( toSceneContent: @Composable SceneScope.() -> Unit, transition: TransitionBuilder.() -> Unit, layoutModifier: Modifier = Modifier, + fromScene: SceneKey = TestScenes.SceneA, + toScene: SceneKey = TestScenes.SceneB, builder: TransitionTestBuilder.() -> Unit, ) { testTransition( - from = TestScenes.SceneA, - to = TestScenes.SceneB, + from = fromScene, + to = toScene, transitionLayout = { currentScene, onChangeScene -> SceneTransitionLayout( currentScene, onChangeScene, - transitions { from(TestScenes.SceneA, to = TestScenes.SceneB, transition) }, + transitions { from(fromScene, to = toScene, transition) }, layoutModifier.fillMaxSize(), ) { - scene(TestScenes.SceneA, content = fromSceneContent) - scene(TestScenes.SceneB, content = toSceneContent) + scene(fromScene, content = fromSceneContent) + scene(toScene, content = toSceneContent) } }, builder, @@ -111,8 +130,24 @@ fun ComposeContentTestRule.testTransition( val test = transitionTest(builder) val assertionScope = object : TransitionTestAssertionScope { - override fun onElement(element: ElementKey): SemanticsNodeInteraction { - return this@testTransition.onNodeWithTag(element.name) + override fun onElement( + element: ElementKey, + scene: SceneKey? + ): SemanticsNodeInteraction { + return if (scene == null) { + onNodeWithTag(element.testTag) + } else { + onNode(hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))) + } + } + + override fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection { + val interaction = onAllNodesWithTag(element.testTag) + val matches = interaction.fetchSemanticsNodes(atLeastOneRootRequired = false).size + if (matches < 2) { + error("Element $element is not shared ($matches matches)") + } + return interaction } } diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt new file mode 100644 index 000000000000..fa94b25028a2 --- /dev/null +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt @@ -0,0 +1,190 @@ +/* + * 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.compose.animation.scene + +import androidx.compose.animation.core.SpringSpec +import androidx.compose.animation.core.TweenSpec +import androidx.compose.animation.core.tween +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.transformation.Transformation +import com.android.compose.animation.scene.transformation.TransformationRange +import com.google.common.truth.Correspondence +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TransitionDslTest { + @Test + fun emptyTransitions() { + val transitions = transitions {} + assertThat(transitions.transitionSpecs).isEmpty() + } + + @Test + fun manyTransitions() { + val transitions = transitions { + from(TestScenes.SceneA, to = TestScenes.SceneB) + from(TestScenes.SceneB, to = TestScenes.SceneC) + from(TestScenes.SceneC, to = TestScenes.SceneA) + } + assertThat(transitions.transitionSpecs).hasSize(3) + } + + @Test + fun toFromBuilders() { + val transitions = transitions { + from(TestScenes.SceneA, to = TestScenes.SceneB) + from(TestScenes.SceneB) + to(TestScenes.SceneC) + } + + assertThat(transitions.transitionSpecs) + .comparingElementsUsing( + Correspondence.transforming<TransitionSpec, Pair<SceneKey?, SceneKey?>>( + { it?.from to it?.to }, + "has (from, to) equal to" + ) + ) + .containsExactly( + TestScenes.SceneA to TestScenes.SceneB, + TestScenes.SceneB to null, + null to TestScenes.SceneC, + ) + } + + @Test + fun defaultTransitionSpec() { + val transitions = transitions { from(TestScenes.SceneA, to = TestScenes.SceneB) } + val transition = transitions.transitionSpecs.single() + assertThat(transition.spec).isInstanceOf(SpringSpec::class.java) + } + + @Test + fun customTransitionSpec() { + val transitions = transitions { + from(TestScenes.SceneA, to = TestScenes.SceneB) { spec = tween(durationMillis = 42) } + } + val transition = transitions.transitionSpecs.single() + assertThat(transition.spec).isInstanceOf(TweenSpec::class.java) + assertThat((transition.spec as TweenSpec).durationMillis).isEqualTo(42) + } + + @Test + fun defaultRange() { + val transitions = transitions { + from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) } + } + + val transition = transitions.transitionSpecs.single() + assertThat(transition.transformations.size).isEqualTo(1) + assertThat(transition.transformations.single().range).isEqualTo(null) + } + + @Test + fun fractionRange() { + val transitions = transitions { + from(TestScenes.SceneA, to = TestScenes.SceneB) { + fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) } + fractionRange(start = 0.2f) { fade(TestElements.Foo) } + fractionRange(end = 0.9f) { fade(TestElements.Foo) } + } + } + + val transition = transitions.transitionSpecs.single() + assertThat(transition.transformations) + .comparingElementsUsing(TRANSFORMATION_RANGE) + .containsExactly( + TransformationRange(start = 0.1f, end = 0.8f), + TransformationRange(start = 0.2f, end = TransformationRange.BoundUnspecified), + TransformationRange(start = TransformationRange.BoundUnspecified, end = 0.9f), + ) + } + + @Test + fun timestampRange() { + val transitions = transitions { + from(TestScenes.SceneA, to = TestScenes.SceneB) { + spec = tween(500) + + timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) } + timestampRange(startMillis = 200) { fade(TestElements.Foo) } + timestampRange(endMillis = 400) { fade(TestElements.Foo) } + } + } + + val transition = transitions.transitionSpecs.single() + assertThat(transition.transformations) + .comparingElementsUsing(TRANSFORMATION_RANGE) + .containsExactly( + TransformationRange(start = 100 / 500f, end = 300 / 500f), + TransformationRange(start = 200 / 500f, end = TransformationRange.BoundUnspecified), + TransformationRange(start = TransformationRange.BoundUnspecified, end = 400 / 500f), + ) + } + + @Test + fun reversed() { + val transitions = transitions { + from(TestScenes.SceneA, to = TestScenes.SceneB) { + spec = tween(500) + reversed { + fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) } + timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) } + } + } + } + + val transition = transitions.transitionSpecs.single() + assertThat(transition.transformations) + .comparingElementsUsing(TRANSFORMATION_RANGE) + .containsExactly( + TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f), + TransformationRange(start = 1f - 300 / 500f, end = 1f - 100 / 500f), + ) + } + + @Test + fun defaultReversed() { + val transitions = transitions { + from(TestScenes.SceneA, to = TestScenes.SceneB) { + spec = tween(500) + fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) } + timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) } + } + } + + // Fetch the transition from B to A, which will automatically reverse the transition from A + // to B we defined. + val transition = + transitions.transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA) + assertThat(transition.transformations) + .comparingElementsUsing(TRANSFORMATION_RANGE) + .containsExactly( + TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f), + TransformationRange(start = 1f - 300 / 500f, end = 1f - 100 / 500f), + ) + } + + companion object { + private val TRANSFORMATION_RANGE = + Correspondence.transforming<Transformation, TransformationRange?>( + { it?.range }, + "has range equal to" + ) + } +} diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt new file mode 100644 index 000000000000..2af363860272 --- /dev/null +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt @@ -0,0 +1,157 @@ +/* + * 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.compose.animation.scene.transformation + +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.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.assertPositionInRootIsEqualTo +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.Edge +import com.android.compose.animation.scene.TestElements +import com.android.compose.animation.scene.TestScenes +import com.android.compose.animation.scene.inScene +import com.android.compose.animation.scene.testTransition +import com.android.compose.modifiers.size +import com.android.compose.test.assertSizeIsEqualTo +import com.android.compose.test.onEach +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SharedElementTest { + @get:Rule val rule = createComposeRule() + + @Test + fun testSharedElement() { + rule.testTransition( + fromSceneContent = { + // Foo is at (10, 50) with a size of (20, 80). + Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo).size(20.dp, 80.dp)) + }, + toSceneContent = { + // Foo is at (50, 70) with a size of (10, 40). + Box(Modifier.offset(50.dp, 70.dp).element(TestElements.Foo).size(10.dp, 40.dp)) + }, + transition = { + spec = tween(16 * 4, easing = LinearEasing) + // Elements should be shared by default. + } + ) { + before { + onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) + onElement(TestElements.Foo).assertSizeIsEqualTo(20.dp, 80.dp) + } + at(0) { + onSharedElement(TestElements.Foo).onEach { + assertPositionInRootIsEqualTo(10.dp, 50.dp) + assertSizeIsEqualTo(20.dp, 80.dp) + } + } + at(16) { + onSharedElement(TestElements.Foo).onEach { + assertPositionInRootIsEqualTo(20.dp, 55.dp) + assertSizeIsEqualTo(17.5.dp, 70.dp) + } + } + at(32) { + onSharedElement(TestElements.Foo).onEach { + assertPositionInRootIsEqualTo(30.dp, 60.dp) + assertSizeIsEqualTo(15.dp, 60.dp) + } + } + at(48) { + onSharedElement(TestElements.Foo).onEach { + assertPositionInRootIsEqualTo(40.dp, 65.dp) + assertSizeIsEqualTo(12.5.dp, 50.dp) + } + } + after { + onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 70.dp) + onElement(TestElements.Foo).assertSizeIsEqualTo(10.dp, 40.dp) + } + } + } + + @Test + fun testSharedElementDisabled() { + rule.testTransition( + fromScene = TestScenes.SceneA, + toScene = TestScenes.SceneB, + // The full layout is 100x100. + layoutModifier = Modifier.size(100.dp), + fromSceneContent = { + Box(Modifier.fillMaxSize()) { + // Foo is at (10, 50). + Box(Modifier.offset(10.dp, 50.dp).element(TestElements.Foo)) + } + }, + toSceneContent = { + Box(Modifier.fillMaxSize()) { + // Foo is at (50, 60). + Box(Modifier.offset(50.dp, 60.dp).element(TestElements.Foo)) + } + }, + transition = { + spec = tween(16 * 4, easing = LinearEasing) + + // Disable the shared element animation. + sharedElement(TestElements.Foo, enabled = false) + + // In SceneA, Foo leaves to the left edge. + translate(TestElements.Foo.inScene(TestScenes.SceneA), Edge.Left) + + // In SceneB, Foo comes from the bottom edge. + translate(TestElements.Foo.inScene(TestScenes.SceneB), Edge.Bottom) + }, + ) { + before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) } + at(0) { + onElement(TestElements.Foo, scene = TestScenes.SceneA) + .assertPositionInRootIsEqualTo(10.dp, 50.dp) + onElement(TestElements.Foo, scene = TestScenes.SceneB) + .assertPositionInRootIsEqualTo(50.dp, 100.dp) + } + at(16) { + onElement(TestElements.Foo, scene = TestScenes.SceneA) + .assertPositionInRootIsEqualTo(7.5.dp, 50.dp) + onElement(TestElements.Foo, scene = TestScenes.SceneB) + .assertPositionInRootIsEqualTo(50.dp, 90.dp) + } + at(32) { + onElement(TestElements.Foo, scene = TestScenes.SceneA) + .assertPositionInRootIsEqualTo(5.dp, 50.dp) + onElement(TestElements.Foo, scene = TestScenes.SceneB) + .assertPositionInRootIsEqualTo(50.dp, 80.dp) + } + at(48) { + onElement(TestElements.Foo, scene = TestScenes.SceneA) + .assertPositionInRootIsEqualTo(2.5.dp, 50.dp) + onElement(TestElements.Foo, scene = TestScenes.SceneB) + .assertPositionInRootIsEqualTo(50.dp, 70.dp) + } + after { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 60.dp) } + } + } +} diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt new file mode 100644 index 000000000000..d6f64bfe4974 --- /dev/null +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.test + +import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.SemanticsNodeInteractionCollection + +/** Assert [assert] on each element of [this] [SemanticsNodeInteractionCollection]. */ +fun SemanticsNodeInteractionCollection.onEach(assert: SemanticsNodeInteraction.() -> Unit) { + for (i in 0 until this.fetchSemanticsNodes().size) { + get(i).assert() + } +} 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 8e3400872a9a..519c0a9c4c7c 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 @@ -39,7 +39,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.ElementKey @@ -166,11 +168,18 @@ fun SceneScope.ExpandedShadeHeader( modifier = Modifier.align(Alignment.CenterVertically) // use graphicsLayer instead of Modifier.scale to anchor transform to - // top left corner + // the (start, top) corner .graphicsLayer( scaleX = 2.57f, scaleY = 2.57f, - transformOrigin = TransformOrigin(0f, 0.5f) + transformOrigin = + TransformOrigin( + when (LocalLayoutDirection.current) { + LayoutDirection.Ltr -> 0f + LayoutDirection.Rtl -> 1f + }, + 0.5f + ) ), ) Spacer(modifier = Modifier.weight(1f)) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index c41dc53fdc6b..cb76ad7c77fe 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -507,9 +507,10 @@ open class ClockRegistry( } } - private var isVerifying = AtomicBoolean(false) + private var isQueued = AtomicBoolean(false) fun verifyLoadedProviders() { - val shouldSchedule = isVerifying.compareAndSet(false, true) + Log.i(TAG, Thread.currentThread().getStackTrace().toString()) + val shouldSchedule = isQueued.compareAndSet(false, true) if (!shouldSchedule) { logger.tryLog( TAG, @@ -521,48 +522,54 @@ open class ClockRegistry( } scope.launch(bgDispatcher) { - if (keepAllLoaded) { - logger.tryLog( - TAG, - LogLevel.INFO, - {}, - { "verifyLoadedProviders: keepAllLoaded=true" } - ) - // Enforce that all plugins are loaded if requested - for ((_, info) in availableClocks) { - info.manager?.loadPlugin() + // TODO(b/267372164): Use better threading approach when converting to flows + synchronized(availableClocks) { + isQueued.set(false) + if (keepAllLoaded) { + logger.tryLog( + TAG, + LogLevel.INFO, + {}, + { "verifyLoadedProviders: keepAllLoaded=true" } + ) + // Enforce that all plugins are loaded if requested + for ((_, info) in availableClocks) { + info.manager?.loadPlugin() + } + return@launch + } + + val currentClock = availableClocks[currentClockId] + if (currentClock == null) { + logger.tryLog( + TAG, + LogLevel.INFO, + {}, + { "verifyLoadedProviders: currentClock=null" } + ) + // Current Clock missing, load no plugins and use default + for ((_, info) in availableClocks) { + info.manager?.unloadPlugin() + } + return@launch } - isVerifying.set(false) - return@launch - } - val currentClock = availableClocks[currentClockId] - if (currentClock == null) { logger.tryLog( TAG, LogLevel.INFO, {}, - { "verifyLoadedProviders: currentClock=null" } + { "verifyLoadedProviders: load currentClock" } ) - // Current Clock missing, load no plugins and use default - for ((_, info) in availableClocks) { - info.manager?.unloadPlugin() - } - isVerifying.set(false) - return@launch - } - - logger.tryLog(TAG, LogLevel.INFO, {}, { "verifyLoadedProviders: load currentClock" }) - val currentManager = currentClock.manager - currentManager?.loadPlugin() + val currentManager = currentClock.manager + currentManager?.loadPlugin() - for ((_, info) in availableClocks) { - val manager = info.manager - if (manager != null && currentManager != manager) { - manager.unloadPlugin() + for ((_, info) in availableClocks) { + val manager = info.manager + if (manager != null && currentManager != manager) { + manager.unloadPlugin() + } } } - isVerifying.set(false) } } diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar.xml b/packages/SystemUI/res/drawable/volume_row_seekbar.xml index 7ce1ba3be7f6..d7d75d4304d0 100644 --- a/packages/SystemUI/res/drawable/volume_row_seekbar.xml +++ b/packages/SystemUI/res/drawable/volume_row_seekbar.xml @@ -20,17 +20,14 @@ <layer-list xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:paddingMode="stack" > + <!-- The groove used for indicating max volume !--> <item android:id="@android:id/background" android:gravity="center_vertical|fill_horizontal"> - <inset - android:insetLeft="@dimen/rounded_slider_track_inset" - android:insetRight="@dimen/rounded_slider_track_inset" > - <shape> - <size android:height="@dimen/volume_dialog_track_width" /> - <corners android:radius="@dimen/volume_dialog_track_corner_radius" /> - <solid android:color="?androidprv:attr/colorAccentSecondaryVariant" /> - </shape> - </inset> + <shape> + <size android:height="@dimen/volume_dialog_track_width" /> + <corners android:radius="@dimen/volume_dialog_panel_width_half" /> + <solid android:color="?androidprv:attr/materialColorOutlineVariant" /> + </shape> </item> <item android:id="@android:id/progress" android:gravity="center_vertical|fill_horizontal"> diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml index 569dd4cf9252..a51c55ee965f 100644 --- a/packages/SystemUI/res/layout/connected_display_dialog.xml +++ b/packages/SystemUI/res/layout/connected_display_dialog.xml @@ -52,7 +52,7 @@ style="@style/Widget.Dialog.Button.BorderButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/cancel" /> + android:text="@string/dismiss_dialog" /> <Space android:layout_width="0dp" @@ -64,6 +64,6 @@ style="@style/Widget.Dialog.Button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/enable_display" /> + android:text="@string/mirror_display" /> </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml index 37b8ae0f40c4..c70f8e2b1c07 100644 --- a/packages/SystemUI/res/layout/notif_half_shelf.xml +++ b/packages/SystemUI/res/layout/notif_half_shelf.xml @@ -22,8 +22,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_horizontal|bottom" android:paddingStart="4dp" - android:paddingEnd="4dp" -> + android:paddingEnd="4dp"> <LinearLayout android:id="@+id/half_shelf" @@ -82,11 +81,21 @@ android:theme="@style/MainSwitch.Settingslib"/> </com.android.systemui.statusbar.notification.row.AppControlView> - <!-- ChannelRows get added dynamically --> - + <ScrollView + android:layout_width="match_parent" + android:layout_height="@dimen/notification_blocker_channel_list_height" + android:clipToPadding="false"> + <LinearLayout + android:id="@+id/scrollView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + <!-- ChannelRows get added dynamically --> + </LinearLayout> + </ScrollView> </com.android.systemui.statusbar.notification.row.ChannelEditorListView> - <RelativeLayout + <LinearLayout android:id="@+id/bottom_actions" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -98,25 +107,23 @@ android:text="@string/see_more_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentStart="true" - android:layout_centerVertical="true" - android:gravity="start|center_vertical" android:minWidth="@dimen/notification_importance_toggle_size" android:minHeight="@dimen/notification_importance_toggle_size" android:maxWidth="200dp" style="@style/Widget.Dialog.Button"/> + <Space + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" /> <TextView android:id="@+id/done_button" android:text="@string/inline_ok_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:gravity="end|center_vertical" android:maxWidth="125dp" android:minWidth="@dimen/notification_importance_toggle_size" android:minHeight="@dimen/notification_importance_toggle_size" - android:layout_alignParentEnd="true" style="@style/Widget.Dialog.Button"/> - </RelativeLayout> + </LinearLayout> </LinearLayout> </FrameLayout> diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index 85fb3ac577bc..587caaf3ecf3 100644 --- a/packages/SystemUI/res/values-land/config.xml +++ b/packages/SystemUI/res/values-land/config.xml @@ -44,6 +44,6 @@ <!-- Whether to force split shade. For now, this value has effect only when flag lockscreen.enable_landscape is enabled. - TODO (b/293290851) - change this comment/resource when flag is enabled --> + TODO (b/293252410) - change this comment/resource when flag is enabled --> <bool name="force_config_use_split_notification_shade">true</bool> </resources> diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 259b9adf0209..cfb40171cfc1 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -89,6 +89,9 @@ <dimen name="global_actions_button_size">72dp</dimen> <dimen name="global_actions_button_padding">26dp</dimen> + <!-- scroll view the size of 2 channel rows --> + <dimen name="notification_blocker_channel_list_height">128dp</dimen> + <dimen name="keyguard_indication_margin_bottom">8dp</dimen> <dimen name="lock_icon_margin_bottom">24dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index e63229aea70b..fc6d20e11d3b 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -38,6 +38,6 @@ <!-- Whether to force split shade. For now, this value has effect only when flag lockscreen.enable_landscape is enabled. - TODO (b/293290851) - change this comment/resource when flag is enabled --> + TODO (b/293252410) - change this comment/resource when flag is enabled --> <bool name="force_config_use_split_notification_shade">false</bool> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index b6bca65b8174..18f24ec6293e 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -88,7 +88,7 @@ <!-- The default tiles to display in QuickSettings --> <string name="quick_settings_tiles_default" translatable="false"> - internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.SafetyCenterQsTileService) + internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,custom(com.android.permissioncontroller/.permission.service.v33.SafetyCenterQsTileService) </string> <!-- The class path of the Safety Quick Settings Tile --> @@ -604,7 +604,7 @@ <!-- Whether to force split shade. For now, this value has effect only when flag lockscreen.enable_landscape is enabled. - TODO (b/293290851) - change this comment/resource when flag is enabled --> + TODO (b/293252410) - change this comment/resource when flag is enabled --> <bool name="force_config_use_split_notification_shade">false</bool> <!-- Whether we use large screen shade header which takes only one row compared to QS header --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 88726af39c25..0ee5da22a31b 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -313,6 +313,8 @@ <!-- The space around a notification menu item --> <dimen name="notification_menu_icon_padding">20dp</dimen> + <!-- scroll view the size of 3 channel rows --> + <dimen name="notification_blocker_channel_list_height">192dp</dimen> <!-- The vertical space around the buttons in the inline settings --> <dimen name="notification_guts_button_spacing">12dp</dimen> @@ -545,7 +547,7 @@ <!-- (volume_dialog_panel_width - rounded_slider_icon_size) / 2 --> <dimen name="volume_slider_icon_inset">11dp</dimen> - <dimen name="volume_dialog_track_width">4dp</dimen> + <dimen name="volume_dialog_track_width">40dp</dimen> <dimen name="volume_dialog_track_corner_radius">2dp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 21696fefb80f..6cdd15e637bd 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -233,6 +233,11 @@ <item type="id" name="pin_pad"/> <!-- + Tag used to store pending intent registration listeners in NotificationTemplateViewWrapper + --> + <item type="id" name="pending_intent_listener_tag" /> + + <!-- Used to tag views programmatically added to the smartspace area so they can be more easily removed later. --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 5860806c6aec..a2637d5e55c3 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3214,9 +3214,10 @@ <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]--> <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string> - <!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]--> - <string name="enable_display">Enable display</string> + <string name="mirror_display">Mirror display</string> + <!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]--> + <string name="dismiss_dialog">Dismiss</string> <!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] --> <string name="privacy_dialog_title">Microphone & Camera</string> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt index fb580ca54aff..5344dcc51eda 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt @@ -18,7 +18,7 @@ package com.android.systemui.biometrics.shared.model import android.hardware.biometrics.BiometricAuthenticator -/** Shadows [BiometricAuthenticator.Modality] for Kotlin use within SysUI. */ +/** Shadows [BiometricAuthenticator.Modality] for Kotlin use within SysUI and Settings. */ enum class BiometricModality { None, Fingerprint, diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt index 39689ec16189..fdac37b7a2c8 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.biometrics.shared.model /** diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt new file mode 100644 index 000000000000..a2b119833474 --- /dev/null +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensor.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.shared.model + +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal + +/** Fingerprint sensor property. Represents [FingerprintSensorPropertiesInternal]. */ +data class FingerprintSensor( + val sensorId: Int, + val sensorStrength: SensorStrength, + val maxEnrollmentsPerUser: Int, + val sensorType: FingerprintSensorType +) + +/** Convert [FingerprintSensorPropertiesInternal] to corresponding [FingerprintSensor] */ +fun FingerprintSensorPropertiesInternal.toFingerprintSensor(): FingerprintSensor { + val sensorStrength: SensorStrength = this.sensorStrength.toSensorStrength() + val sensorType: FingerprintSensorType = this.sensorType.toSensorType() + return FingerprintSensor(this.sensorId, sensorStrength, this.maxEnrollmentsPerUser, sensorType) +} diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt index a9b4fe87cc47..65c5a49b1135 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.biometrics.shared.model import android.graphics.Rect diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 4b14d3cff718..bf688698de5f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -37,9 +37,6 @@ import java.util.StringJoiner; * Various shared constants between Launcher and SysUI as part of quickstep */ public class QuickStepContract { - // Fully qualified name of the Launcher activity. - public static final String LAUNCHER_ACTIVITY_CLASS_NAME = - "com.google.android.apps.nexuslauncher.NexusLauncherActivity"; public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy"; public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation"; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl index cf83f62af550..31d78b9a8656 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/ISysuiUnlockAnimationController.aidl @@ -24,7 +24,8 @@ import com.android.systemui.shared.system.smartspace.SmartspaceState; interface ISysuiUnlockAnimationController { // Provides an implementation of the LauncherUnlockAnimationController to System UI, so that // SysUI can use it to control the unlock animation in the launcher window. - oneway void setLauncherUnlockController(ILauncherUnlockAnimationController callback); + oneway void setLauncherUnlockController( + String activityClass, ILauncherUnlockAnimationController callback); // Called by Launcher whenever anything happens to change the state of its smartspace. System UI // proactively saves this and uses it to perform the unlock animation without needing to make a diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt index 96a974d5dd80..7b2e1afda700 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt @@ -19,6 +19,12 @@ package com.android.systemui.util import android.os.Trace import android.os.TraceNameSupplier import java.util.concurrent.atomic.AtomicInteger +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async /** * Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection] @@ -85,5 +91,18 @@ class TraceUtils { Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, cookie) } } + + /** + * Convenience method to avoid one indentation level when we want to add a trace when + * launching a coroutine + */ + fun <T> CoroutineScope.tracedAsync( + method: String, + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend () -> T + ): Deferred<T> { + return async(context, start) { traceAsync(method) { block() } } + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index ba8e42752586..f6a0563ebf94 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -184,6 +184,10 @@ public class KeyguardClockSwitch extends RelativeLayout { } } + public boolean getSplitShadeCentered() { + return mSplitShadeCentered; + } + @Override protected void onFinishInflate() { super.onFinishInflate(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 29414ea7f4c0..5646abefcef1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -234,6 +234,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } } + public KeyguardClockSwitch getView() { + return mView; + } + private void hideSliceViewAndNotificationIconContainer() { View ksv = mView.findViewById(R.id.keyguard_slice_view); ksv.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 625c1de0134d..51dafac7b421 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -18,6 +18,7 @@ package com.android.keyguard; import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; + import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY; @@ -68,8 +69,6 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.keyguard.dagger.KeyguardBouncerScope; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Gefingerpoken; -import com.android.systemui.res.R; -import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor; import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate; import com.android.systemui.biometrics.SideFpsController; import com.android.systemui.biometrics.SideFpsUiRequestSource; @@ -77,6 +76,7 @@ import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; @@ -84,6 +84,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -163,7 +164,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } mCurrentUser = KeyguardUpdateMonitor.getCurrentUser(); showPrimarySecurityScreen(false); - reinflateViewFlipper((l) -> {}); + if (mCurrentSecurityMode != SecurityMode.SimPin + && mCurrentSecurityMode != SecurityMode.SimPuk) { + reinflateViewFlipper((l) -> {}); + } } }; @@ -417,7 +421,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } }; private final UserInteractor mUserInteractor; - private final Provider<AuthenticationInteractor> mAuthenticationInteractor; + private final Provider<DeviceEntryInteractor> mDeviceEntryInteractor; private final Provider<JavaAdapter> mJavaAdapter; private final DeviceProvisionedController mDeviceProvisionedController; private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor; @@ -454,7 +458,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate, KeyguardTransitionInteractor keyguardTransitionInteractor, Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor, - Provider<AuthenticationInteractor> authenticationInteractor + Provider<DeviceEntryInteractor> deviceEntryInteractor ) { super(view); view.setAccessibilityDelegate(faceAuthAccessibilityDelegate); @@ -484,7 +488,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; mBouncerMessageInteractor = bouncerMessageInteractor; mUserInteractor = userInteractor; - mAuthenticationInteractor = authenticationInteractor; + mDeviceEntryInteractor = deviceEntryInteractor; mJavaAdapter = javaAdapter; mKeyguardTransitionInteractor = keyguardTransitionInteractor; mDeviceProvisionedController = deviceProvisionedController; @@ -516,9 +520,9 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard // When the scene framework says that the lockscreen has been dismissed, dismiss the // keyguard here, revealing the underlying app or launcher: mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow( - mAuthenticationInteractor.get().isLockscreenDismissed(), - isLockscreenDismissed -> { - if (isLockscreenDismissed) { + mDeviceEntryInteractor.get().isDeviceEntered(), + isDeviceEntered -> { + if (isDeviceEntered) { final int selectedUserId = mUserInteractor.getSelectedUserId(); showNextSecurityScreenOrFinish( /* authenticated= */ true, @@ -1078,15 +1082,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard * one side). */ private boolean canUseOneHandedBouncer() { - switch(mCurrentSecurityMode) { - case PIN: - case Pattern: - case SimPin: - case SimPuk: - return getResources().getBoolean(R.bool.can_use_one_handed_bouncer); - default: - return false; - } + return switch (mCurrentSecurityMode) { + case PIN, Pattern, SimPin, SimPuk -> getResources().getBoolean( + R.bool.can_use_one_handed_bouncer); + default -> false; + }; } private boolean canDisplayUserSwitcher() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index d8486029a903..f9cc03eea288 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -18,6 +18,7 @@ package com.android.keyguard; import static java.util.Collections.emptySet; +import android.animation.LayoutTransition; import android.content.Context; import android.graphics.Canvas; import android.os.Build; @@ -78,6 +79,14 @@ public class KeyguardStatusView extends GridLayout { mKeyguardSlice = findViewById(R.id.keyguard_slice_view); mMediaHostContainer = findViewById(R.id.status_view_media_container); + if (mMediaHostContainer != null) { + LayoutTransition mediaLayoutTransition = new LayoutTransition(); + ((ViewGroup) mMediaHostContainer).setLayoutTransition(mediaLayoutTransition); + mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_APPEARING); + mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); + mediaLayoutTransition.disableTransitionType(LayoutTransition.APPEARING); + mediaLayoutTransition.disableTransitionType(LayoutTransition.DISAPPEARING); + } updateDark(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 8d0d299e56c2..931ba6d97c58 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -23,6 +23,7 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CL import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.animation.Animator; +import android.animation.LayoutTransition; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.content.res.Configuration; @@ -101,6 +102,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final Rect mClipBounds = new Rect(); private final KeyguardInteractor mKeyguardInteractor; + private Boolean mSplitShadeEnabled = false; private Boolean mStatusViewCentered = true; private DumpManager mDumpManager; @@ -150,6 +152,48 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV @Override public void onInit() { mKeyguardClockSwitchController.init(); + final View mediaHostContainer = mView.findViewById(R.id.status_view_media_container); + if (mediaHostContainer != null) { + mKeyguardClockSwitchController.getView().addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + if (!mSplitShadeEnabled + || mKeyguardClockSwitchController.getView().getSplitShadeCentered() + // Note: isKeyguardVisible() returns false after Launcher -> AOD. + || !mKeyguardUpdateMonitor.isKeyguardVisible()) { + return; + } + + int oldHeight = oldBottom - oldTop; + if (v.getHeight() == oldHeight) return; + + if (mediaHostContainer.getVisibility() != View.VISIBLE + // If the media is appearing, also don't do the transition. + || mediaHostContainer.getHeight() == 0) { + return; + } + + final LayoutTransition mediaLayoutTransition = + ((ViewGroup) mediaHostContainer).getLayoutTransition(); + if (mediaLayoutTransition == null) return; + + mediaLayoutTransition.enableTransitionType(LayoutTransition.CHANGING); + }); + + mediaHostContainer.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + final LayoutTransition mediaLayoutTransition = + ((ViewGroup) mediaHostContainer).getLayoutTransition(); + if (mediaLayoutTransition == null) return; + if (!mediaLayoutTransition.isTransitionTypeEnabled( + LayoutTransition.CHANGING)) { + return; + } + // Note: when this is called, the LayoutTransition is already been set up. + // Disables the LayoutTransition until it's explicitly enabled again. + mediaLayoutTransition.disableTransitionType(LayoutTransition.CHANGING); + } + ); + } mDumpManager.registerDumpable(getInstanceName(), this); if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { @@ -385,6 +429,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV */ public void setSplitShadeEnabled(boolean enabled) { mKeyguardClockSwitchController.setSplitShadeEnabled(enabled); + mSplitShadeEnabled = enabled; } /** diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 165c4bb018d3..a81069a1f7db 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -424,11 +424,11 @@ public class LockIconViewController implements Dumpable { private void updateConfiguration() { WindowManager windowManager = mContext.getSystemService(WindowManager.class); Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); - WindowInsets insets = windowManager.getCurrentWindowMetrics().getWindowInsets(); mWidthPixels = bounds.right; if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { // Assumed to be initially neglected as there are no left or right insets in portrait // However, on landscape, these insets need to included when calculating the midpoint + WindowInsets insets = windowManager.getCurrentWindowMetrics().getWindowInsets(); mWidthPixels -= insets.getSystemWindowInsetLeft() + insets.getSystemWindowInsetRight(); } mHeightPixels = bounds.bottom; diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 954129ef78c8..22bd20767e14 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -18,8 +18,8 @@ package com.android.systemui; import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_X; import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat; - import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS; +import static com.android.systemui.flags.Flags.SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import android.animation.Animator; @@ -482,7 +482,14 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { boolean wasRemoved = false; if (animView instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) animView; - wasRemoved = row.isRemoved(); + if (mFeatureFlags.isEnabled(SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX)) { + // If the view is already removed from its parent and added as Transient, + // we need to clean the transient view upon animation end + wasRemoved = row.getTransientContainer() != null + || row.getParent() == null || row.isRemoved(); + } else { + wasRemoved = row.isRemoved(); + } } if (!mCancelled || wasRemoved) { mCallback.onChildDismissed(animView); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index c095aa8349b3..584357bb739c 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -80,8 +80,8 @@ import androidx.core.math.MathUtils; import com.android.internal.accessibility.common.MagnificationConstants; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; -import com.android.systemui.res.R; import com.android.systemui.model.SysUiState; +import com.android.systemui.res.R; import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; @@ -205,7 +205,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private final Supplier<IWindowSession> mGlobalWindowSessionSupplier; private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; private final MagnificationGestureDetector mGestureDetector; - private final int mBounceEffectDuration; + private int mBounceEffectDuration; private final Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback; private Locale mLocale; private NumberFormat mPercentFormat; @@ -272,8 +272,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold setupMagnificationSizeScaleOptions(); - mBounceEffectDuration = mResources.getInteger( - com.android.internal.R.integer.config_shortAnimTime); + setBounceEffectDuration(mResources.getInteger( + com.android.internal.R.integer.config_shortAnimTime)); updateDimensions(); final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible(); @@ -1461,6 +1461,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mDragView.setColorFilter(filter); } + @VisibleForTesting + void setBounceEffectDuration(int duration) { + mBounceEffectDuration = duration; + } + private void animateBounceEffect() { final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mMirrorView, PropertyValuesHolder.ofFloat(View.SCALE_X, 1, mBounceEffectAnimationScale, 1), diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index b2433d4832f4..80be008dbd3a 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -29,9 +29,9 @@ import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationResultModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.time.SystemClock @@ -59,18 +59,6 @@ import kotlinx.coroutines.withContext /** Defines interface for classes that can access authentication-related application state. */ interface AuthenticationRepository { - - /** - * Whether the device is unlocked. - * - * A device that is not yet unlocked requires unlocking by completing an authentication - * challenge according to the current authentication method, unless in cases when the current - * authentication method is not "secure" (for example, None); in such cases, the value of this - * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed - * by the user to proceed. - */ - val isUnlocked: StateFlow<Boolean> - /** * Whether the auto confirm feature is enabled for the currently-selected user. * @@ -129,14 +117,6 @@ interface AuthenticationRepository { /** Returns the length of the PIN or `0` if the current auth method is not PIN. */ suspend fun getPinLength(): Int - /** - * Returns whether the lockscreen is enabled. - * - * When the lockscreen is not enabled, it shouldn't show in cases when the authentication method - * is considered not secure (for example, "swipe" is considered to be "none"). - */ - suspend fun isLockscreenEnabled(): Boolean - /** Reports an authentication attempt. */ suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) @@ -167,6 +147,7 @@ interface AuthenticationRepository { suspend fun checkCredential(credential: LockscreenCredential): AuthenticationResultModel } +@SysUISingleton class AuthenticationRepositoryImpl @Inject constructor( @@ -174,20 +155,10 @@ constructor( private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>, @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, - keyguardRepository: KeyguardRepository, private val lockPatternUtils: LockPatternUtils, broadcastDispatcher: BroadcastDispatcher, ) : AuthenticationRepository { - override val isUnlocked = keyguardRepository.isKeyguardUnlocked - - override suspend fun isLockscreenEnabled(): Boolean { - return withContext(backgroundDispatcher) { - val selectedUserId = userRepository.selectedUserId - !lockPatternUtils.isLockScreenDisabled(selectedUserId) - } - } - override val isAutoConfirmEnabled: StateFlow<Boolean> = refreshingFlow( initialValue = false, diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 4cfc6aaa2b50..453a7a6d3536 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -26,9 +26,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationThrottling import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.keyguard.data.repository.KeyguardRepository -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -42,15 +40,19 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -/** Hosts application business logic related to authentication. */ +/** + * Hosts application business logic related to user authentication. + * + * Note: there is a distinction between authentication (determining a user's identity) and device + * entry (dismissing the lockscreen). For logic that is specific to device entry, please use + * `DeviceEntryInteractor` instead. + */ @SysUISingleton class AuthenticationInteractor @Inject @@ -59,8 +61,7 @@ constructor( private val repository: AuthenticationRepository, @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, - private val keyguardRepository: KeyguardRepository, - sceneInteractor: SceneInteractor, + private val deviceEntryRepository: DeviceEntryRepository, private val clock: SystemClock, ) { /** @@ -77,76 +78,13 @@ constructor( * Note: this layer adds the synthetic authentication method of "swipe" which is special. When * the current authentication method is "swipe", the user does not need to complete any * authentication challenge to unlock the device; they just need to dismiss the lockscreen to - * get past it. This also means that the value of [isUnlocked] remains `false` even when the - * lockscreen is showing and still needs to be dismissed by the user to proceed. + * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains + * `true` even when the lockscreen is showing and still needs to be dismissed by the user to + * proceed. */ val authenticationMethod: Flow<DomainLayerAuthenticationMethodModel> = repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() } - /** - * Whether the device is unlocked. - * - * A device that is not yet unlocked requires unlocking by completing an authentication - * challenge according to the current authentication method, unless in cases when the current - * authentication method is not "secure" (for example, None and Swipe); in such cases, the value - * of this flow will always be `true`, even if the lockscreen is showing and still needs to be - * dismissed by the user to proceed. - */ - val isUnlocked: StateFlow<Boolean> = - combine( - repository.isUnlocked, - authenticationMethod, - ) { isUnlocked, authenticationMethod -> - !authenticationMethod.isSecure || isUnlocked - } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = false, - ) - - /** - * Whether the lockscreen has been dismissed (by any method). This can be false even when the - * device is unlocked, e.g. when swipe to unlock is enabled. - * - * Note: - * - `false` doesn't mean the lockscreen is visible (it may be occluded or covered by other UI). - * - `true` doesn't mean the lockscreen is invisible (since this state changes before the - * transition occurs). - */ - val isLockscreenDismissed: StateFlow<Boolean> = - sceneInteractor.desiredScene - .map { it.key } - .filter { currentScene -> - currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen - } - .map { it == SceneKey.Gone } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = false, - ) - - /** - * Whether it's currently possible to swipe up to dismiss the lockscreen without requiring - * authentication. This returns false whenever the lockscreen has been dismissed. - * - * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other - * UI. - */ - val canSwipeToDismiss = - combine(authenticationMethod, isLockscreenDismissed) { - authenticationMethod, - isLockscreenDismissed -> - authenticationMethod is DomainLayerAuthenticationMethodModel.Swipe && - !isLockscreenDismissed - } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = false, - ) - /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */ val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling @@ -211,32 +149,15 @@ constructor( * Note: this layer adds the synthetic authentication method of "swipe" which is special. When * the current authentication method is "swipe", the user does not need to complete any * authentication challenge to unlock the device; they just need to dismiss the lockscreen to - * get past it. This also means that the value of [isUnlocked] remains `false` even when the - * lockscreen is showing and still needs to be dismissed by the user to proceed. + * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains + * `true` even when the lockscreen is showing and still needs to be dismissed by the user to + * proceed. */ suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel { return repository.getAuthenticationMethod().toDomainLayer() } /** - * Returns `true` if the device currently requires authentication before content can be viewed; - * `false` if content can be displayed without unlocking first. - */ - suspend fun isAuthenticationRequired(): Boolean { - return !isUnlocked.value && getAuthenticationMethod().isSecure - } - - /** - * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically - * dismisses once the authentication challenge is completed. For example, completing a biometric - * authentication challenge via face unlock or fingerprint sensor can automatically bypass the - * lock screen. - */ - fun isBypassEnabled(): Boolean { - return keyguardRepository.isBypassEnabled() - } - - /** * Attempts to authenticate the user and unlock the device. * * If [tryAutoConfirm] is `true`, authentication is attempted if and only if the auth method @@ -312,7 +233,7 @@ constructor( /** Starts refreshing the throttling state every second. */ private suspend fun startThrottlingCountdown() { - cancelCountdown() + cancelThrottlingCountdown() throttlingCountdownJob = applicationScope.launch { while (refreshThrottling() > 0) { @@ -322,14 +243,14 @@ constructor( } /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */ - private fun cancelCountdown() { + private fun cancelThrottlingCountdown() { throttlingCountdownJob?.cancel() throttlingCountdownJob = null } /** Notifies that the currently-selected user has changed. */ private suspend fun onSelectedUserChanged() { - cancelCountdown() + cancelThrottlingCountdown() if (refreshThrottling() > 0) { startThrottlingCountdown() } @@ -378,7 +299,7 @@ constructor( DomainLayerAuthenticationMethodModel { return when (this) { is DataLayerAuthenticationMethodModel.None -> - if (repository.isLockscreenEnabled()) { + if (deviceEntryRepository.isInsecureLockscreenEnabled()) { DomainLayerAuthenticationMethodModel.Swipe } else { DomainLayerAuthenticationMethodModel.None @@ -394,13 +315,10 @@ constructor( /** Result of a user authentication attempt. */ enum class AuthenticationResult { - /** Authentication succeeded and the device is now unlocked. */ + /** Authentication succeeded. */ SUCCEEDED, - /** Authentication failed and the device remains unlocked. */ + /** Authentication failed. */ FAILED, - /** - * Authentication was not performed, e.g. due to insufficient input, and the device remains - * unlocked. - */ + /** Authentication was not performed, e.g. due to insufficient input. */ SKIPPED, } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 46d3c8a0c3e3..79d9c1ba70bc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -1096,8 +1096,19 @@ public class UdfpsController implements DozeReceiver, Dumpable { // cancel the fingerprint scan. mCancelAodFingerUpAction = mFgExecutor.executeDelayed(this::tryAodSendFingerUp, AOD_SEND_FINGER_UP_DELAY_MILLIS); - // using a hard-coded value for major and minor until it is available from the sensor - onFingerDown(requestId, screenX, screenY, minor, major); + // using a hard-coded value for orientation, time and gestureStart until they are + // available from the sensor. + onFingerDown( + requestId, + MotionEvent.INVALID_POINTER_ID /* pointerId */, + screenX, + screenY, + minor, + major, + 0f /* orientation */, + 0L /* time */, + 0L /* gestureStart */, + true /* isAod */); }; if (mScreenOn) { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index bae0ac74548d..0c0236999e39 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -26,6 +26,7 @@ import com.android.systemui.classifier.FalsingClassifier import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags @@ -50,6 +51,7 @@ constructor( @Application private val applicationScope: CoroutineScope, @Application private val applicationContext: Context, private val repository: BouncerRepository, + private val deviceEntryInteractor: DeviceEntryInteractor, private val authenticationInteractor: AuthenticationInteractor, private val sceneInteractor: SceneInteractor, flags: SceneContainerFlags, @@ -144,7 +146,7 @@ constructor( message: String? = null, ) { applicationScope.launch { - if (authenticationInteractor.isAuthenticationRequired()) { + if (deviceEntryInteractor.isAuthenticationRequired()) { repository.setMessage( message ?: promptMessage(authenticationInteractor.getAuthenticationMethod()) ) @@ -235,17 +237,16 @@ constructor( repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod())) } - /** If the bouncer is showing, hides the bouncer and return to the lockscreen scene. */ - fun hide( - loggingReason: String, - ) { + /** Notifies the interactor that the input method editor has been hidden. */ + fun onImeHidden() { + // If the bouncer is showing, hide it and return to the lockscreen scene. if (sceneInteractor.desiredScene.value.key != SceneKey.Bouncer) { return } sceneInteractor.changeScene( scene = SceneModel(SceneKey.Lockscreen), - loggingReason = loggingReason, + loggingReason = "IME hidden", ) } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 0b0a8f5e00f1..66c6162533bf 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -78,10 +78,7 @@ sealed class AuthMethodBouncerViewModel( */ fun onImeVisibilityChanged(isVisible: Boolean) { if (isImeVisible && !isVisible) { - // The IME has gone from visible to invisible, dismiss the bouncer. - interactor.hide( - loggingReason = "IME hidden", - ) + interactor.onImeHidden() } isImeVisible = isVisible diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 782ead360d51..c98cf317bb73 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -62,6 +62,10 @@ constructor( initialValue = !bouncerInteractor.isThrottled.value, ) + // Handle to the scope of the child ViewModel (stored in [authMethod]). + private var childViewModelScope: CoroutineScope? = null + private val _throttlingDialogMessage = MutableStateFlow<String?>(null) + /** View-model for the current UI, based on the current authentication method. */ val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> = authenticationInteractor.authenticationMethod @@ -72,8 +76,31 @@ constructor( initialValue = null, ) - // Handle to the scope of the child ViewModel (stored in [authMethod]). - private var childViewModelScope: CoroutineScope? = null + /** + * A message for a throttling dialog to show when the user has attempted the wrong credential + * too many times and now must wait a while before attempting again. + * + * If `null`, no dialog should be shown. + * + * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user + * dismisses this dialog. + */ + val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow() + + /** The user-facing message to show in the bouncer. */ + val message: StateFlow<MessageViewModel> = + combine(bouncerInteractor.message, bouncerInteractor.isThrottled) { message, isThrottled -> + toMessageViewModel(message, isThrottled) + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = + toMessageViewModel( + message = bouncerInteractor.message.value, + isThrottled = bouncerInteractor.isThrottled.value, + ), + ) init { if (flags.isEnabled()) { @@ -98,33 +125,6 @@ constructor( } } - /** The user-facing message to show in the bouncer. */ - val message: StateFlow<MessageViewModel> = - combine(bouncerInteractor.message, bouncerInteractor.isThrottled) { message, isThrottled -> - toMessageViewModel(message, isThrottled) - } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = - toMessageViewModel( - message = bouncerInteractor.message.value, - isThrottled = bouncerInteractor.isThrottled.value, - ), - ) - - private val _throttlingDialogMessage = MutableStateFlow<String?>(null) - /** - * A message for a throttling dialog to show when the user has attempted the wrong credential - * too many times and now must wait a while before attempting again. - * - * If `null`, no dialog should be shown. - * - * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user - * dismisses this dialog. - */ - val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow() - /** Notifies that the emergency services button was clicked. */ fun onEmergencyServicesButtonClicked() { // TODO(b/280877228): implement this diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index 9b93522b2d5f..e8a84449566d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -18,26 +18,25 @@ package com.android.systemui.controls.ui import android.app.Activity import android.app.ActivityOptions -import android.app.ActivityTaskManager -import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED import android.app.Dialog import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.Intent -import android.graphics.Rect import android.view.View import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowInsets.Type import android.view.WindowManager import android.widget.ImageView +import androidx.annotation.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.res.R import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.boundsOnScreen import com.android.wm.shell.taskview.TaskView /** @@ -65,8 +64,8 @@ class DetailDialog( private const val EXTRA_USE_PANEL = "controls.DISPLAY_IN_PANEL" } - var detailTaskId = INVALID_TASK_ID private lateinit var taskViewContainer: View + private lateinit var controlDetailRoot: View private val taskWidthPercentWidth = activityContext.resources.getFloat( R.dimen.controls_task_view_width_percentage ) @@ -79,12 +78,7 @@ class DetailDialog( addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) } - fun removeDetailTask() { - if (detailTaskId == INVALID_TASK_ID) return - ActivityTaskManager.getInstance().removeTask(detailTaskId) - detailTaskId = INVALID_TASK_ID - } - + @VisibleForTesting val stateCallback = object : TaskView.Listener { override fun onInitialized() { taskViewContainer.apply { @@ -98,33 +92,29 @@ class DetailDialog( activityContext, 0 /* enterResId */, 0 /* exitResId */ - ).setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED) - options.isPendingIntentBackgroundActivityLaunchAllowedByPermission = true + ).apply { + pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED + isPendingIntentBackgroundActivityLaunchAllowedByPermission = true + taskAlwaysOnTop = true + } taskView.startActivity( pendingIntent, fillInIntent, options, - getTaskViewBounds() + taskView.boundsOnScreen, ) } override fun onTaskRemovalStarted(taskId: Int) { - detailTaskId = INVALID_TASK_ID - dismiss() + taskView.release() } override fun onTaskCreated(taskId: Int, name: ComponentName?) { - detailTaskId = taskId requireViewById<ViewGroup>(R.id.controls_activity_view).apply { setAlpha(1f) } } - - override fun onReleased() { - removeDetailTask() - } - override fun onBackPressedOnTaskRoot(taskId: Int) { dismiss() } @@ -138,6 +128,9 @@ class DetailDialog( setContentView(R.layout.controls_detail_dialog) taskViewContainer = requireViewById<ViewGroup>(R.id.control_task_view_container) + controlDetailRoot = requireViewById<View>(R.id.control_detail_root).apply { + setOnClickListener { _: View -> dismiss() } + } requireViewById<ViewGroup>(R.id.controls_activity_view).apply { addView(taskView) @@ -147,13 +140,9 @@ class DetailDialog( requireViewById<ImageView>(R.id.control_detail_close).apply { setOnClickListener { _: View -> dismiss() } } - requireViewById<View>(R.id.control_detail_root).apply { - setOnClickListener { _: View -> dismiss() } - } requireViewById<ImageView>(R.id.control_detail_open_in_app).apply { setOnClickListener { v: View -> - removeDetailTask() dismiss() val action = ActivityStarter.OnDismissAction { @@ -201,26 +190,9 @@ class DetailDialog( taskView.setListener(cvh.uiExecutor, stateCallback) } - fun getTaskViewBounds(): Rect { - val wm = checkNotNull(context.getSystemService(WindowManager::class.java)) - val windowMetrics = wm.getCurrentWindowMetrics() - val rect = windowMetrics.bounds - val metricInsets = windowMetrics.windowInsets - val insets = metricInsets.getInsetsIgnoringVisibility(Type.systemBars() - or Type.displayCutout()) - val headerHeight = context.resources.getDimensionPixelSize( - R.dimen.controls_detail_dialog_header_height) - - val finalRect = Rect(rect.left - insets.left /* left */, - rect.top + insets.top + headerHeight /* top */, - rect.right - insets.right /* right */, - rect.bottom - insets.bottom /* bottom */) - return finalRect - } - override fun dismiss() { if (!isShowing()) return - taskView.release() + taskView.removeTask() val isActivityFinishing = (activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt index 1b0d03276415..848c78644659 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt @@ -18,7 +18,6 @@ package com.android.systemui.controls.ui import android.app.ActivityOptions -import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.PendingIntent import android.content.ComponentName import android.content.Context @@ -45,8 +44,6 @@ class PanelTaskViewController( taskView.alpha = 0f } - private var detailTaskId = INVALID_TASK_ID - private val fillInIntent = Intent().apply { // Apply flags to make behaviour match documentLaunchMode=always. @@ -57,7 +54,6 @@ class PanelTaskViewController( private val stateCallback = object : TaskView.Listener { override fun onInitialized() { - val options = ActivityOptions.makeCustomAnimation( activityContext, @@ -88,12 +84,10 @@ class PanelTaskViewController( } override fun onTaskRemovalStarted(taskId: Int) { - detailTaskId = INVALID_TASK_ID release() } override fun onTaskCreated(taskId: Int, name: ComponentName?) { - detailTaskId = taskId taskView.alpha = 1f } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index da5e933e947a..04a9cae31382 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -25,6 +25,7 @@ import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IActivityTaskManager; import android.app.INotificationManager; +import android.app.IUriGrantsManager; import android.app.IWallpaperManager; import android.app.KeyguardManager; import android.app.NotificationManager; @@ -688,4 +689,12 @@ public class FrameworkServicesModule { static StatusBarManager provideStatusBarManager(Context context) { return context.getSystemService(StatusBarManager.class); } + + @Provides + @Singleton + static IUriGrantsManager provideIUriGrantsManager() { + return IUriGrantsManager.Stub.asInterface( + ServiceManager.getService(Context.URI_GRANTS_SERVICE) + ); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 283a07b206b9..4b6ad6d9be03 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -48,6 +48,7 @@ import com.android.systemui.controls.dagger.ControlsModule; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.SystemUser; import com.android.systemui.demomode.dagger.DemoModeModule; +import com.android.systemui.deviceentry.DeviceEntryModule; import com.android.systemui.display.DisplayModule; import com.android.systemui.doze.dagger.DozeComponent; import com.android.systemui.dreams.dagger.DreamModule; @@ -173,6 +174,7 @@ import javax.inject.Named; ControlsModule.class, CoroutinesModule.class, DemoModeModule.class, + DeviceEntryModule.class, DisableFlagsModule.class, DisplayModule.class, DreamModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt new file mode 100644 index 000000000000..e7f835f7b858 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt @@ -0,0 +1,12 @@ +package com.android.systemui.deviceentry + +import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule +import dagger.Module + +@Module( + includes = + [ + DeviceEntryRepositoryModule::class, + ], +) +object DeviceEntryModule diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt new file mode 100644 index 000000000000..5b85ad01301b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt @@ -0,0 +1,125 @@ +package com.android.systemui.deviceentry.data.repository + +import com.android.internal.widget.LockPatternUtils +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.data.repository.UserRepository +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +/** Interface for classes that can access device-entry-related application state. */ +interface DeviceEntryRepository { + /** + * Whether the device is unlocked. + * + * A device that is not yet unlocked requires unlocking by completing an authentication + * challenge according to the current authentication method, unless in cases when the current + * authentication method is not "secure" (for example, None); in such cases, the value of this + * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed + * by the user to proceed. + */ + val isUnlocked: StateFlow<Boolean> + + /** + * Whether the lockscreen should be shown when the authentication method is not secure (e.g. + * `None` or `Swipe`). + */ + suspend fun isInsecureLockscreenEnabled(): Boolean + + /** + * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically + * dismissed once the authentication challenge is completed. + * + * This is a setting that is specific to the face unlock authentication method, because the user + * intent to unlock is not known. On devices that don't support face unlock, this always returns + * `true`. + * + * When this is `false`, an automatically-triggered face unlock shouldn't automatically dismiss + * the lockscreen. + */ + fun isBypassEnabled(): Boolean +} + +/** Encapsulates application state for device entry. */ +@SysUISingleton +class DeviceEntryRepositoryImpl +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val userRepository: UserRepository, + private val lockPatternUtils: LockPatternUtils, + private val keyguardBypassController: KeyguardBypassController, + keyguardStateController: KeyguardStateController, +) : DeviceEntryRepository { + + override val isUnlocked = + ConflatedCallbackFlow.conflatedCallbackFlow { + val callback = + object : KeyguardStateController.Callback { + override fun onUnlockedChanged() { + trySendWithFailureLogging( + keyguardStateController.isUnlocked, + TAG, + "updated isUnlocked due to onUnlockedChanged" + ) + } + + override fun onKeyguardShowingChanged() { + trySendWithFailureLogging( + keyguardStateController.isUnlocked, + TAG, + "updated isUnlocked due to onKeyguardShowingChanged" + ) + } + } + + keyguardStateController.addCallback(callback) + // Adding the callback does not send an initial update. + trySendWithFailureLogging( + keyguardStateController.isUnlocked, + TAG, + "initial isKeyguardUnlocked" + ) + + awaitClose { keyguardStateController.removeCallback(callback) } + } + .distinctUntilChanged() + .stateIn( + applicationScope, + SharingStarted.Eagerly, + initialValue = false, + ) + + override suspend fun isInsecureLockscreenEnabled(): Boolean { + return withContext(backgroundDispatcher) { + val selectedUserId = userRepository.getSelectedUserInfo().id + !lockPatternUtils.isLockScreenDisabled(selectedUserId) + } + } + + override fun isBypassEnabled() = keyguardBypassController.bypassEnabled + + companion object { + private const val TAG = "DeviceEntryRepositoryImpl" + } +} + +@Module +interface DeviceEntryRepositoryModule { + @Binds fun repository(impl: DeviceEntryRepositoryImpl): DeviceEntryRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt new file mode 100644 index 000000000000..5612c9a488ff --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -0,0 +1,110 @@ +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.SceneKey +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * Hosts application business logic related to device entry. + * + * Device entry occurs when the user successfully dismisses (or bypasses) the lockscreen, regardless + * of the authentication method used. + */ +@SysUISingleton +class DeviceEntryInteractor +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val repository: DeviceEntryRepository, + private val authenticationInteractor: AuthenticationInteractor, + sceneInteractor: SceneInteractor, +) { + /** + * Whether the device is unlocked. + * + * A device that is not yet unlocked requires unlocking by completing an authentication + * challenge according to the current authentication method, unless in cases when the current + * authentication method is not "secure" (for example, None and Swipe); in such cases, the value + * of this flow will always be `true`, even if the lockscreen is showing and still needs to be + * dismissed by the user to proceed. + */ + val isUnlocked: StateFlow<Boolean> = + combine( + repository.isUnlocked, + authenticationInteractor.authenticationMethod, + ) { isUnlocked, authenticationMethod -> + !authenticationMethod.isSecure || isUnlocked + } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = false, + ) + + /** + * Whether the device has been entered (i.e. the lockscreen has been dismissed, by any method). + * This can be `false` when the device is unlocked, e.g. when the user still needs to swipe away + * the non-secure lockscreen, even though they've already authenticated. + * + * Note: This does not imply that the lockscreen is visible or not. + */ + val isDeviceEntered: StateFlow<Boolean> = + sceneInteractor.desiredScene + .map { it.key } + .filter { currentScene -> + currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen + } + .map { it == SceneKey.Gone } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + + /** + * Whether it's currently possible to swipe up to enter the device without requiring + * authentication. This returns `false` whenever the lockscreen has been dismissed. + * + * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other + * UI. + */ + val canSwipeToEnter = + combine(authenticationInteractor.authenticationMethod, isDeviceEntered) { + authenticationMethod, + isDeviceEntered -> + authenticationMethod is AuthenticationMethodModel.Swipe && !isDeviceEntered + } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + + /** + * Returns `true` if the device currently requires authentication before entry is granted; + * `false` if the device can be entered without authenticating first. + */ + suspend fun isAuthenticationRequired(): Boolean { + return !isUnlocked.value && authenticationInteractor.getAuthenticationMethod().isSecure + } + + /** + * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically + * dismissed once the authentication challenge is completed. For example, completing a biometric + * authentication challenge via face unlock or fingerprint sensor can automatically bypass the + * lock screen. + */ + fun isBypassEnabled() = repository.isBypassEnabled() +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 4b38267d98e8..a00c3b5ae38e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -80,16 +80,9 @@ object Flags { @JvmField val NOTIFICATION_INLINE_REPLY_ANIMATION = releasedFlag("notification_inline_reply_animation") - /** Makes sure notification panel is updated before the user switch is complete. */ - // TODO(b/278873737): Tracking Bug - @JvmField - val LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE = - releasedFlag("load_notifications_before_the_user_switch_is_complete") - // TODO(b/277338665): Tracking Bug @JvmField - val NOTIFICATION_SHELF_REFACTOR = - unreleasedFlag("notification_shelf_refactor", teamfood = true) + val NOTIFICATION_SHELF_REFACTOR = releasedFlag("notification_shelf_refactor") // TODO(b/290787599): Tracking Bug @JvmField @@ -298,6 +291,16 @@ object Flags { @JvmField val MIGRATE_KEYGUARD_STATUS_BAR_VIEW = unreleasedFlag("migrate_keyguard_status_bar_view") + /** Migrate clocks from keyguard status view to keyguard root view*/ + // TODO(b/301502635): Tracking Bug. + @JvmField val MIGRATE_CLOCKS_TO_BLUEPRINT = + unreleasedFlag("migrate_clocks_to_blueprint") + + /** Migrate KeyguardRootView to use composables. */ + // TODO(b/301969856): Tracking Bug. + @JvmField val KEYGUARD_ROOT_VIEW_USE_COMPOSE = + unreleasedFlag("keyguard_root_view_use_compose") + /** Enables preview loading animation in the wallpaper picker. */ // TODO(b/274443705): Tracking Bug @JvmField @@ -763,6 +766,13 @@ object Flags { // TODO(b/289573946): Tracking Bug @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text", teamfood = true) + // TODO(b/302087895): Tracking Bug + @JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data") + + // TODO(b/302144438): Tracking Bug + @JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE = + unreleasedFlag("decouple_remote_input_delegate_and_callback_update") + // 2900 - CentralSurfaces-related flags // TODO(b/285174336): Tracking Bug diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt index d9b2c39b9bef..d89cf63cd483 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderTracker.kt @@ -176,7 +176,6 @@ class SeekableSliderTracker( SliderState.DRAG_HANDLE_REACHED_BOOKEND -> executeOnBookend() SliderState.JUMP_TRACK_LOCATION_SELECTED -> sliderListener.onProgressJump(latestProgress) - SliderState.JUMP_BOOKEND_SELECTED -> executeOnBookend() else -> {} } } @@ -197,7 +196,7 @@ class SeekableSliderTracker( epsilon: Float = 0.00001f, ): Boolean { val delta = abs(currentProgress - latestProgress) - return abs(delta - config.jumpThreshold) < epsilon + return delta > config.jumpThreshold - epsilon } private fun bookendReached(currentProgress: Float): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt index 9c99c90bb910..9f12f3f65227 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderStateListener.kt @@ -37,7 +37,9 @@ interface SliderStateListener { * Notification that the slider reached a certain progress on the slider track. * * This method is called in all intermediate steps of a continuous progress change as the slider - * moves through the slider track. + * moves through the slider track. A single discrete movement of the handle by an external + * button or by a jump on the slider track will not trigger this callback. See + * [onSelectAndArrow] and [onProgressJump] for these cases. * * @param[progress] The progress of the slider in the range from 0F to 1F (inclusive). */ @@ -56,7 +58,7 @@ interface SliderStateListener { fun onProgressJump(@FloatRange(from = 0.0, to = 1.0) progress: Float) /** - * Notification that the slider handle was moved by a button press. + * Notification that the slider handle was moved discretely by one step via a button press. * * @param[progress] The progress of the slider in the range from 0F to 1F (inclusive). */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index ff7405066184..2a69ec556d4b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -44,7 +44,6 @@ import com.android.systemui.flags.Flags import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.shared.recents.utilities.Utilities import com.android.systemui.shared.system.ActivityManagerWrapper -import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController import com.android.systemui.shared.system.smartspace.SmartspaceState @@ -219,6 +218,11 @@ class KeyguardUnlockAnimationController @Inject constructor( */ private var launcherUnlockController: ILauncherUnlockAnimationController? = null + /** + * Fully qualified class name of the launcher activity + */ + private var launcherActivityClass: String? = null + private val listeners = ArrayList<KeyguardUnlockAnimationListener>() /** @@ -226,7 +230,11 @@ class KeyguardUnlockAnimationController @Inject constructor( * doesn't happen, we won't use in-window animations or the smartspace shared element * transition, but that's okay! */ - override fun setLauncherUnlockController(callback: ILauncherUnlockAnimationController?) { + override fun setLauncherUnlockController( + activityClass: String, + callback: ILauncherUnlockAnimationController? + ) { + launcherActivityClass = activityClass launcherUnlockController = callback } @@ -545,7 +553,6 @@ class KeyguardUnlockAnimationController @Inject constructor( // gesture and the surface behind the keyguard should be made visible so that we can animate // it in. if (requestedShowSurfaceBehindKeyguard) { - // If we're flinging to dismiss here, it means the touch gesture ended in a fling during // the time it takes the keyguard exit animation to start. This is an edge case race // condition, which we handle by just playing a canned animation on the now-visible @@ -785,7 +792,6 @@ class KeyguardUnlockAnimationController @Inject constructor( if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { - keyguardViewMediator.get().showSurfaceBehindKeyguard() } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD && keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) { @@ -1113,17 +1119,18 @@ class KeyguardUnlockAnimationController @Inject constructor( return playingCannedUnlockAnimation } + /** + * Return whether the Google Nexus launcher is underneath the keyguard, vs. some other + * launcher or an app. If so, we can communicate with it to perform in-window/shared element + * transitions! + */ + fun isNexusLauncherUnderneath(): Boolean { + return launcherActivityClass?.let { ActivityManagerWrapper.getInstance() + .runningTask?.topActivity?.className?.equals(it) } + ?: false + } + companion object { - /** - * Return whether the Google Nexus launcher is underneath the keyguard, vs. some other - * launcher or an app. If so, we can communicate with it to perform in-window/shared element - * transitions! - */ - fun isNexusLauncherUnderneath(): Boolean { - return ActivityManagerWrapper.getInstance() - .runningTask?.topActivity?.className?.equals( - QuickStepContract.LAUNCHER_ACTIVITY_CLASS_NAME) ?: false - } fun isFoldable(context: Context): Boolean { return context.resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index fb02c7d68a9f..1761ca86f588 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -26,7 +26,6 @@ import com.android.keyguard.LockIconView import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardStatusViewComponent import com.android.systemui.CoreStartable -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags @@ -40,7 +39,9 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel +import com.android.systemui.res.R import com.android.systemui.shade.NotificationShadeWindowView +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator @@ -69,6 +70,7 @@ constructor( private val context: Context, private val keyguardIndicationController: KeyguardIndicationController, private val lockIconViewController: LockIconViewController, + private val shadeInteractor: ShadeInteractor, ) : CoreStartable { private var rootViewHandle: DisposableHandle? = null @@ -135,6 +137,7 @@ constructor( occludingAppDeviceEntryMessageViewModel, chipbarCoordinator, keyguardStateController, + shadeInteractor ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index b506a363d3d4..7e826b86c9ee 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -128,7 +128,6 @@ import com.android.systemui.CoreStartable; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.EventLogTags; -import com.android.systemui.res.R; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.LaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -146,6 +145,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransition import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionStateManager; @@ -181,7 +181,6 @@ import java.util.concurrent.Executor; import java.util.function.Consumer; import dagger.Lazy; - import kotlinx.coroutines.CoroutineDispatcher; /** @@ -2833,7 +2832,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // playing in-window animations for this particular unlock since a previous unlock might // have changed the Launcher state. if (mWakeAndUnlocking - && KeyguardUnlockAnimationController.Companion.isNexusLauncherUnderneath()) { + && mKeyguardUnlockAnimationControllerLazy.get().isNexusLauncherUnderneath()) { flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; } @@ -3287,7 +3286,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // of the in-window animations are reflected. This is needed even if we're not actually // playing in-window animations for this particular unlock since a previous unlock might // have changed the Launcher state. - if (KeyguardUnlockAnimationController.Companion.isNexusLauncherUnderneath()) { + if (mKeyguardUnlockAnimationControllerLazy.get().isNexusLauncherUnderneath()) { flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index 06cf7235ad2c..e8740a4b24c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -26,7 +26,6 @@ import android.os.UserHandle import android.util.Log import com.android.internal.widget.LockPatternUtils import com.android.systemui.Dumpable -import com.android.systemui.res.R import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository @@ -40,6 +39,8 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.shared.model.AuthenticationFlags import com.android.systemui.keyguard.shared.model.DevicePosture +import com.android.systemui.res.R +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.user.data.repository.UserRepository import java.io.PrintWriter import javax.inject.Inject @@ -133,6 +134,7 @@ constructor( devicePostureRepository: DevicePostureRepository, facePropertyRepository: FacePropertyRepository, fingerprintPropertyRepository: FingerprintPropertyRepository, + mobileConnectionsRepository: MobileConnectionsRepository, dumpManager: DumpManager, ) : BiometricSettingsRepository, Dumpable { @@ -346,14 +348,15 @@ constructor( .and(isFingerprintBiometricAllowed) .stateIn(scope, SharingStarted.Eagerly, false) - override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> - get() = isFaceAuthenticationEnabled.and(isFaceEnrolled) + override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> = + isFaceAuthenticationEnabled + .and(isFaceEnrolled) + .and(mobileConnectionsRepository.isAnySimSecure.isFalse()) - override val isFaceAuthCurrentlyAllowed: Flow<Boolean> - get() = - isFaceAuthEnrolledAndEnabled - .and(isFaceBiometricsAllowed) - .and(isFaceAuthSupportedInCurrentPosture) + override val isFaceAuthCurrentlyAllowed: Flow<Boolean> = + isFaceAuthEnrolledAndEnabled + .and(isFaceBiometricsAllowed) + .and(isFaceAuthSupportedInCurrentPosture) } @OptIn(ExperimentalCoroutinesApi::class) @@ -426,3 +429,5 @@ private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean = private fun Flow<Boolean>.and(anotherFlow: Flow<Boolean>): Flow<Boolean> = this.combine(anotherFlow) { a, b -> a && b } + +private fun Flow<Boolean>.isFalse(): Flow<Boolean> = this.map { !it } 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 2557e8112e1e..36b93cdf6217 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 @@ -47,7 +47,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode import com.android.systemui.statusbar.phone.DozeParameters -import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -97,9 +96,6 @@ interface KeyguardRepository { */ val isKeyguardShowing: Flow<Boolean> - /** Is the keyguard in a unlocked state? */ - val isKeyguardUnlocked: StateFlow<Boolean> - /** Is an activity showing over the keyguard? */ val isKeyguardOccluded: Flow<Boolean> @@ -206,14 +202,6 @@ interface KeyguardRepository { */ fun isKeyguardShowing(): Boolean - /** - * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically - * dismissed once the authentication challenge is completed. For example, completing a biometric - * authentication challenge via face unlock or fingerprint sensor can automatically bypass the - * lock screen. - */ - fun isBypassEnabled(): Boolean - /** Sets whether the bottom area UI should animate the transition out of doze state. */ fun setAnimateDozingTransitions(animate: Boolean) @@ -265,7 +253,6 @@ constructor( screenLifecycle: ScreenLifecycle, biometricUnlockController: BiometricUnlockController, private val keyguardStateController: KeyguardStateController, - private val keyguardBypassController: KeyguardBypassController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val dozeTransitionListener: DozeTransitionListener, private val dozeParameters: DozeParameters, @@ -370,44 +357,6 @@ constructor( } .distinctUntilChanged() - override val isKeyguardUnlocked: StateFlow<Boolean> = - conflatedCallbackFlow { - val callback = - object : KeyguardStateController.Callback { - override fun onUnlockedChanged() { - trySendWithFailureLogging( - keyguardStateController.isUnlocked, - TAG, - "updated isKeyguardUnlocked due to onUnlockedChanged" - ) - } - - override fun onKeyguardShowingChanged() { - trySendWithFailureLogging( - keyguardStateController.isUnlocked, - TAG, - "updated isKeyguardUnlocked due to onKeyguardShowingChanged" - ) - } - } - - keyguardStateController.addCallback(callback) - // Adding the callback does not send an initial update. - trySendWithFailureLogging( - keyguardStateController.isUnlocked, - TAG, - "initial isKeyguardUnlocked" - ) - - awaitClose { keyguardStateController.removeCallback(callback) } - } - .distinctUntilChanged() - .stateIn( - scope, - SharingStarted.Eagerly, - initialValue = false, - ) - override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { @@ -543,10 +492,6 @@ constructor( return keyguardStateController.isShowing } - override fun isBypassEnabled(): Boolean { - return keyguardBypassController.bypassEnabled - } - // TODO(b/297345631): Expose this at the interactor level instead so that it can be powered by // [SceneInteractor] when scenes are ready. override val statusBarState: StateFlow<StatusBarState> = 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 338f9945f5db..80634682c8cf 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 @@ -23,7 +23,6 @@ import android.app.StatusBarManager import android.graphics.Point import android.util.MathUtils import com.android.app.animation.Interpolators -import com.android.systemui.res.R import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -31,6 +30,7 @@ import com.android.systemui.common.shared.model.Position import com.android.systemui.common.shared.model.SharedNotificationContainerPosition import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -40,10 +40,10 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState -import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.ScreenModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey @@ -79,6 +79,7 @@ constructor( private val commandQueue: CommandQueue, featureFlags: FeatureFlags, sceneContainerFlags: SceneContainerFlags, + deviceEntryRepository: DeviceEntryRepository, bouncerRepository: KeyguardBouncerRepository, configurationRepository: ConfigurationRepository, shadeRepository: ShadeRepository, @@ -168,7 +169,7 @@ constructor( val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing /** Whether the keyguard is unlocked or not. */ - val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked + val isKeyguardUnlocked: Flow<Boolean> = deviceEntryRepository.isUnlocked /** Whether the keyguard is occluded (covered by an activity). */ val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded @@ -323,26 +324,7 @@ constructor( repository.setAnimateDozingTransitions(animate) } - fun isKeyguardDismissable(): Boolean { - return repository.isKeyguardUnlocked.value - } - companion object { private const val TAG = "KeyguardInteractor" - - fun isKeyguardVisibleInState(state: KeyguardState): Boolean { - return when (state) { - KeyguardState.OFF -> true - KeyguardState.DOZING -> true - KeyguardState.DREAMING -> true - KeyguardState.AOD -> true - KeyguardState.ALTERNATE_BOUNCER -> true - KeyguardState.PRIMARY_BOUNCER -> true - KeyguardState.LOCKSCREEN -> true - KeyguardState.GONE -> false - KeyguardState.OCCLUDED -> true - KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false - } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index dd7eee924007..abc30efec716 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -25,6 +25,8 @@ import android.view.View import android.view.ViewGroup import android.view.ViewPropertyAnimator import android.widget.ImageView +import androidx.core.animation.CycleInterpolator +import androidx.core.animation.ObjectAnimator import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams @@ -383,6 +385,27 @@ object KeyguardBottomAreaViewBinder { falsingManager, ) view.setOnTouchListener(onTouchListener) + view.setOnClickListener { + messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short) + val amplitude = + view.context.resources + .getDimensionPixelSize(R.dimen.keyguard_affordance_shake_amplitude) + .toFloat() + val shakeAnimator = + ObjectAnimator.ofFloat( + view, + "translationX", + -amplitude / 2, + amplitude / 2, + ) + shakeAnimator.duration = + KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds + shakeAnimator.interpolator = + CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles) + shakeAnimator.start() + + vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake) + } view.onLongClickListener = OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener) } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt index 125e2dac7bb7..f2d39dabb1b5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt @@ -99,41 +99,7 @@ class KeyguardQuickAffordanceOnTouchListener( // When not using a stylus, lifting the finger/pointer will actually cancel // the long-press gesture. Calling cancel after the quick affordance was // already long-press activated is a no-op, so it's safe to call from here. - cancel( - onAnimationEnd = - if (event.eventTime - event.downTime < longPressDurationMs) { - Runnable { - messageDisplayer.invoke( - R.string.keyguard_affordance_press_too_short - ) - val amplitude = - view.context.resources - .getDimensionPixelSize( - R.dimen.keyguard_affordance_shake_amplitude - ) - .toFloat() - val shakeAnimator = - ObjectAnimator.ofFloat( - view, - "translationX", - -amplitude / 2, - amplitude / 2, - ) - shakeAnimator.duration = - KeyguardBottomAreaVibrations.ShakeAnimationDuration - .inWholeMilliseconds - shakeAnimator.interpolator = - CycleInterpolator( - KeyguardBottomAreaVibrations.ShakeAnimationCycles - ) - shakeAnimator.start() - - vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake) - } - } else { - null - } - ) + cancel() } false } @@ -168,10 +134,10 @@ class KeyguardQuickAffordanceOnTouchListener( view.setOnClickListener(null) } - fun cancel(onAnimationEnd: Runnable? = null) { + fun cancel() { longPressAnimator?.cancel() longPressAnimator = null - view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd) + view.animate().scaleX(1f).scaleY(1f) } companion object { 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 eeb4ac34bf37..aa76702dc3d4 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 @@ -23,6 +23,8 @@ import android.util.Size import android.view.View import android.view.ViewGroup import android.widget.ImageView +import androidx.core.animation.CycleInterpolator +import androidx.core.animation.ObjectAnimator import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams @@ -216,6 +218,27 @@ object KeyguardQuickAffordanceViewBinder { falsingManager, ) view.setOnTouchListener(onTouchListener) + view.setOnClickListener { + messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short) + val amplitude = + view.context.resources + .getDimensionPixelSize(R.dimen.keyguard_affordance_shake_amplitude) + .toFloat() + val shakeAnimator = + ObjectAnimator.ofFloat( + view, + "translationX", + -amplitude / 2, + amplitude / 2, + ) + shakeAnimator.duration = + KeyguardBottomAreaVibrations.ShakeAnimationDuration.inWholeMilliseconds + shakeAnimator.interpolator = + CycleInterpolator(KeyguardBottomAreaVibrations.ShakeAnimationCycles) + shakeAnimator.start() + + vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake) + } view.onLongClickListener = OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener) } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index f564d0073e42..053727ace76d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -32,6 +32,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.ViewPriority @@ -55,6 +56,7 @@ object KeyguardRootViewBinder { occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel, chipbarCoordinator: ChipbarCoordinator, keyguardStateController: KeyguardStateController, + shadeInteractor: ShadeInteractor, ): DisposableHandle { val disposableHandle = view.repeatWhenAttached { @@ -88,6 +90,17 @@ object KeyguardRootViewBinder { } } } + + launch { + shadeInteractor.isAnyFullyExpanded.collect { isFullyAnyExpanded -> + view.visibility = + if (isFullyAnyExpanded) { + View.INVISIBLE + } else { + View.VISIBLE + } + } + } } repeatOnLifecycle(Lifecycle.State.STARTED) { 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 2ad74fbc6674..864e345e9c1e 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 @@ -62,6 +62,7 @@ import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessage import com.android.systemui.monet.ColorScheme import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.FalsingManager +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.clocks.ClockRegistry import com.android.systemui.shared.clocks.DefaultClockController import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants @@ -109,6 +110,7 @@ constructor( private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel, private val chipbarCoordinator: ChipbarCoordinator, private val keyguardStateController: KeyguardStateController, + private val shadeInteractor: ShadeInteractor, ) { val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) @@ -317,6 +319,7 @@ constructor( occludingAppDeviceEntryMessageViewModel, chipbarCoordinator, keyguardStateController, + shadeInteractor, ) ) rootView.addView( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt index 9409036586e8..f4bc7137b50c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt @@ -29,11 +29,11 @@ import androidx.constraintlayout.widget.ConstraintSet import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.LockIconView import com.android.keyguard.LockIconViewController -import com.android.systemui.res.R import com.android.systemui.biometrics.AuthController import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import javax.inject.Inject @@ -73,11 +73,11 @@ constructor( val mBottomPaddingPx = context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) val bounds = windowManager.currentWindowMetrics.bounds - val insets = windowManager.currentWindowMetrics.windowInsets var widthPixels = bounds.right.toFloat() if (featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { // Assumed to be initially neglected as there are no left or right insets in portrait. // However, on landscape, these insets need to included when calculating the midpoint. + val insets = windowManager.currentWindowMetrics.windowInsets widthPixels -= (insets.systemWindowInsetLeft + insets.systemWindowInsetRight).toFloat() } val heightPixels = bounds.bottom.toFloat() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 91b33572345d..c03e4d950cae 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -16,10 +16,10 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -34,26 +34,22 @@ class LockscreenSceneViewModel @Inject constructor( @Application applicationScope: CoroutineScope, - authenticationInteractor: AuthenticationInteractor, + deviceEntryInteractor: DeviceEntryInteractor, communalInteractor: CommunalInteractor, val longPress: KeyguardLongPressViewModel, ) { /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: StateFlow<SceneKey> = - authenticationInteractor.isUnlocked + deviceEntryInteractor.isUnlocked .map { isUnlocked -> upDestinationSceneKey(isUnlocked) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = upDestinationSceneKey(authenticationInteractor.isUnlocked.value), + initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value), ) private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey { - return if (isUnlocked) { - SceneKey.Gone - } else { - SceneKey.Bouncer - } + return if (isUnlocked) SceneKey.Gone else SceneKey.Bouncer } /** The key of the scene we should switch to when swiping left. */ diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 6c2ce7fa5156..1943b340b9b1 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -29,6 +29,7 @@ import com.android.systemui.log.LogcatEchoTrackerDebug; import com.android.systemui.log.LogcatEchoTrackerProd; import com.android.systemui.log.table.TableLogBuffer; import com.android.systemui.log.table.TableLogBufferFactory; +import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.util.Compile; import com.android.systemui.util.wakelock.WakeLockLog; @@ -229,12 +230,12 @@ public class LogModule { } /** - * Provides a logging buffer for logs related to {@link com.android.systemui.qs.QSFragment}'s + * Provides a logging buffer for logs related to {@link QSFragmentLegacy}'s * disable flag adjustments. */ @Provides @SysUISingleton - @QSFragmentDisableLog + @QSDisableLog public static LogBuffer provideQSFragmentDisableLogBuffer(LogBufferFactory factory) { return factory.create("QSFragmentDisableFlagsLog", 10 /* maxSize */, false /* systrace */); diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSDisableLog.java index 557a254e5c09..b3bceca57f14 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSFragmentDisableLog.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSDisableLog.java @@ -19,6 +19,7 @@ package com.android.systemui.log.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.android.systemui.log.LogBuffer; +import com.android.systemui.qs.QSFragmentLegacy; import java.lang.annotation.Documented; import java.lang.annotation.Retention; @@ -27,10 +28,10 @@ import javax.inject.Qualifier; /** * A {@link LogBuffer} for disable flag adjustments made in - * {@link com.android.systemui.qs.QSFragment}. + * {@link QSFragmentLegacy}. */ @Qualifier @Documented @Retention(RUNTIME) -public @interface QSFragmentDisableLog { +public @interface QSDisableLog { } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt index ae3c912d6d1b..ed6d41e5a75b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt @@ -101,7 +101,8 @@ constructor( panelEventsEvents: ShadeStateEvents, private val secureSettings: SecureSettings, @Main private val handler: Handler, - private val splitShadeStateController: SplitShadeStateController + private val splitShadeStateController: SplitShadeStateController, + private val logger: MediaViewLogger, ) { /** Track the media player setting status on lock screen. */ @@ -1057,6 +1058,7 @@ constructor( // that and directly set the mediaFrame's bounds within the premeasured host. targetHost.addView(mediaFrame) } + logger.logMediaHostAttachment(currentAttachmentLocation) if (isCrossFadeAnimatorRunning) { // When cross-fading with an animation, we only notify the media carousel of the // location change, once the view is reattached to the new place and not diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt index 8f1595d7d7a2..3ff2315956ad 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt @@ -52,4 +52,8 @@ class MediaViewLogger @Inject constructor(@MediaViewLog private val buffer: LogB { "location ($str1): $int1 -> $int2" } ) } + + fun logMediaHostAttachment(host: Int) { + buffer.log(TAG, LogLevel.DEBUG, { int1 = host }, { "Host (updateHostAttachment): $int1" }) + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 826f75f2f63b..19012e29b184 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -78,6 +78,14 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private int mMinRows = 1; private int mMaxColumns = TileLayout.NO_MAX_COLUMNS; + /** + * it's fine to read this value when class is initialized because SysUI is always restarted + * when running tests in test harness, see SysUiTestIsolationRule. This check is done quite + * often - with every shade open action - so we don't want to potentially make it less + * performant only for test use case + */ + private boolean mRunningInTestHarness = ActivityManager.isRunningInTestHarness(); + public PagedTileLayout(Context context, AttributeSet attrs) { super(context, attrs); mScroller = new Scroller(context, SCROLL_CUBIC); @@ -590,11 +598,11 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { private boolean shouldNotRunAnimation(Set<String> tilesToReveal) { boolean noAnimationNeeded = tilesToReveal.isEmpty() || mPages.size() < 2; boolean scrollingInProgress = getScrollX() != 0 || !beginFakeDrag(); - // isRunningInTestHarness() to disable animation in functional testing as it caused + // checking mRunningInTestHarness to disable animation in functional testing as it caused // flakiness and is not needed there. Alternative solutions were more complex and would // still be either potentially flaky or modify internal data. // For more info see b/253493927 and b/293234595 - return noAnimationNeeded || scrollingInProgress || ActivityManager.isRunningInTestHarness(); + return noAnimationNeeded || scrollingInProgress || mRunningInTestHarness; } private int sanitizePageAction(int action) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index 463c79c6696a..eba1c25d0e74 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -28,7 +28,7 @@ import androidx.annotation.Nullable; import com.android.app.animation.Interpolators; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.plugins.qs.QS; +import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.QSPanel.QSTileLayout; @@ -86,8 +86,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener private final QuickQSPanel mQuickQsPanel; private final QSPanelController mQsPanelController; private final QuickQSPanelController mQuickQSPanelController; - private final QuickStatusBarHeader mQuickStatusBarHeader; - private final QS mQs; + private final View mQsRootView; @Nullable private PagedTileLayout mPagedLayout; @@ -115,8 +114,6 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener // Brightness slider opacity driver. Uses linear interpolator. @Nullable private TouchAnimator mBrightnessOpacityAnimator; - // Animator for Footer actions in QQS - private TouchAnimator mQQSFooterActionsAnimator; // Height animator for QQS tiles (height changing from QQS size to QS size) @Nullable private HeightExpansionAnimator mQQSTileHeightAnimator; @@ -144,22 +141,21 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener private int[] mTmpLoc2 = new int[2]; @Inject - public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader, + public QSAnimator(@RootView View rootView, QuickQSPanel quickPanel, QSPanelController qsPanelController, QuickQSPanelController quickQSPanelController, QSHost qsTileHost, @Main Executor executor, TunerService tunerService, QSExpansionPathInterpolator qsExpansionPathInterpolator) { - mQs = qs; + mQsRootView = rootView; mQuickQsPanel = quickPanel; mQsPanelController = qsPanelController; mQuickQSPanelController = quickQSPanelController; - mQuickStatusBarHeader = quickStatusBarHeader; mHost = qsTileHost; mExecutor = executor; mQSExpansionPathInterpolator = qsExpansionPathInterpolator; mHost.addCallback(this); mQsPanelController.addOnAttachStateChangeListener(this); - qs.getView().addOnLayoutChangeListener(this); + mQsRootView.addOnLayoutChangeListener(this); if (mQsPanelController.isAttachedToWindow()) { onViewAttachedToWindow(null); } @@ -314,8 +310,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener break; } - final View tileIcon = tileView.getIcon().getIconView(); - View view = mQs.getView(); + View view = mQsRootView; // This case: less tiles to animate in small displays. if (count < mQuickQSPanelController.getTileLayout().getNumVisibleTiles()) { @@ -480,7 +475,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener .setStartDelay(QS_TILE_LABEL_FADE_OUT_START) .setEndDelay(QS_TILE_LABEL_FADE_OUT_END); SideLabelTileLayout qqsLayout = (SideLabelTileLayout) mQuickQsPanel.getTileLayout(); - View view = mQs.getView(); + View view = mQsRootView; List<String> specs = mPagedLayout.getSpecsForPage(page); if (specs.isEmpty()) { // specs should not be empty in a valid secondary page, as we scrolled to it. @@ -577,7 +572,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener // For (1), compute the distance via the vertical distance between QQS and QS tile // layout top. - View quickSettingsRootView = mQs.getView(); + View quickSettingsRootView = mQsRootView; View qsTileLayout = (View) mQsPanelController.getTileLayout(); View qqsTileLayout = (View) mQuickQSPanelController.getTileLayout(); getRelativePosition(mTmpLoc1, qsTileLayout, quickSettingsRootView); @@ -607,7 +602,7 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener private int getRelativeTranslationY(View view1, View view2) { int[] qsPosition = new int[2]; int[] qqsPosition = new int[2]; - View commonView = mQs.getView(); + View commonView = mQsRootView; getRelativePositionInt(qsPosition, view1, commonView); getRelativePositionInt(qqsPosition, view2, commonView); return qsPosition[1] - qqsPosition[1]; @@ -690,9 +685,6 @@ public class QSAnimator implements QSHost.Callback, PagedTileLayout.PageListener if (mBrightnessTranslationAnimator != null) { mBrightnessTranslationAnimator.setPosition(position); } - if (mQQSFooterActionsAnimator != null) { - mQQSFooterActionsAnimator.setPosition(position); - } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSDisableFlagsLogger.kt index 6563e425190d..6f6f467170c6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDisableFlagsLogger.kt @@ -1,20 +1,22 @@ package com.android.systemui.qs -import com.android.systemui.log.dagger.QSFragmentDisableLog import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.QSDisableLog import com.android.systemui.statusbar.disableflags.DisableFlagsLogger import javax.inject.Inject -/** A helper class for logging disable flag changes made in [QSFragment]. */ -class QSFragmentDisableFlagsLogger @Inject constructor( - @QSFragmentDisableLog private val buffer: LogBuffer, +/** A helper class for logging disable flag changes made in [QSImpl]. */ +class QSDisableFlagsLogger +@Inject +constructor( + @QSDisableLog private val buffer: LogBuffer, private val disableFlagsLogger: DisableFlagsLogger ) { /** - * Logs a string representing the new state received by [QSFragment] and any modifications that - * were made to the flags locally. + * Logs a string representing the new state received by [QSImpl] and any modifications that were + * made to the flags locally. * * @param new see [DisableFlagsLogger.getDisableFlagsString] * @param newAfterLocalModification see [DisableFlagsLogger.getDisableFlagsString] @@ -43,4 +45,4 @@ class QSFragmentDisableFlagsLogger @Inject constructor( } } -private const val TAG = "QSFragmentDisableFlagsLog" +private const val TAG = "QSDisableFlagsLog" diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java new file mode 100644 index 000000000000..8589ae9305fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.Trace; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.FloatRange; +import androidx.annotation.Nullable; + +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.plugins.qs.QSContainerController; +import com.android.systemui.qs.dagger.QSFragmentComponent; +import com.android.systemui.res.R; +import com.android.systemui.statusbar.policy.BrightnessMirrorController; +import com.android.systemui.util.LifecycleFragment; + +import java.util.function.Consumer; + +import javax.inject.Inject; +import javax.inject.Provider; + +public class QSFragmentLegacy extends LifecycleFragment implements QS { + + private final Provider<QSImpl> mQsImplProvider; + + private final QSFragmentComponent.Factory mQsComponentFactory; + + @Nullable + private QSImpl mQsImpl; + + @Inject + public QSFragmentLegacy( + Provider<QSImpl> qsImplProvider, + QSFragmentComponent.Factory qsComponentFactory + ) { + mQsComponentFactory = qsComponentFactory; + mQsImplProvider = qsImplProvider; + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + Bundle savedInstanceState) { + try { + Trace.beginSection("QSFragment#onCreateView"); + inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), + R.style.Theme_SystemUI_QuickSettings)); + return inflater.inflate(R.layout.qs_panel, container, false); + } finally { + Trace.endSection(); + } + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this); + mQsImpl = mQsImplProvider.get(); + mQsImpl.onComponentCreated(qsFragmentComponent, savedInstanceState); + } + + @Override + public void setScrollListener(ScrollListener listener) { + if (mQsImpl != null) { + mQsImpl.setScrollListener(listener); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (mQsImpl != null) { + mQsImpl.onCreate(savedInstanceState); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mQsImpl != null) { + mQsImpl.onDestroy(); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (mQsImpl != null) { + mQsImpl.onSaveInstanceState(outState); + } + } + + @Override + public View getHeader() { + if (mQsImpl != null) { + return mQsImpl.getHeader(); + } else { + return null; + } + } + + @Override + public void setHasNotifications(boolean hasNotifications) { + if (mQsImpl != null) { + mQsImpl.setHasNotifications(hasNotifications); + } + } + + @Override + public void setPanelView(HeightListener panelView) { + if (mQsImpl != null) { + mQsImpl.setPanelView(panelView); + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (mQsImpl != null) { + mQsImpl.onConfigurationChanged(newConfig); + } + } + + @Override + public void setFancyClipping(int leftInset, int top, int rightInset, int bottom, + int cornerRadius, boolean visible, boolean fullWidth) { + if (mQsImpl != null) { + mQsImpl.setFancyClipping(leftInset, top, rightInset, bottom, cornerRadius, visible, + fullWidth); + } + } + + @Override + public boolean isFullyCollapsed() { + if (mQsImpl != null) { + return mQsImpl.isFullyCollapsed(); + } else { + return true; + } + } + + @Override + public void setCollapsedMediaVisibilityChangedListener(Consumer<Boolean> listener) { + if (mQsImpl != null) { + mQsImpl.setCollapsedMediaVisibilityChangedListener(listener); + } + } + + @Override + public void setContainerController(QSContainerController controller) { + if (mQsImpl != null) { + mQsImpl.setContainerController(controller); + } + } + + @Override + public boolean isCustomizing() { + if (mQsImpl != null) { + return mQsImpl.isCustomizing(); + } else { + return false; + } + } + + public QSPanelController getQSPanelController() { + if (mQsImpl != null) { + return mQsImpl.getQSPanelController(); + } else { + return null; + } + } + + public void setBrightnessMirrorController( + BrightnessMirrorController brightnessMirrorController) { + if (mQsImpl != null) { + mQsImpl.setBrightnessMirrorController(brightnessMirrorController); + } + } + + @Override + public boolean isShowingDetail() { + if (mQsImpl != null) { + return mQsImpl.isShowingDetail(); + } else { + return false; + } + } + + @Override + public void setHeaderClickable(boolean clickable) { + if (mQsImpl != null) { + mQsImpl.setHeaderClickable(clickable); + } + } + + @Override + public void setExpanded(boolean expanded) { + if (mQsImpl != null) { + mQsImpl.setExpanded(expanded); + } + } + + @Override + public void setOverscrolling(boolean stackScrollerOverscrolling) { + if (mQsImpl != null) { + mQsImpl.setOverscrolling(stackScrollerOverscrolling); + } + } + + @Override + public void setListening(boolean listening) { + if (mQsImpl != null) { + mQsImpl.setListening(listening); + } + } + + @Override + public void setQsVisible(boolean visible) { + if (mQsImpl != null) { + mQsImpl.setQsVisible(visible); + } + } + + @Override + public void setHeaderListening(boolean listening) { + if (mQsImpl != null) { + mQsImpl.setHeaderListening(listening); + } + } + + @Override + public void notifyCustomizeChanged() { + if (mQsImpl != null) { + mQsImpl.notifyCustomizeChanged(); + } + } + + @Override + public void setInSplitShade(boolean inSplitShade) { + if (mQsImpl != null) { + mQsImpl.setInSplitShade(inSplitShade); + } + } + + @Override + public void setTransitionToFullShadeProgress( + boolean isTransitioningToFullShade, + @FloatRange(from = 0.0, to = 1.0) float qsTransitionFraction, + @FloatRange(from = 0.0, to = 1.0) float qsSquishinessFraction) { + if (mQsImpl != null) { + mQsImpl.setTransitionToFullShadeProgress(isTransitioningToFullShade, + qsTransitionFraction, qsSquishinessFraction); + } + } + + @Override + public void setOverScrollAmount(int overScrollAmount) { + if (mQsImpl != null) { + mQsImpl.setOverScrollAmount(overScrollAmount); + } + } + + @Override + public int getHeightDiff() { + if (mQsImpl != null) { + return mQsImpl.getHeightDiff(); + } else { + return 0; + } + } + + @Override + public void setIsNotificationPanelFullWidth(boolean isFullWidth) { + if (mQsImpl != null) { + mQsImpl.setIsNotificationPanelFullWidth(isFullWidth); + } + } + + @Override + public void setQsExpansion(float expansion, float panelExpansionFraction, + float proposedTranslation, float squishinessFraction) { + if (mQsImpl != null) { + mQsImpl.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, + squishinessFraction); + } + } + + @Override + public void animateHeaderSlidingOut() { + if (mQsImpl != null) { + mQsImpl.animateHeaderSlidingOut(); + } + } + + @Override + public void setCollapseExpandAction(Runnable action) { + if (mQsImpl != null) { + mQsImpl.setCollapseExpandAction(action); + } + } + + @Override + public void closeDetail() { + if (mQsImpl != null) { + mQsImpl.closeDetail(); + } + } + + @Override + public void closeCustomizer() { + if (mQsImpl != null) { + mQsImpl.closeDetail(); + } + } + + /** + * The height this view wants to be. This is different from {@link View#getMeasuredHeight} such + * that during closing the detail panel, this already returns the smaller height. + */ + @Override + public int getDesiredHeight() { + if (mQsImpl != null) { + return mQsImpl.getDesiredHeight(); + } else { + return 0; + } + } + + @Override + public void setHeightOverride(int desiredHeight) { + if (mQsImpl != null) { + mQsImpl.setHeightOverride(desiredHeight); + } + } + + @Override + public int getQsMinExpansionHeight() { + if (mQsImpl != null) { + return mQsImpl.getQsMinExpansionHeight(); + } else { + return 0; + } + } + + @Override + public void hideImmediately() { + if (mQsImpl != null) { + mQsImpl.hideImmediately(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt index 253560b93f1a..9fa6769fe5f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt @@ -31,10 +31,13 @@ class QSFragmentStartable @Inject constructor( private val fragmentService: FragmentService, - private val qsFragmentProvider: Provider<QSFragment> + private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy> ) : CoreStartable { override fun start() { - fragmentService.addFragmentInstantiationProvider(QSFragment::class.java, qsFragmentProvider) + fragmentService.addFragmentInstantiationProvider( + QSFragmentLegacy::class.java, + qsFragmentLegacyProvider + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index fd81e9a5cd0a..a32a024dbb44 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -1,15 +1,17 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package com.android.systemui.qs; @@ -20,21 +22,18 @@ import static com.android.systemui.media.dagger.MediaModule.QS_PANEL; import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; -import static com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.os.Bundle; -import android.os.Trace; import android.util.IndentingPrintWriter; import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.widget.LinearLayout; import androidx.annotation.FloatRange; @@ -47,7 +46,6 @@ import androidx.lifecycle.LifecycleRegistry; import com.android.app.animation.Interpolators; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.compose.ComposeFacade; import com.android.systemui.dump.DumpManager; @@ -58,19 +56,20 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.customize.QSCustomizerController; -import com.android.systemui.qs.dagger.QSFragmentComponent; +import com.android.systemui.qs.dagger.QSComponent; import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.res.R; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.util.LifecycleFragment; import com.android.systemui.util.Utils; import java.io.PrintWriter; @@ -80,8 +79,8 @@ import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Named; -public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Callbacks, - StatusBarStateController.StateListener, Dumpable { +public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateController.StateListener, + Dumpable { private static final String TAG = "QS"; private static final boolean DEBUG = false; private static final String EXTRA_EXPANDED = "expanded"; @@ -113,8 +112,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; private final MediaHost mQsMediaHost; private final MediaHost mQqsMediaHost; - private final QSFragmentComponent.Factory mQsComponentFactory; - private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger; + private final QSDisableFlagsLogger mQsDisableFlagsLogger; private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; private final FeatureFlags mFeatureFlags; private final QSLogger mLogger; @@ -167,14 +165,17 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private boolean mIsSmallScreen; + private CommandQueue mCommandQueue; + + private View mRootView; + @Inject - public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, + public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue, @Named(QS_PANEL) MediaHost qsMediaHost, @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost, KeyguardBypassController keyguardBypassController, - QSFragmentComponent.Factory qsComponentFactory, - QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger, + QSDisableFlagsLogger qsDisableFlagsLogger, DumpManager dumpManager, QSLogger qsLogger, FooterActionsController footerActionsController, FooterActionsViewModel.Factory footerActionsViewModelFactory, @@ -184,12 +185,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mQsMediaHost = qsMediaHost; mQqsMediaHost = qqsMediaHost; - mQsComponentFactory = qsComponentFactory; - mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger; + mQsDisableFlagsLogger = qsDisableFlagsLogger; mLogger = qsLogger; mLargeScreenShadeInterpolator = largeScreenShadeInterpolator; mFeatureFlags = featureFlags; - commandQueue.observe(getLifecycle(), this); + mCommandQueue = commandQueue; mBypassController = keyguardBypassController; mStatusBarStateController = statusBarStateController; mDumpManager = dumpManager; @@ -199,34 +199,23 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner(); } - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, - Bundle savedInstanceState) { - try { - Trace.beginSection("QSFragment#onCreateView"); - inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), - R.style.Theme_SystemUI_QuickSettings)); - return inflater.inflate(R.layout.qs_panel, container, false); - } finally { - Trace.endSection(); - } - } + public void onComponentCreated(QSComponent qsComponent, @Nullable Bundle savedInstanceState) { + mRootView = qsComponent.getRootView(); - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this); - mQSPanelController = qsFragmentComponent.getQSPanelController(); - mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController(); + mCommandQueue.addCallback(this); + + mQSPanelController = qsComponent.getQSPanelController(); + mQuickQSPanelController = qsComponent.getQuickQSPanelController(); mQSPanelController.init(); mQuickQSPanelController.init(); - mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */ - this); - bindFooterActionsView(view); + mQSFooterActionsViewModel = mFooterActionsViewModelFactory + .create(mListeningAndVisibilityLifecycleOwner); + bindFooterActionsView(mRootView); mFooterActionsController.init(); - mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view); + mQSPanelScrollView = mRootView.findViewById(R.id.expanded_qs_scroll_view); mQSPanelScrollView.addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { updateQsBounds(); @@ -238,26 +227,26 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca if (mScrollListener != null) { mScrollListener.onQsPanelScrollChanged(scrollY); } - }); - mHeader = view.findViewById(R.id.header); - mFooter = qsFragmentComponent.getQSFooter(); + }); + mHeader = mRootView.findViewById(R.id.header); + mFooter = qsComponent.getQSFooter(); - mQSContainerImplController = qsFragmentComponent.getQSContainerImplController(); + mQSContainerImplController = qsComponent.getQSContainerImplController(); mQSContainerImplController.init(); mContainer = mQSContainerImplController.getView(); mDumpManager.registerDumpable(mContainer.getClass().getSimpleName(), mContainer); - mQSAnimator = qsFragmentComponent.getQSAnimator(); - mQSSquishinessController = qsFragmentComponent.getQSSquishinessController(); + mQSAnimator = qsComponent.getQSAnimator(); + mQSSquishinessController = qsComponent.getQSSquishinessController(); - mQSCustomizerController = qsFragmentComponent.getQSCustomizerController(); + mQSCustomizerController = qsComponent.getQSCustomizerController(); mQSCustomizerController.init(); mQSCustomizerController.setQs(this); if (savedInstanceState != null) { setQsVisible(savedInstanceState.getBoolean(EXTRA_VISIBLE)); setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED)); setListening(savedInstanceState.getBoolean(EXTRA_LISTENING)); - setEditLocation(view); + setEditLocation(mRootView); mQSCustomizerController.restoreInstanceState(savedInstanceState); if (mQsExpanded) { mQSPanelController.getTileLayout().restoreInstanceState(savedInstanceState); @@ -265,7 +254,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } mStatusBarStateController.addCallback(this); onStateChanged(mStatusBarStateController.getState()); - view.addOnLayoutChangeListener( + mRootView.addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { boolean sizeChanged = (oldTop - oldBottom) != (top - bottom); if (sizeChanged) { @@ -327,15 +316,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mScrollListener = listener; } - @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); mDumpManager.registerDumpable(getClass().getSimpleName(), this); } - @Override public void onDestroy() { - super.onDestroy(); + mCommandQueue.removeCallback(this); mStatusBarStateController.removeCallback(this); if (mListening) { setListening(false); @@ -351,9 +337,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mListeningAndVisibilityLifecycleOwner.destroy(); } - @Override public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); outState.putBoolean(EXTRA_EXPANDED, mQsExpanded); outState.putBoolean(EXTRA_LISTENING, mListening); outState.putBoolean(EXTRA_VISIBLE, mQsVisible); @@ -394,9 +378,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mPanelView = panelView; } - @Override public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); setEditLocation(getView()); if (newConfig.getLayoutDirection() != mLayoutDirection) { mLayoutDirection = newConfig.getLayoutDirection(); @@ -452,9 +434,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca int state2BeforeAdjustment = state2; state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2); - mQsFragmentDisableFlagsLogger.logDisableFlagChange( - /* new= */ new DisableState(state1, state2BeforeAdjustment), - /* newAfterLocalModification= */ new DisableState(state1, state2) + mQsDisableFlagsLogger.logDisableFlagChange( + /* new= */ new DisableFlagsLogger.DisableState(state1, state2BeforeAdjustment), + /* newAfterLocalModification= */ new DisableFlagsLogger.DisableState(state1, state2) ); final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0; @@ -919,32 +901,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca getView().setY(-getQsMinExpansionHeight()); } - private final ViewTreeObserver.OnPreDrawListener mStartHeaderSlidingIn - = new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - getView().getViewTreeObserver().removeOnPreDrawListener(this); - getView().animate() - .translationY(0f) - .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .setListener(mAnimateHeaderSlidingInListener) - .start(); - return true; - } - }; - - private final Animator.AnimatorListener mAnimateHeaderSlidingInListener - = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mHeaderAnimating = false; - updateQsState(); - // Unset the listener, otherwise this may persist for another view property animation - getView().animate().setListener(null); - } - }; - @Override public void onUpcomingStateChanged(int upcomingState) { if (upcomingState == KEYGUARD) { @@ -1030,6 +986,20 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca return "GONE"; } + @Override + public View getView() { + return mRootView; + } + + @Override + public Context getContext() { + return mRootView.getContext(); + } + + private Resources getResources() { + return getContext().getResources(); + } + /** * A {@link LifecycleOwner} whose state is driven by the current state of this fragment: * diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 9359958397d1..6bbdc54d260d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -19,7 +19,7 @@ package com.android.systemui.qs; import static com.android.systemui.classifier.Classifier.QS_SWIPE_SIDE; import static com.android.systemui.media.dagger.MediaModule.QS_PANEL; import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS; -import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; +import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_MEDIA_PLAYER; import android.view.MotionEvent; import android.view.View; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index ef8167420f13..60c92c000760 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -17,7 +17,6 @@ package com.android.systemui.qs; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; import android.annotation.NonNull; import android.annotation.Nullable; @@ -44,9 +43,6 @@ import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.ViewController; import com.android.systemui.util.animation.DisappearParameters; -import kotlin.Unit; -import kotlin.jvm.functions.Function1; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; @@ -54,7 +50,8 @@ import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Collectors; -import javax.inject.Named; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; /** * Controller for QSPanel views. @@ -135,7 +132,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr T view, QSHost host, QSCustomizerController qsCustomizerController, - @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, + boolean usingMediaPlayer, MediaHost mediaHost, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 9a9626d7c7a0..10f95e0b7201 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -48,6 +48,7 @@ import com.android.systemui.qs.nano.QsTileState; import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository; +import com.android.systemui.qs.tiles.di.NewQSTileFactory; import com.android.systemui.settings.UserFileManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; @@ -56,6 +57,8 @@ import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.settings.SecureSettings; +import dagger.Lazy; + import org.jetbrains.annotations.NotNull; import java.io.PrintWriter; @@ -121,6 +124,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P @Inject public QSTileHost(Context context, + Lazy<NewQSTileFactory> newQsTileFactoryProvider, QSFactory defaultFactory, @Main Executor mainExecutor, PluginManager pluginManager, @@ -147,6 +151,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P mShadeController = shadeController; + if (featureFlags.getPipelineTilesEnabled()) { + mQsFactories.add(newQsTileFactoryProvider.get()); + } mQsFactories.add(defaultFactory); pluginManager.addPluginListener(this, QSFactory.class, true); mUserTracker = userTracker; @@ -326,7 +333,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P try { tile = createTile(tileSpec); if (tile != null) { - tile.setTileSpec(tileSpec); if (tile.isAvailable()) { newTiles.put(tileSpec, tile); mQSLogger.logTileAdded(tileSpec); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index 099d19d8619f..f278dce047e0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -17,14 +17,13 @@ package com.android.systemui.qs; import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; -import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_COLLAPSED_LANDSCAPE_MEDIA; -import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; +import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_COLLAPSED_LANDSCAPE_MEDIA; +import static com.android.systemui.qs.dagger.QSScopeModule.QS_USING_MEDIA_PLAYER; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; -import com.android.systemui.res.R; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.media.controls.ui.MediaHost; @@ -32,6 +31,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.leak.RotationUtils; diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index 7888f4c7388d..a103566400a6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -33,11 +33,11 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.RecyclerView; -import com.android.systemui.res.R; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.qs.QSDetailClipper; import com.android.systemui.qs.QSUtils; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.LightBarController; /** @@ -135,8 +135,10 @@ public class QSCustomizer extends LinearLayout { setVisibility(View.VISIBLE); long duration = mClipper.animateCircularClip( mX, mY, true, new ExpandAnimatorListener(tileAdapter)); - mQsContainerController.setCustomizerAnimating(true); - mQsContainerController.setCustomizerShowing(true, duration); + if (mQsContainerController != null) { + mQsContainerController.setCustomizerAnimating(true); + mQsContainerController.setCustomizerShowing(true, duration); + } } } @@ -150,8 +152,10 @@ public class QSCustomizer extends LinearLayout { mClipper.showBackground(); isShown = true; setCustomizing(true); - mQsContainerController.setCustomizerAnimating(false); - mQsContainerController.setCustomizerShowing(true); + if (mQsContainerController != null) { + mQsContainerController.setCustomizerAnimating(false); + mQsContainerController.setCustomizerShowing(true); + } } } @@ -169,8 +173,10 @@ public class QSCustomizer extends LinearLayout { } else { setVisibility(View.GONE); } - mQsContainerController.setCustomizerAnimating(animate); - mQsContainerController.setCustomizerShowing(false, duration); + if (mQsContainerController != null) { + mQsContainerController.setCustomizerAnimating(animate); + mQsContainerController.setCustomizerShowing(false, duration); + } } } @@ -180,7 +186,9 @@ public class QSCustomizer extends LinearLayout { void setCustomizing(boolean customizing) { mCustomizing = customizing; - mQs.notifyCustomizeChanged(); + if (mQs != null) { + mQs.notifyCustomizeChanged(); + } } public boolean isCustomizing() { @@ -208,15 +216,21 @@ public class QSCustomizer extends LinearLayout { setCustomizing(true); } mOpening = false; - mQsContainerController.setCustomizerAnimating(false); + if (mQsContainerController != null) { + mQsContainerController.setCustomizerAnimating(false); + } mRecyclerView.setAdapter(mTileAdapter); } @Override public void onAnimationCancel(Animator animation) { mOpening = false; - mQs.notifyCustomizeChanged(); - mQsContainerController.setCustomizerAnimating(false); + if (mQs != null) { + mQs.notifyCustomizeChanged(); + } + if (mQsContainerController != null) { + mQsContainerController.setCustomizerAnimating(false); + } } } @@ -226,7 +240,9 @@ public class QSCustomizer extends LinearLayout { if (!isShown) { setVisibility(View.GONE); } - mQsContainerController.setCustomizerAnimating(false); + if (mQsContainerController != null) { + mQsContainerController.setCustomizerAnimating(false); + } } @Override @@ -234,7 +250,9 @@ public class QSCustomizer extends LinearLayout { if (!isShown) { setVisibility(View.GONE); } - mQsContainerController.setCustomizerAnimating(false); + if (mQsContainerController != null) { + mQsContainerController.setCustomizerAnimating(false); + } } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java index ce504b2c10a6..024e760e6ed1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java @@ -34,14 +34,14 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.android.internal.logging.UiEventLogger; -import com.android.systemui.res.R; import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSEditEvent; -import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.dagger.QSScope; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -199,7 +199,7 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { } /** */ - public void setQs(@Nullable QSFragment qsFragment) { + public void setQs(@Nullable QS qsFragment) { mView.setQs(qsFragment); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index a6226b36b0fd..2af7ae0614ac 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -130,11 +130,9 @@ public class TileQueryHelper { if (tile == null) { continue; } else if (!tile.isAvailable()) { - tile.setTileSpec(spec); tile.destroy(); continue; } - tile.setTileSpec(spec); tilesToAdd.add(tile); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSComponent.kt new file mode 100644 index 000000000000..f3413b80a2ef --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSComponent.kt @@ -0,0 +1,40 @@ +package com.android.systemui.qs.dagger + +import android.view.View +import com.android.systemui.dagger.qualifiers.RootView +import com.android.systemui.qs.FooterActionsController +import com.android.systemui.qs.QSAnimator +import com.android.systemui.qs.QSContainerImplController +import com.android.systemui.qs.QSFooter +import com.android.systemui.qs.QSPanelController +import com.android.systemui.qs.QSSquishinessController +import com.android.systemui.qs.QuickQSPanelController +import com.android.systemui.qs.customize.QSCustomizerController + +interface QSComponent { + /** Construct a [QSPanelController]. */ + fun getQSPanelController(): QSPanelController + + /** Construct a [QuickQSPanelController]. */ + fun getQuickQSPanelController(): QuickQSPanelController + + /** Construct a [QSAnimator]. */ + fun getQSAnimator(): QSAnimator + + /** Construct a [QSContainerImplController]. */ + fun getQSContainerImplController(): QSContainerImplController + + /** Construct a [QSFooter] */ + fun getQSFooter(): QSFooter + + /** Construct a [QSCustomizerController]. */ + fun getQSCustomizerController(): QSCustomizerController + + /** Construct a [QSSquishinessController]. */ + fun getQSSquishinessController(): QSSquishinessController + + /** Construct a [FooterActionsController]. */ + fun getQSFooterActionController(): FooterActionsController + + @RootView fun getRootView(): View +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt new file mode 100644 index 000000000000..ba1aa629f8cd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.dagger + +import android.view.View +import com.android.systemui.dagger.qualifiers.RootView +import dagger.BindsInstance +import dagger.Subcomponent + +@Subcomponent(modules = [QSFlexiglassModule::class]) +@QSScope +interface QSFlexiglassComponent : QSComponent { + + @Subcomponent.Factory + interface Factory { + fun create(@RootView @BindsInstance rootView: View): QSFlexiglassComponent + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt new file mode 100644 index 000000000000..36fac44e0173 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.dagger + +import android.content.Context +import com.android.systemui.qs.dagger.QSScopeModule.Companion.QS_USING_COLLAPSED_LANDSCAPE_MEDIA +import com.android.systemui.qs.dagger.QSScopeModule.Companion.QS_USING_MEDIA_PLAYER +import dagger.Module +import dagger.Provides +import javax.inject.Named + +@Module(includes = [QSScopeModule::class]) +interface QSFlexiglassModule { + + @Module + companion object { + + /** */ + @Provides + @Named(QS_USING_MEDIA_PLAYER) + @JvmStatic + fun providesQSUsingMediaPlayer(context: Context?): Boolean { + return false + } + + /** */ + @Provides + @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) + @JvmStatic + fun providesQSUsingCollapsedLandscapeMedia(context: Context): Boolean { + return false + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java index 594f4f86a680..327e858059f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java @@ -16,53 +16,21 @@ package com.android.systemui.qs.dagger; -import com.android.systemui.qs.FooterActionsController; -import com.android.systemui.qs.QSAnimator; -import com.android.systemui.qs.QSContainerImplController; -import com.android.systemui.qs.QSFooter; -import com.android.systemui.qs.QSFragment; -import com.android.systemui.qs.QSPanelController; -import com.android.systemui.qs.QSSquishinessController; -import com.android.systemui.qs.QuickQSPanelController; -import com.android.systemui.qs.customize.QSCustomizerController; +import com.android.systemui.qs.QSFragmentLegacy; import dagger.BindsInstance; import dagger.Subcomponent; /** - * Dagger Subcomponent for {@link QSFragment}. + * Dagger Subcomponent for {@link QSFragmentLegacy}. */ @Subcomponent(modules = {QSFragmentModule.class}) @QSScope -public interface QSFragmentComponent { +public interface QSFragmentComponent extends QSComponent { /** Factory for building a {@link QSFragmentComponent}. */ @Subcomponent.Factory interface Factory { - QSFragmentComponent create(@BindsInstance QSFragment qsFragment); + QSFragmentComponent create(@BindsInstance QSFragmentLegacy qsFragment); } - - /** Construct a {@link QSPanelController}. */ - QSPanelController getQSPanelController(); - - /** Construct a {@link QuickQSPanelController}. */ - QuickQSPanelController getQuickQSPanelController(); - - /** Construct a {@link QSAnimator}. */ - QSAnimator getQSAnimator(); - - /** Construct a {@link QSContainerImplController}. */ - QSContainerImplController getQSContainerImplController(); - - /** Construct a {@link QSFooter} */ - QSFooter getQSFooter(); - - /** Construct a {@link QSCustomizerController}. */ - QSCustomizerController getQSCustomizerController(); - - /** Construct a {@link QSSquishinessController}. */ - QSSquishinessController getQSSquishinessController(); - - /** Construct a {@link FooterActionsController}. */ - FooterActionsController getQSFooterActionController(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java index bcd98035a8b9..0c9c24df5e36 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java @@ -20,21 +20,11 @@ import static com.android.systemui.util.Utils.useCollapsedMediaInLandscape; import static com.android.systemui.util.Utils.useQsMediaPlayer; import android.content.Context; -import android.view.LayoutInflater; import android.view.View; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.plugins.qs.QS; -import com.android.systemui.qs.QSContainerImpl; -import com.android.systemui.qs.QSFooter; -import com.android.systemui.qs.QSFooterView; -import com.android.systemui.qs.QSFooterViewController; -import com.android.systemui.qs.QSFragment; -import com.android.systemui.qs.QSPanel; -import com.android.systemui.qs.QuickQSPanel; -import com.android.systemui.qs.QuickStatusBarHeader; -import com.android.systemui.qs.customize.QSCustomizer; +import com.android.systemui.qs.QSFragmentLegacy; import javax.inject.Named; @@ -45,93 +35,31 @@ import dagger.Provides; /** * Dagger Module for {@link QSFragmentComponent}. */ -@Module -public interface QSFragmentModule { - String QS_USING_MEDIA_PLAYER = "qs_using_media_player"; - String QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media"; +@Module(includes = {QSScopeModule.class}) +public interface QSFragmentModule { - /** - * Provide a context themed using the QS theme - */ - @Provides - @QSThemedContext - static Context provideThemedContext(@RootView View view) { - return view.getContext(); - } - - /** */ - @Provides - @QSThemedContext - static LayoutInflater provideThemedLayoutInflater(@QSThemedContext Context context) { - return LayoutInflater.from(context); - } - - /** */ @Provides @RootView - static View provideRootView(QSFragment qsFragment) { + static View provideRootView(QSFragmentLegacy qsFragment) { return qsFragment.getView(); } /** */ - @Provides - static QSPanel provideQSPanel(@RootView View view) { - return view.findViewById(R.id.quick_settings_panel); - } - - /** */ - @Provides - static QSContainerImpl providesQSContainerImpl(@RootView View view) { - return view.findViewById(R.id.quick_settings_container); - } - - /** */ @Binds - QS bindQS(QSFragment qsFragment); - - /** */ - @Provides - static QuickStatusBarHeader providesQuickStatusBarHeader(@RootView View view) { - return view.findViewById(R.id.header); - } - - /** */ - @Provides - static QuickQSPanel providesQuickQSPanel(QuickStatusBarHeader quickStatusBarHeader) { - return quickStatusBarHeader.findViewById(R.id.quick_qs_panel); - } + QS bindQS(QSFragmentLegacy qsFragment); /** */ @Provides - static QSFooterView providesQSFooterView(@RootView View view) { - return view.findViewById(R.id.qs_footer); - } - - /** */ - @Provides - @QSScope - static QSFooter providesQSFooter(QSFooterViewController qsFooterViewController) { - qsFooterViewController.init(); - return qsFooterViewController; - } - - /** */ - @Provides - @QSScope - static QSCustomizer providesQSCutomizer(@RootView View view) { - return view.findViewById(R.id.qs_customize); - } - - /** */ - @Provides - @Named(QS_USING_MEDIA_PLAYER) + @Named(QSScopeModule.QS_USING_MEDIA_PLAYER) static boolean providesQSUsingMediaPlayer(Context context) { return useQsMediaPlayer(context); } + + /** */ @Provides - @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) + @Named(QSScopeModule.QS_USING_COLLAPSED_LANDSCAPE_MEDIA) static boolean providesQSUsingCollapsedLandscapeMedia(Context context) { return useCollapsedMediaInLandscape(context.getResources()); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index 03de3a02da13..a65967a0349b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -31,6 +31,7 @@ import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.external.QSExternalModule; import com.android.systemui.qs.pipeline.dagger.QSPipelineModule; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.policy.CastController; @@ -41,18 +42,18 @@ import com.android.systemui.statusbar.policy.SafetyController; import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.util.settings.SecureSettings; -import java.util.Map; - -import javax.inject.Named; - import dagger.Module; import dagger.Provides; import dagger.multibindings.Multibinds; +import java.util.Map; + +import javax.inject.Named; + /** * Module for QS dependencies */ -@Module(subcomponents = {QSFragmentComponent.class}, +@Module(subcomponents = {QSFragmentComponent.class, QSFlexiglassComponent.class}, includes = { MediaModule.class, QSExternalModule.class, @@ -68,6 +69,11 @@ public interface QSModule { @Multibinds Map<String, QSTileImpl<?>> tileMap(); + /** A map of internal QS tile ViewModels. Ensures that this can be injected even if + * it is empty */ + @Multibinds + Map<String, QSTileViewModel> tileViewModelMap(); + @Provides @SysUISingleton static AutoTileManager provideAutoTileManager( diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSScopeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSScopeModule.kt new file mode 100644 index 000000000000..e68ec4c7c0ac --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSScopeModule.kt @@ -0,0 +1,92 @@ +package com.android.systemui.qs.dagger + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import com.android.systemui.dagger.qualifiers.RootView +import com.android.systemui.qs.QSContainerImpl +import com.android.systemui.qs.QSFooter +import com.android.systemui.qs.QSFooterView +import com.android.systemui.qs.QSFooterViewController +import com.android.systemui.qs.QSPanel +import com.android.systemui.qs.QuickQSPanel +import com.android.systemui.qs.QuickStatusBarHeader +import com.android.systemui.qs.customize.QSCustomizer +import com.android.systemui.res.R +import dagger.Module +import dagger.Provides + +@Module +interface QSScopeModule { + companion object { + const val QS_USING_MEDIA_PLAYER = "qs_using_media_player" + const val QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media" + + @Provides + @QSThemedContext + @JvmStatic + fun provideThemedContext(@RootView view: View): Context { + return view.context + } + + /** */ + @Provides + @QSThemedContext + @JvmStatic + fun provideThemedLayoutInflater(@QSThemedContext context: Context): LayoutInflater { + return LayoutInflater.from(context) + } + + /** */ + @Provides + @JvmStatic + fun provideQSPanel(@RootView view: View): QSPanel { + return view.requireViewById<QSPanel>(R.id.quick_settings_panel) + } + + /** */ + @Provides + @JvmStatic + fun providesQSContainerImpl(@RootView view: View): QSContainerImpl { + return view.requireViewById<QSContainerImpl>(R.id.quick_settings_container) + } + + /** */ + @Provides + @JvmStatic + fun providesQuickStatusBarHeader(@RootView view: View): QuickStatusBarHeader { + return view.requireViewById<QuickStatusBarHeader>(R.id.header) + } + + /** */ + @Provides + @JvmStatic + fun providesQuickQSPanel(quickStatusBarHeader: QuickStatusBarHeader): QuickQSPanel { + return quickStatusBarHeader.requireViewById<QuickQSPanel>(R.id.quick_qs_panel) + } + + /** */ + @Provides + @JvmStatic + fun providesQSFooterView(@RootView view: View): QSFooterView { + return view.requireViewById<QSFooterView>(R.id.qs_footer) + } + + /** */ + @Provides + @QSScope + @JvmStatic + fun providesQSFooter(qsFooterViewController: QSFooterViewController): QSFooter { + qsFooterViewController.init() + return qsFooterViewController + } + + /** */ + @Provides + @QSScope + @JvmStatic + fun providesQSCutomizer(@RootView view: View): QSCustomizer { + return view.requireViewById<QSCustomizer>(R.id.qs_customize) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index 34d6233deddb..128c23745e5f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -17,6 +17,7 @@ package com.android.systemui.qs.external; import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG; +import android.app.IUriGrantsManager; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -31,6 +32,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.Process; import android.os.RemoteException; import android.provider.Settings; import android.service.quicksettings.IQSTileService; @@ -43,11 +45,9 @@ import android.view.View; import android.view.WindowManagerGlobal; import android.widget.Switch; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.animation.ActivityLaunchAnimator; @@ -67,9 +67,10 @@ import com.android.systemui.settings.DisplayTracker; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; -import javax.inject.Inject; - import dagger.Lazy; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; public class CustomTile extends QSTileImpl<State> implements TileChangeListener { public static final String PREFIX = "custom("; @@ -109,24 +110,30 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private final AtomicBoolean mInitialDefaultIconFetched = new AtomicBoolean(false); private final TileServices mTileServices; - private CustomTile( - QSHost host, + private int mServiceUid = Process.INVALID_UID; + + private final IUriGrantsManager mIUriGrantsManager; + + @AssistedInject + CustomTile( + Lazy<QSHost> host, QsEventLogger uiEventLogger, - Looper backgroundLooper, - Handler mainHandler, + @Background Looper backgroundLooper, + @Main Handler mainHandler, FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, - String action, - Context userContext, + @Assisted String action, + @Assisted Context userContext, CustomTileStatePersister customTileStatePersister, TileServices tileServices, - DisplayTracker displayTracker + DisplayTracker displayTracker, + IUriGrantsManager uriGrantsManager ) { - super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, - statusBarStateController, activityStarter, qsLogger); + super(host.get(), uiEventLogger, backgroundLooper, mainHandler, falsingManager, + metricsLogger, statusBarStateController, activityStarter, qsLogger); mTileServices = tileServices; mWindowManager = WindowManagerGlobal.getWindowManagerService(); mComponent = ComponentName.unflattenFromString(action); @@ -139,6 +146,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mService = mServiceManager.getTileService(); mCustomTileStatePersister = customTileStatePersister; mDisplayTracker = displayTracker; + mIUriGrantsManager = uriGrantsManager; } @Override @@ -268,7 +276,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener * * @param tile tile populated with state to apply */ - public void updateTileState(Tile tile) { + public void updateTileState(Tile tile, int appUid) { + mServiceUid = appUid; // This comes from a binder call IQSService.updateQsTile mHandler.post(() -> handleUpdateTileState(tile)); } @@ -433,14 +442,25 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener state.state = tileState; Drawable drawable = null; try { - drawable = mTile.getIcon().loadDrawable(mUserContext); + drawable = mTile.getIcon().loadDrawableCheckingUriGrant( + mUserContext, + mIUriGrantsManager, + mServiceUid, + mComponent.getPackageName() + ); } catch (Exception e) { Log.w(TAG, "Invalid icon, forcing into unavailable state"); state.state = Tile.STATE_UNAVAILABLE; - drawable = mDefaultIcon.loadDrawable(mUserContext); } - final Drawable drawableF = drawable; + final Drawable drawableF; + if (drawable != null) { + drawableF = drawable; + } else if (mDefaultIcon != null) { + drawableF = mDefaultIcon.loadDrawable(mUserContext); + } else { + drawableF = null; + } state.iconSupplier = () -> { if (drawableF == null) return null; Drawable.ConstantState cs = drawableF.getConstantState(); @@ -543,96 +563,17 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener /** * Create a {@link CustomTile} for a given spec and user. * - * @param builder including injected common dependencies. + * @param factory including injected common dependencies. * @param spec as provided by {@link CustomTile#toSpec} * @param userContext context for the user that is creating this tile. * @return a new {@link CustomTile} */ - public static CustomTile create(Builder builder, String spec, Context userContext) { - return builder - .setSpec(spec) - .setUserContext(userContext) - .build(); - } - - public static class Builder { - final Lazy<QSHost> mQSHostLazy; - final QsEventLogger mUiEventLogger; - final Looper mBackgroundLooper; - final Handler mMainHandler; - private final FalsingManager mFalsingManager; - final MetricsLogger mMetricsLogger; - final StatusBarStateController mStatusBarStateController; - final ActivityStarter mActivityStarter; - final QSLogger mQSLogger; - final CustomTileStatePersister mCustomTileStatePersister; - private TileServices mTileServices; - final DisplayTracker mDisplayTracker; - - Context mUserContext; - String mSpec = ""; - - @Inject - public Builder( - Lazy<QSHost> hostLazy, - QsEventLogger uiEventLogger, - @Background Looper backgroundLooper, - @Main Handler mainHandler, - FalsingManager falsingManager, - MetricsLogger metricsLogger, - StatusBarStateController statusBarStateController, - ActivityStarter activityStarter, - QSLogger qsLogger, - CustomTileStatePersister customTileStatePersister, - TileServices tileServices, - DisplayTracker displayTracker - ) { - mQSHostLazy = hostLazy; - mUiEventLogger = uiEventLogger; - mBackgroundLooper = backgroundLooper; - mMainHandler = mainHandler; - mFalsingManager = falsingManager; - mMetricsLogger = metricsLogger; - mStatusBarStateController = statusBarStateController; - mActivityStarter = activityStarter; - mQSLogger = qsLogger; - mCustomTileStatePersister = customTileStatePersister; - mTileServices = tileServices; - mDisplayTracker = displayTracker; - } - - Builder setSpec(@NonNull String spec) { - mSpec = spec; - return this; - } - - Builder setUserContext(@NonNull Context userContext) { - mUserContext = userContext; - return this; - } - - @VisibleForTesting - public CustomTile build() { - if (mUserContext == null) { - throw new NullPointerException("UserContext cannot be null"); - } - String action = getAction(mSpec); - return new CustomTile( - mQSHostLazy.get(), - mUiEventLogger, - mBackgroundLooper, - mMainHandler, - mFalsingManager, - mMetricsLogger, - mStatusBarStateController, - mActivityStarter, - mQSLogger, - action, - mUserContext, - mCustomTileStatePersister, - mTileServices, - mDisplayTracker - ); - } + public static CustomTile create(Factory factory, String spec, Context userContext) { + return factory.create(getAction(spec), userContext); + } + + @AssistedFactory + public interface Factory { + CustomTile create(String action, Context userContext); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt index 1659c3e673ef..c3c587de5a24 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.external +import android.app.IUriGrantsManager import android.content.Context import android.graphics.drawable.Icon import android.view.ContextThemeWrapper @@ -34,7 +35,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog * Dialog to present to the user to ask for authorization to add a [TileService]. */ class TileRequestDialog( - context: Context + context: Context, ) : SystemUIDialog(context) { companion object { @@ -44,7 +45,7 @@ class TileRequestDialog( /** * Set the data of the tile to add, to show the user. */ - fun setTileData(tileData: TileData) { + fun setTileData(tileData: TileData, iUriGrantsManager: IUriGrantsManager) { val ll = (LayoutInflater .from(context) .inflate(R.layout.tile_service_request_dialog, null) @@ -54,7 +55,7 @@ class TileRequestDialog( .getString(R.string.qs_tile_request_dialog_text, tileData.appName) } addView( - createTileView(tileData), + createTileView(tileData, iUriGrantsManager), context.resources.getDimensionPixelSize( R.dimen.qs_tile_service_request_tile_width), context.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) @@ -65,13 +66,21 @@ class TileRequestDialog( setView(ll, spacing, spacing, spacing, spacing / 2) } - private fun createTileView(tileData: TileData): QSTileView { + private fun createTileView( + tileData: TileData, + iUriGrantsManager: IUriGrantsManager, + ): QSTileView { val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) val tile = QSTileViewImpl(themedContext, true) val state = QSTile.BooleanState().apply { label = tileData.label handlesLongClick = false - icon = tileData.icon?.loadDrawable(context)?.let { + icon = tileData.icon?.loadDrawableCheckingUriGrant( + context, + iUriGrantsManager, + tileData.callingUid, + tileData.packageName, + )?.let { QSTileImpl.DrawableIcon(it) } ?: ResourceIcon.get(R.drawable.android) contentDescription = label @@ -93,8 +102,10 @@ class TileRequestDialog( * @property icon Icon for the tile. */ data class TileData( + val callingUid: Int, val appName: CharSequence, val label: CharSequence, - val icon: Icon? + val icon: Icon?, + val packageName: String, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt index 899d0e2a4e35..08567afd729e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.external import android.app.Dialog +import android.app.IUriGrantsManager import android.app.StatusBarManager import android.content.ComponentName import android.content.DialogInterface @@ -42,12 +43,13 @@ private const val TAG = "TileServiceRequestController" /** * Controller to interface between [TileRequestDialog] and [QSHost]. */ -class TileServiceRequestController constructor( - private val qsHost: QSHost, - private val commandQueue: CommandQueue, - private val commandRegistry: CommandRegistry, - private val eventLogger: TileRequestDialogEventLogger, - private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) } +class TileServiceRequestController( + private val qsHost: QSHost, + private val commandQueue: CommandQueue, + private val commandRegistry: CommandRegistry, + private val eventLogger: TileRequestDialogEventLogger, + private val iUriGrantsManager: IUriGrantsManager, + private val dialogCreator: () -> TileRequestDialog = { TileRequestDialog(qsHost.context) } ) { companion object { @@ -62,13 +64,14 @@ class TileServiceRequestController constructor( private val commandQueueCallback = object : CommandQueue.Callbacks { override fun requestAddTile( + callingUid: Int, componentName: ComponentName, appName: CharSequence, label: CharSequence, icon: Icon, callback: IAddTileResultCallback ) { - requestTileAdd(componentName, appName, label, icon) { + requestTileAdd(callingUid, componentName, appName, label, icon) { try { callback.onTileRequest(it) } catch (e: RemoteException) { @@ -98,6 +101,7 @@ class TileServiceRequestController constructor( @VisibleForTesting internal fun requestTileAdd( + callingUid: Int, componentName: ComponentName, appName: CharSequence, label: CharSequence, @@ -119,7 +123,13 @@ class TileServiceRequestController constructor( eventLogger.logUserResponse(response, packageName, instanceId) callback.accept(response) } - val tileData = TileRequestDialog.TileData(appName, label, icon) + val tileData = TileRequestDialog.TileData( + callingUid, + appName, + label, + icon, + componentName.packageName, + ) createDialog(tileData, dialogResponse).also { dialog -> dialogCanceller = { if (packageName == it) { @@ -143,7 +153,7 @@ class TileServiceRequestController constructor( } } return dialogCreator().apply { - setTileData(tileData) + setTileData(tileData, iUriGrantsManager) setShowForAllUsers(true) setCanceledOnTouchOutside(true) setOnCancelListener { responseHandler.accept(DISMISSED) } @@ -168,7 +178,7 @@ class TileServiceRequestController constructor( Log.w(TAG, "Malformed componentName ${args[0]}") return } - requestTileAdd(componentName, args[1], args[2], null) { + requestTileAdd(0, componentName, args[1], args[2], null) { Log.d(TAG, "Response: $it") } } @@ -192,14 +202,16 @@ class TileServiceRequestController constructor( @SysUISingleton class Builder @Inject constructor( private val commandQueue: CommandQueue, - private val commandRegistry: CommandRegistry + private val commandRegistry: CommandRegistry, + private val iUriGrantsManager: IUriGrantsManager, ) { fun create(qsHost: QSHost): TileServiceRequestController { return TileServiceRequestController( qsHost, commandQueue, commandRegistry, - TileRequestDialogEventLogger() + TileRequestDialogEventLogger(), + iUriGrantsManager, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index c8691acfd278..fc2402258009 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -184,7 +184,7 @@ public class TileServices extends IQSService.Stub { } } - private void verifyCaller(CustomTile tile) { + private int verifyCaller(CustomTile tile) { try { String packageName = tile.getComponent().getPackageName(); int uid = mContext.getPackageManager().getPackageUidAsUser(packageName, @@ -192,6 +192,7 @@ public class TileServices extends IQSService.Stub { if (Binder.getCallingUid() != uid) { throw new SecurityException("Component outside caller's uid"); } + return uid; } catch (PackageManager.NameNotFoundException e) { throw new SecurityException(e); } @@ -228,7 +229,7 @@ public class TileServices extends IQSService.Stub { public void updateQsTile(Tile tile, IBinder token) { CustomTile customTile = getTileForToken(token); if (customTile != null) { - verifyCaller(customTile); + int uid = verifyCaller(customTile); synchronized (mServices) { final TileServiceManager tileServiceManager = mServices.get(customTile); if (tileServiceManager == null || !tileServiceManager.isLifecycleStarted()) { @@ -239,7 +240,7 @@ public class TileServices extends IQSService.Stub { tileServiceManager.clearPendingBind(); tileServiceManager.setLastUpdate(System.currentTimeMillis()); } - customTile.updateTileState(tile); + customTile.updateTileState(tile, uid); customTile.refreshState(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt index b394a079fb00..8b2c3de18469 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt @@ -72,7 +72,7 @@ interface FooterActionsInteractor { * Show the device monitoring dialog, expanded from [expandable] if it's not null. * * Important: [quickSettingsContext] *must* be the [Context] associated to the - * [Quick Settings fragment][com.android.systemui.qs.QSFragment]. + * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy]. */ fun showDeviceMonitoringDialog(quickSettingsContext: Context, expandable: Expandable?) diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index 769cb1fe1370..64fa33ce8118 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -23,7 +23,6 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import com.android.settingslib.Utils -import com.android.systemui.res.R import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -34,6 +33,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor +import com.android.systemui.res.R import com.android.systemui.util.icuMessageFormat import javax.inject.Inject import javax.inject.Named @@ -43,7 +43,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map @@ -201,8 +200,8 @@ class FooterActionsViewModel( * will suspend indefinitely and will need to be cancelled to stop observing. * * Important: [quickSettingsContext] must be the [Context] associated to the - * [Quick Settings fragment][com.android.systemui.qs.QSFragment], and the call to this function - * must be cancelled when that fragment is destroyed. + * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this + * function must be cancelled when that fragment is destroyed. */ suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) { footerActionsInteractor.deviceMonitoringDialogRequests.collect { diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt index a4600fbbf4bd..b50798e59953 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt @@ -20,14 +20,20 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory +import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostRepository +import com.android.systemui.qs.pipeline.data.repository.DefaultTilesRepository import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepositoryImpl +import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredBroadcastRepository +import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl import com.android.systemui.qs.pipeline.domain.startable.QSPipelineCoreStartable import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractorImpl import dagger.Binds import dagger.Module import dagger.Provides @@ -42,6 +48,11 @@ abstract class QSPipelineModule { abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository @Binds + abstract fun provideDefaultTilesRepository( + impl: DefaultTilesQSHostRepository + ): DefaultTilesRepository + + @Binds abstract fun bindCurrentTilesInteractor( impl: CurrentTilesInteractorImpl ): CurrentTilesInteractor @@ -52,10 +63,20 @@ abstract class QSPipelineModule { ): InstalledTilesComponentRepository @Binds + abstract fun provideDisabledByPolicyInteractor( + impl: DisabledByPolicyInteractorImpl + ): DisabledByPolicyInteractor + + @Binds @IntoMap @ClassKey(QSPipelineCoreStartable::class) abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable + @Binds + abstract fun provideQSSettingsRestoredRepository( + impl: QSSettingsRestoredBroadcastRepository + ): QSSettingsRestoredRepository + companion object { /** * Provides a logging buffer for all logs related to the new Quick Settings pipeline to log @@ -67,5 +88,12 @@ abstract class QSPipelineModule { fun provideQSTileListLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create(QSPipelineLogger.TILE_LIST_TAG, maxSize = 700, systrace = false) } + + @Provides + @SysUISingleton + @QSRestoreLog + fun providesQSRestoreLogBuffer(factory: LogBufferFactory): LogBuffer { + return factory.create(QSPipelineLogger.RESTORE_TAG, maxSize = 50, systrace = false) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSRestoreLog.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSRestoreLog.kt new file mode 100644 index 000000000000..c9649292fb0e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSRestoreLog.kt @@ -0,0 +1,6 @@ +package com.android.systemui.qs.pipeline.dagger + +import javax.inject.Qualifier + +/** A [LogBuffer] for the QS pipeline to track restore of associated settings. */ +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class QSRestoreLog diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreData.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreData.kt new file mode 100644 index 000000000000..d962632407df --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreData.kt @@ -0,0 +1,10 @@ +package com.android.systemui.qs.pipeline.data.model + +import com.android.systemui.qs.pipeline.shared.TileSpec + +/** Data restored from Quick Settings as part of Backup & Restore. */ +data class RestoreData( + val restoredTiles: List<TileSpec>, + val restoredAutoAddedTiles: Set<TileSpec>, + val userId: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt index 43a16b69d1a8..7998dfbe3f92 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt @@ -16,28 +16,19 @@ package com.android.systemui.qs.pipeline.data.repository -import android.database.ContentObserver -import android.provider.Settings -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import android.util.SparseArray import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.pipeline.data.model.RestoreData import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.util.settings.SecureSettings -import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow /** Repository to track what QS tiles have been auto-added */ interface AutoAddRepository { /** Flow of tiles that have been auto-added */ - fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> + suspend fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> /** Mark a tile as having been auto-added */ suspend fun markTileAdded(userId: Int, spec: TileSpec) @@ -47,89 +38,39 @@ interface AutoAddRepository { * multiple times. */ suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) + + suspend fun reconcileRestore(restoreData: RestoreData) } /** - * Implementation that tracks the auto-added tiles stored in [Settings.Secure.QS_AUTO_ADDED_TILES]. + * Implementation of [AutoAddRepository] that delegates to an instance of [UserAutoAddRepository] + * for each user. */ @SysUISingleton class AutoAddSettingRepository @Inject -constructor( - private val secureSettings: SecureSettings, - @Background private val bgDispatcher: CoroutineDispatcher, -) : AutoAddRepository { - override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> { - return conflatedCallbackFlow { - val observer = - object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - trySend(Unit) - } - } +constructor(private val userAutoAddRepositoryFactory: UserAutoAddRepository.Factory) : + AutoAddRepository { - secureSettings.registerContentObserverForUser(SETTING, observer, userId) + private val userAutoAddRepositories = SparseArray<UserAutoAddRepository>() - awaitClose { secureSettings.unregisterContentObserver(observer) } - } - .onStart { emit(Unit) } - .map { secureSettings.getStringForUser(SETTING, userId) ?: "" } - .distinctUntilChanged() - .map { - it.split(DELIMITER).map(TileSpec::create).filter { it !is TileSpec.Invalid }.toSet() - } - .flowOn(bgDispatcher) + override suspend fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> { + if (userId !in userAutoAddRepositories) { + val repository = userAutoAddRepositoryFactory.create(userId) + userAutoAddRepositories.put(userId, repository) + } + return userAutoAddRepositories.get(userId).autoAdded() } override suspend fun markTileAdded(userId: Int, spec: TileSpec) { - if (spec is TileSpec.Invalid) { - return - } - val added = load(userId).toMutableSet() - if (added.add(spec)) { - store(userId, added) - } + userAutoAddRepositories.get(userId)?.markTileAdded(spec) } override suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) { - if (spec is TileSpec.Invalid) { - return - } - val added = load(userId).toMutableSet() - if (added.remove(spec)) { - store(userId, added) - } - } - - private suspend fun store(userId: Int, tiles: Set<TileSpec>) { - val toStore = - tiles - .filter { it !is TileSpec.Invalid } - .joinToString(DELIMITER, transform = TileSpec::spec) - withContext(bgDispatcher) { - secureSettings.putStringForUser( - SETTING, - toStore, - null, - false, - userId, - true, - ) - } - } - - private suspend fun load(userId: Int): Set<TileSpec> { - return withContext(bgDispatcher) { - (secureSettings.getStringForUser(SETTING, userId) ?: "") - .split(",") - .map(TileSpec::create) - .filter { it !is TileSpec.Invalid } - .toSet() - } + userAutoAddRepositories.get(userId)?.unmarkTileAdded(spec) } - companion object { - private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES - private const val DELIMITER = "," + override suspend fun reconcileRestore(restoreData: RestoreData) { + userAutoAddRepositories.get(restoreData.userId)?.reconcileRestore(restoreData) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt new file mode 100644 index 000000000000..fe0a69b03287 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/DefaultTilesRepository.kt @@ -0,0 +1,25 @@ +package com.android.systemui.qs.pipeline.data.repository + +import android.content.res.Resources +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.pipeline.shared.TileSpec +import javax.inject.Inject + +interface DefaultTilesRepository { + val defaultTiles: List<TileSpec> +} + +@SysUISingleton +class DefaultTilesQSHostRepository +@Inject +constructor( + @Main private val resources: Resources, +) : DefaultTilesRepository { + override val defaultTiles: List<TileSpec> + get() = + QSHost.getDefaultSpecs(resources).map(TileSpec::create).filter { + it != TileSpec.Invalid + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt new file mode 100644 index 000000000000..6cee1161a104 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt @@ -0,0 +1,122 @@ +package com.android.systemui.qs.pipeline.data.repository + +import android.content.Intent +import android.content.IntentFilter +import android.os.UserHandle +import android.provider.Settings +import android.util.Log +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter.toTilesList +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.shareIn + +/** Provides restored data (from Backup and Restore) for Quick Settings pipeline */ +interface QSSettingsRestoredRepository { + val restoreData: Flow<RestoreData> +} + +@SysUISingleton +class QSSettingsRestoredBroadcastRepository +@Inject +constructor( + broadcastDispatcher: BroadcastDispatcher, + logger: QSPipelineLogger, + @Application private val scope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) : QSSettingsRestoredRepository { + + override val restoreData = + flow { + val firstIntent = mutableMapOf<Int, Intent>() + broadcastDispatcher + .broadcastFlow(INTENT_FILTER, UserHandle.ALL) { intent, receiver -> + intent to receiver.sendingUserId + } + .filter { it.first.isCorrectSetting() } + .collect { (intent, user) -> + if (user !in firstIntent) { + firstIntent[user] = intent + } else { + val firstRestored = firstIntent.remove(user)!! + emit(processIntents(user, firstRestored, intent)) + } + } + } + .catch { Log.e(TAG, "Error parsing broadcast", it) } + .flowOn(backgroundDispatcher) + .buffer(BUFFER_CAPACITY) + .shareIn(scope, SharingStarted.Eagerly) + .onEach(logger::logSettingsRestored) + + private fun processIntents(user: Int, intent1: Intent, intent2: Intent): RestoreData { + intent1.validateIntent() + intent2.validateIntent() + val setting1 = intent1.getStringExtra(Intent.EXTRA_SETTING_NAME) + val setting2 = intent2.getStringExtra(Intent.EXTRA_SETTING_NAME) + val (tiles, autoAdd) = + if (setting1 == TILES_SETTING && setting2 == AUTO_ADD_SETTING) { + intent1 to intent2 + } else if (setting1 == AUTO_ADD_SETTING && setting2 == TILES_SETTING) { + intent2 to intent1 + } else { + throw IllegalStateException("Wrong intents ($intent1, $intent2)") + } + + return RestoreData( + (tiles.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?: "").toTilesList(), + (autoAdd.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?: "").toTilesSet(), + user, + ) + } + + private companion object { + private const val TAG = "QSSettingsRestoredBroadcastRepository" + // This capacity is the number of restore data that we will keep buffered in the shared + // flow. It is unlikely that at any given time there would be this many restores being + // processed by consumers, but just in case that a couple of users are restored at the + // same time and they need to be replayed for the consumers of the flow. + private const val BUFFER_CAPACITY = 10 + + private val INTENT_FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED) + private const val TILES_SETTING = Settings.Secure.QS_TILES + private const val AUTO_ADD_SETTING = Settings.Secure.QS_AUTO_ADDED_TILES + private val requiredExtras = + listOf( + Intent.EXTRA_SETTING_NAME, + Intent.EXTRA_SETTING_PREVIOUS_VALUE, + Intent.EXTRA_SETTING_NEW_VALUE, + ) + + private fun Intent.isCorrectSetting(): Boolean { + val setting = getStringExtra(Intent.EXTRA_SETTING_NAME) + return setting == TILES_SETTING || setting == AUTO_ADD_SETTING + } + + private fun Intent.validateIntent() { + requiredExtras.forEach { extra -> + if (!hasExtra(extra)) { + throw IllegalStateException("$this doesn't have $extra") + } + } + } + + private fun String.toTilesList() = toTilesList(this) + + private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt index 47c99f25730b..00ea0b5c5ed3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt @@ -18,34 +18,19 @@ package com.android.systemui.qs.pipeline.data.repository import android.annotation.UserIdInt import android.content.res.Resources -import android.database.ContentObserver -import android.provider.Settings import android.util.SparseArray import com.android.systemui.res.R -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.qs.QSHost +import com.android.systemui.qs.pipeline.data.model.RestoreData import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import com.android.systemui.retail.data.repository.RetailModeRepository -import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext /** Repository that tracks the current tiles. */ interface TileSpecRepository { @@ -55,7 +40,7 @@ interface TileSpecRepository { * * Tiles will never be [TileSpec.Invalid] in the list and it will never be empty. */ - fun tilesSpecs(@UserIdInt userId: Int): Flow<List<TileSpec>> + suspend fun tilesSpecs(@UserIdInt userId: Int): Flow<List<TileSpec>> /** * Adds a [tile] for a given [userId] at [position]. Using [POSITION_AT_END] will add the tile @@ -81,6 +66,8 @@ interface TileSpecRepository { */ suspend fun setTiles(@UserIdInt userId: Int, tiles: List<TileSpec>) + suspend fun reconcileRestore(restoreData: RestoreData, currentAutoAdded: Set<TileSpec>) + companion object { /** Position to indicate the end of the list */ const val POSITION_AT_END = -1 @@ -88,28 +75,23 @@ interface TileSpecRepository { } /** - * Implementation of [TileSpecRepository] that persist the values of tiles in - * [Settings.Secure.QS_TILES]. - * - * All operations against [Settings] will be performed in a background thread. + * Implementation of [TileSpecRepository] that delegates to an instance of [UserTileSpecRepository] + * for each user. * * If the device is in retail mode, the tiles are fixed to the value of * [R.string.quick_settings_tiles_retail_mode]. */ +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class TileSpecSettingsRepository @Inject constructor( - private val secureSettings: SecureSettings, @Main private val resources: Resources, private val logger: QSPipelineLogger, private val retailModeRepository: RetailModeRepository, - @Background private val backgroundDispatcher: CoroutineDispatcher, + private val userTileSpecRepositoryFactory: UserTileSpecRepository.Factory, ) : TileSpecRepository { - private val mutex = Mutex() - private val tileSpecsPerUser = SparseArray<List<TileSpec>>() - private val retailModeTiles by lazy { resources .getString(R.string.quick_settings_tiles_retail_mode) @@ -118,123 +100,59 @@ constructor( .filter { it !is TileSpec.Invalid } } - @OptIn(ExperimentalCoroutinesApi::class) - override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> { + private val userTileRepositories = SparseArray<UserTileSpecRepository>() + + override suspend fun tilesSpecs(userId: Int): Flow<List<TileSpec>> { + if (userId !in userTileRepositories) { + val userTileRepository = userTileSpecRepositoryFactory.create(userId) + userTileRepositories.put(userId, userTileRepository) + } + val realTiles = userTileRepositories.get(userId).tiles() + return retailModeRepository.retailMode.flatMapLatest { inRetailMode -> if (inRetailMode) { logger.logUsingRetailTiles() flowOf(retailModeTiles) } else { - settingsTiles(userId) + realTiles } } } - private fun settingsTiles(userId: Int): Flow<List<TileSpec>> { - return conflatedCallbackFlow { - val observer = - object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - trySend(Unit) - } - } - - secureSettings.registerContentObserverForUser(SETTING, observer, userId) - - awaitClose { secureSettings.unregisterContentObserver(observer) } - } - .onStart { emit(Unit) } - .map { loadTiles(userId) } - .onEach { logger.logTilesChangedInSettings(it, userId) } - .distinctUntilChanged() - .map { parseTileSpecs(it, userId).also { storeTiles(userId, it) } } - .distinctUntilChanged() - .onEach { mutex.withLock { tileSpecsPerUser.put(userId, it) } } - .flowOn(backgroundDispatcher) - } - - override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) = - mutex.withLock { - if (tile == TileSpec.Invalid) { - return - } - val tilesList = tileSpecsPerUser.get(userId, emptyList()).toMutableList() - if (tile !in tilesList) { - if (position < 0 || position >= tilesList.size) { - tilesList.add(tile) - } else { - tilesList.add(position, tile) - } - storeTiles(userId, tilesList) - tileSpecsPerUser.put(userId, tilesList) - } - } - - override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) = - mutex.withLock { - if (tiles.all { it == TileSpec.Invalid }) { - return - } - val tilesList = tileSpecsPerUser.get(userId, emptyList()).toMutableList() - if (tilesList.removeAll(tiles)) { - storeTiles(userId, tilesList.toList()) - tileSpecsPerUser.put(userId, tilesList) - } + override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) { + if (retailModeRepository.inRetailMode) { + return } - - override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) = - mutex.withLock { - val filtered = tiles.filter { it != TileSpec.Invalid } - if (filtered.isNotEmpty()) { - storeTiles(userId, filtered) - tileSpecsPerUser.put(userId, tiles) - } + if (tile is TileSpec.Invalid) { + return } + userTileRepositories.get(userId)?.addTile(tile, position) + } - private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) { + override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) { if (retailModeRepository.inRetailMode) { - // No storing tiles when in retail mode return } - val toStore = - tiles - .filter { it !is TileSpec.Invalid } - .joinToString(DELIMITER, transform = TileSpec::spec) - withContext(backgroundDispatcher) { - secureSettings.putStringForUser( - SETTING, - toStore, - null, - false, - forUser, - true, - ) - } + userTileRepositories.get(userId)?.removeTiles(tiles) } - private suspend fun loadTiles(userId: Int): String { - return withContext(backgroundDispatcher) { - secureSettings.getStringForUser(SETTING, userId) ?: "" + override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) { + if (retailModeRepository.inRetailMode) { + return } + userTileRepositories.get(userId)?.setTiles(tiles) } - private fun parseTileSpecs(tilesFromSettings: String, user: Int): List<TileSpec> { - val fromSettings = - tilesFromSettings.split(DELIMITER).map(TileSpec::create).filter { - it != TileSpec.Invalid - } - return if (fromSettings.isNotEmpty()) { - fromSettings.also { logger.logParsedTiles(it, false, user) } - } else { - QSHost.getDefaultSpecs(resources) - .map(TileSpec::create) - .filter { it != TileSpec.Invalid } - .also { logger.logParsedTiles(it, true, user) } - } + override suspend fun reconcileRestore( + restoreData: RestoreData, + currentAutoAdded: Set<TileSpec> + ) { + userTileRepositories + .get(restoreData.userId) + ?.reconcileRestore(restoreData, currentAutoAdded) } companion object { - private const val SETTING = Settings.Secure.QS_TILES - private const val DELIMITER = "," + private const val DELIMITER = TilesSettingConverter.DELIMITER } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverter.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverter.kt new file mode 100644 index 000000000000..bb55fcdac58a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverter.kt @@ -0,0 +1,18 @@ +package com.android.systemui.qs.pipeline.data.repository + +import com.android.systemui.qs.pipeline.shared.TileSpec + +object TilesSettingConverter { + + const val DELIMITER = "," + + fun toTilesList(commaSeparatedTiles: String) = + commaSeparatedTiles.split(DELIMITER).map(TileSpec::create).filter { it != TileSpec.Invalid } + + fun toTilesSet(commaSeparatedTiles: String) = + commaSeparatedTiles + .split(DELIMITER) + .map(TileSpec::create) + .filter { it != TileSpec.Invalid } + .toSet() +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt new file mode 100644 index 000000000000..d452241e86e2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt @@ -0,0 +1,186 @@ +package com.android.systemui.qs.pipeline.data.repository + +import android.database.ContentObserver +import android.provider.Settings +import com.android.systemui.common.coroutine.ConflatedCallbackFlow +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.util.settings.SecureSettings +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Single user version of [AutoAddRepository]. It provides a similar interface as + * [AutoAddRepository], but focusing solely on the user it was created for. + * + * This is the source of truth for that user's tiles, after the user has been started. Persisting + * all the changes to [Settings]. Changes in [Settings] that disagree with this repository will be + * reverted + * + * All operations against [Settings] will be performed in a background thread. + */ +class UserAutoAddRepository +@AssistedInject +constructor( + @Assisted private val userId: Int, + private val secureSettings: SecureSettings, + private val logger: QSPipelineLogger, + @Application private val applicationScope: CoroutineScope, + @Background private val bgDispatcher: CoroutineDispatcher, +) { + + private val changeEvents = MutableSharedFlow<ChangeAction>( + extraBufferCapacity = CHANGES_BUFFER_SIZE + ) + + private lateinit var _autoAdded: StateFlow<Set<TileSpec>> + + suspend fun autoAdded(): StateFlow<Set<TileSpec>> { + if (!::_autoAdded.isInitialized) { + _autoAdded = + changeEvents + .scan(load().also { logger.logAutoAddTilesParsed(userId, it) }) { + current, + change -> + change.apply(current).also { + if (change is RestoreTiles) { + logger.logAutoAddTilesRestoredReconciled(userId, it) + } + } + } + .flowOn(bgDispatcher) + .stateIn(applicationScope) + .also { startFlowCollections(it) } + } + return _autoAdded + } + + private fun startFlowCollections(autoAdded: StateFlow<Set<TileSpec>>) { + applicationScope.launch(bgDispatcher) { + launch { autoAdded.collect { store(it) } } + launch { + // As Settings is not the source of truth, once we started tracking tiles for a + // user, we don't want anyone to change the underlying setting. Therefore, if there + // are any changes that don't match with the source of truth (this class), we + // overwrite them with the current value. + ConflatedCallbackFlow.conflatedCallbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + secureSettings.registerContentObserverForUser(SETTING, observer, userId) + awaitClose { secureSettings.unregisterContentObserver(observer) } + } + .map { load() } + .flowOn(bgDispatcher) + .collect { setting -> + val current = autoAdded.value + if (setting != current) { + store(current) + } + } + } + } + } + + suspend fun markTileAdded(spec: TileSpec) { + if (spec is TileSpec.Invalid) { + return + } + changeEvents.emit(MarkTile(spec)) + } + + suspend fun unmarkTileAdded(spec: TileSpec) { + if (spec is TileSpec.Invalid) { + return + } + changeEvents.emit(UnmarkTile(spec)) + } + + private suspend fun store(tiles: Set<TileSpec>) { + val toStore = + tiles + .filter { it !is TileSpec.Invalid } + .joinToString(DELIMITER, transform = TileSpec::spec) + withContext(bgDispatcher) { + secureSettings.putStringForUser( + SETTING, + toStore, + null, + false, + userId, + true, + ) + } + } + + private suspend fun load(): Set<TileSpec> { + return withContext(bgDispatcher) { + (secureSettings.getStringForUser(SETTING, userId) ?: "").toTilesSet() + } + } + + suspend fun reconcileRestore(restoreData: RestoreData) { + changeEvents.emit(RestoreTiles(restoreData)) + } + + private sealed interface ChangeAction { + fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec> + } + + private data class MarkTile( + val tileSpec: TileSpec, + ) : ChangeAction { + override fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec> { + return currentAutoAdded.toMutableSet().apply { add(tileSpec) } + } + } + + private data class UnmarkTile( + val tileSpec: TileSpec, + ) : ChangeAction { + override fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec> { + return currentAutoAdded.toMutableSet().apply { remove(tileSpec) } + } + } + + private data class RestoreTiles( + val restoredData: RestoreData, + ) : ChangeAction { + override fun apply(currentAutoAdded: Set<TileSpec>): Set<TileSpec> { + return currentAutoAdded + restoredData.restoredAutoAddedTiles + } + } + + companion object { + private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES + private const val DELIMITER = "," + // We want a small buffer in case multiple changes come in at the same time (sometimes + // happens in first start. This should be enough to not lose changes. + private const val CHANGES_BUFFER_SIZE = 10 + + private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this) + } + + @AssistedFactory + interface Factory { + fun create(userId: Int): UserAutoAddRepository + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt new file mode 100644 index 000000000000..152fd0f83811 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt @@ -0,0 +1,252 @@ +package com.android.systemui.qs.pipeline.data.repository + +import android.annotation.UserIdInt +import android.database.ContentObserver +import android.provider.Settings +import com.android.systemui.common.coroutine.ConflatedCallbackFlow +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.util.settings.SecureSettings +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Single user version of [TileSpecRepository]. It provides a similar interface as + * [TileSpecRepository], but focusing solely on the user it was created for. + * + * This is the source of truth for that user's tiles, after the user has been started. Persisting + * all the changes to [Settings]. Changes in [Settings] that disagree with this repository will be + * reverted + * + * All operations against [Settings] will be performed in a background thread. + */ +class UserTileSpecRepository +@AssistedInject +constructor( + @Assisted private val userId: Int, + private val defaultTilesRepository: DefaultTilesRepository, + private val secureSettings: SecureSettings, + private val logger: QSPipelineLogger, + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + + private val defaultTiles: List<TileSpec> + get() = defaultTilesRepository.defaultTiles + + private val changeEvents = MutableSharedFlow<ChangeAction>( + extraBufferCapacity = CHANGES_BUFFER_SIZE + ) + + private lateinit var _tiles: StateFlow<List<TileSpec>> + + suspend fun tiles(): Flow<List<TileSpec>> { + if (!::_tiles.isInitialized) { + _tiles = + changeEvents + .scan(loadTilesFromSettingsAndParse(userId)) { current, change -> + change.apply(current).also { + if (current != it) { + if (change is RestoreTiles) { + logger.logTilesRestoredAndReconciled(current, it, userId) + } else { + logger.logProcessTileChange(change, it, userId) + } + } + } + } + .flowOn(backgroundDispatcher) + .stateIn(applicationScope) + .also { startFlowCollections(it) } + } + return _tiles + } + + private fun startFlowCollections(tiles: StateFlow<List<TileSpec>>) { + applicationScope.launch(backgroundDispatcher) { + launch { tiles.collect { storeTiles(userId, it) } } + launch { + // As Settings is not the source of truth, once we started tracking tiles for a + // user, we don't want anyone to change the underlying setting. Therefore, if there + // are any changes that don't match with the source of truth (this class), we + // overwrite them with the current value. + ConflatedCallbackFlow.conflatedCallbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + secureSettings.registerContentObserverForUser(SETTING, observer, userId) + awaitClose { secureSettings.unregisterContentObserver(observer) } + } + .map { loadTilesFromSettings(userId) } + .flowOn(backgroundDispatcher) + .collect { setting -> + val current = tiles.value + if (setting != current) { + storeTiles(userId, current) + } + } + } + } + } + + private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) { + val toStore = + tiles + .filter { it !is TileSpec.Invalid } + .joinToString(DELIMITER, transform = TileSpec::spec) + withContext(backgroundDispatcher) { + secureSettings.putStringForUser( + SETTING, + toStore, + null, + false, + forUser, + true, + ) + } + } + + suspend fun addTile(tile: TileSpec, position: Int = TileSpecRepository.POSITION_AT_END) { + if (tile is TileSpec.Invalid) { + return + } + changeEvents.emit(AddTile(tile, position)) + } + + suspend fun removeTiles(tiles: Collection<TileSpec>) { + changeEvents.emit(RemoveTiles(tiles)) + } + + suspend fun setTiles(tiles: List<TileSpec>) { + changeEvents.emit(ChangeTiles(tiles)) + } + + private fun parseTileSpecs(fromSettings: List<TileSpec>, user: Int): List<TileSpec> { + return if (fromSettings.isNotEmpty()) { + fromSettings.also { logger.logParsedTiles(it, false, user) } + } else { + defaultTiles.also { logger.logParsedTiles(it, true, user) } + } + } + + private suspend fun loadTilesFromSettingsAndParse(userId: Int): List<TileSpec> { + return parseTileSpecs(loadTilesFromSettings(userId), userId) + } + + private suspend fun loadTilesFromSettings(userId: Int): List<TileSpec> { + return withContext(backgroundDispatcher) { + secureSettings.getStringForUser(SETTING, userId) ?: "" + } + .toTilesList() + } + + suspend fun reconcileRestore(restoreData: RestoreData, currentAutoAdded: Set<TileSpec>) { + changeEvents.emit(RestoreTiles(restoreData, currentAutoAdded)) + } + + sealed interface ChangeAction { + fun apply(currentTiles: List<TileSpec>): List<TileSpec> + } + + private data class AddTile( + val tileSpec: TileSpec, + val position: Int = TileSpecRepository.POSITION_AT_END + ) : ChangeAction { + override fun apply(currentTiles: List<TileSpec>): List<TileSpec> { + val tilesList = currentTiles.toMutableList() + if (tileSpec !in tilesList) { + if (position < 0 || position >= tilesList.size) { + tilesList.add(tileSpec) + } else { + tilesList.add(position, tileSpec) + } + } + return tilesList + } + } + + private data class RemoveTiles(val tileSpecs: Collection<TileSpec>) : ChangeAction { + override fun apply(currentTiles: List<TileSpec>): List<TileSpec> { + return currentTiles.toMutableList().apply { removeAll(tileSpecs) } + } + } + + private data class ChangeTiles( + val newTiles: List<TileSpec>, + ) : ChangeAction { + override fun apply(currentTiles: List<TileSpec>): List<TileSpec> { + val new = newTiles.filter { it !is TileSpec.Invalid } + return if (new.isNotEmpty()) new else currentTiles + } + } + + private data class RestoreTiles( + val restoreData: RestoreData, + val currentAutoAdded: Set<TileSpec>, + ) : ChangeAction { + + override fun apply(currentTiles: List<TileSpec>): List<TileSpec> { + return reconcileTiles(currentTiles, currentAutoAdded, restoreData) + } + } + + companion object { + private const val SETTING = Settings.Secure.QS_TILES + private const val DELIMITER = TilesSettingConverter.DELIMITER + // We want a small buffer in case multiple changes come in at the same time (sometimes + // happens in first start. This should be enough to not lose changes. + private const val CHANGES_BUFFER_SIZE = 10 + + private fun String.toTilesList() = TilesSettingConverter.toTilesList(this) + + fun reconcileTiles( + currentTiles: List<TileSpec>, + currentAutoAdded: Set<TileSpec>, + restoreData: RestoreData + ): List<TileSpec> { + val toRestore = restoreData.restoredTiles.toMutableList() + val freshlyAutoAdded = + currentAutoAdded.filterNot { it in restoreData.restoredAutoAddedTiles } + freshlyAutoAdded + .filter { it in currentTiles && it !in restoreData.restoredTiles } + .map { it to currentTiles.indexOf(it) } + .sortedBy { it.second } + .forEachIndexed { iteration, (tile, position) -> + val insertAt = position + iteration + if (insertAt > toRestore.size) { + toRestore.add(tile) + } else { + toRestore.add(insertAt, tile) + } + } + + return toRestore + } + } + + @AssistedFactory + interface Factory { + fun create( + userId: Int, + ): UserTileSpecRepository + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt index 5a5e47af73c9..c5512c15ccc2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt @@ -20,6 +20,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.os.UserHandle +import android.util.Log import com.android.systemui.Dumpable import com.android.systemui.ProtoDumpable import com.android.systemui.dagger.SysUISingleton @@ -40,10 +41,12 @@ import com.android.systemui.qs.pipeline.domain.model.TileModel import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.qs.tiles.di.NewQSTileFactory import com.android.systemui.qs.toProto import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise +import dagger.Lazy import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -130,6 +133,7 @@ constructor( private val installedTilesComponentRepository: InstalledTilesComponentRepository, private val userRepository: UserRepository, private val customTileStatePersister: CustomTileStatePersister, + private val newQSTileFactory: Lazy<NewQSTileFactory>, private val tileFactory: QSFactory, private val customTileAddedRepository: CustomTileAddedRepository, private val tileLifecycleManagerFactory: TileLifecycleManager.Factory, @@ -138,7 +142,7 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, private val logger: QSPipelineLogger, - featureFlags: QSPipelineFlagsRepository, + private val featureFlags: QSPipelineFlagsRepository, ) : CurrentTilesInteractor { private val _currentSpecsAndTiles: MutableStateFlow<List<TileModel>> = @@ -268,6 +272,7 @@ constructor( // repository launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) } } + Log.d("Fabian", "Finished resolving tiles") } } } @@ -331,12 +336,19 @@ constructor( } private suspend fun createTile(spec: TileSpec): QSTile? { - val tile = withContext(mainDispatcher) { tileFactory.createTile(spec.spec) } + val tile = + withContext(mainDispatcher) { + if (featureFlags.pipelineTilesEnabled) { + newQSTileFactory.get().createTile(spec.spec) + } else { + null + } + ?: tileFactory.createTile(spec.spec) + } if (tile == null) { logger.logTileNotFoundInFactory(spec) return null } else { - tile.tileSpec = spec.spec return if (!tile.isAvailable) { logger.logTileDestroyed( spec, diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt new file mode 100644 index 000000000000..9844903eff26 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt @@ -0,0 +1,53 @@ +package com.android.systemui.qs.pipeline.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository +import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.launch + +/** + * Interactor in charge of triggering reconciliation after QS Secure Settings are restored. For a + * given user, it will trigger the reconciliations in the correct order to prevent race conditions. + * + * Currently, the order is: + * 1. TileSpecRepository, with the restored data and the current (before restore) auto add tiles + * 2. AutoAddRepository + * + * [start] needs to be called to trigger the collection of [QSSettingsRestoredRepository]. + */ +@SysUISingleton +class RestoreReconciliationInteractor +@Inject +constructor( + private val tileSpecRepository: TileSpecRepository, + private val autoAddRepository: AutoAddRepository, + private val qsSettingsRestoredRepository: QSSettingsRestoredRepository, + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + + @OptIn(ExperimentalCoroutinesApi::class) + fun start() { + applicationScope.launch(backgroundDispatcher) { + qsSettingsRestoredRepository.restoreData.flatMapConcat { data -> + autoAddRepository.autoAddedTiles(data.userId) + .take(1) + .map { tiles -> data to tiles } + }.collect { (restoreData, autoAdded) -> + tileSpecRepository.reconcileRestore(restoreData, autoAdded) + autoAddRepository.reconcileRestore(restoreData) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt index 0743ba0b121a..1539f05508d0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt @@ -20,6 +20,7 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor +import com.android.systemui.qs.pipeline.domain.interactor.RestoreReconciliationInteractor import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository import javax.inject.Inject @@ -30,11 +31,13 @@ constructor( private val currentTilesInteractor: CurrentTilesInteractor, private val autoAddInteractor: AutoAddInteractor, private val featureFlags: QSPipelineFlagsRepository, + private val restoreReconciliationInteractor: RestoreReconciliationInteractor, ) : CoreStartable { override fun start() { if (featureFlags.pipelineAutoAddEnabled) { autoAddInteractor.init(currentTilesInteractor) + restoreReconciliationInteractor.start() } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt index 551b0f4890a4..1a71b715fe3a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt @@ -1,7 +1,7 @@ package com.android.systemui.qs.pipeline.shared import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import javax.inject.Inject @@ -10,7 +10,7 @@ import javax.inject.Inject class QSPipelineFlagsRepository @Inject constructor( - private val featureFlags: FeatureFlags, + private val featureFlags: FeatureFlagsClassic, ) { /** @see Flags.QS_PIPELINE_NEW_HOST */ @@ -20,4 +20,8 @@ constructor( /** @see Flags.QS_PIPELINE_AUTO_ADD */ val pipelineAutoAddEnabled: Boolean get() = pipelineHostEnabled && featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD) + + /** @see Flags.QS_PIPELINE_NEW_TILES */ + val pipelineTilesEnabled: Boolean + get() = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_TILES) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt index 11b5dd7cb036..aed08f8b5457 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt @@ -31,11 +31,7 @@ import com.android.systemui.qs.external.CustomTile sealed class TileSpec private constructor(open val spec: String) { /** Represents a spec that couldn't be parsed into a valid type of tile. */ - object Invalid : TileSpec("") { - override fun toString(): String { - return "TileSpec.INVALID" - } - } + data object Invalid : TileSpec("") /** Container for the spec of a tile provided by SystemUI. */ data class PlatformTileSpec diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt index 573cb7154c4f..bca86c9ee8af 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt @@ -16,11 +16,13 @@ package com.android.systemui.qs.pipeline.shared.logging -import android.annotation.UserIdInt import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.qs.pipeline.dagger.QSAutoAddLog +import com.android.systemui.qs.pipeline.dagger.QSRestoreLog import com.android.systemui.qs.pipeline.dagger.QSTileListLog +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.repository.UserTileSpecRepository import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject @@ -34,11 +36,13 @@ class QSPipelineLogger constructor( @QSTileListLog private val tileListLogBuffer: LogBuffer, @QSAutoAddLog private val tileAutoAddLogBuffer: LogBuffer, + @QSRestoreLog private val restoreLogBuffer: LogBuffer, ) { companion object { const val TILE_LIST_TAG = "QSTileListLog" const val AUTO_ADD_TAG = "QSAutoAddableLog" + const val RESTORE_TAG = "QSRestoreLog" } /** @@ -60,20 +64,37 @@ constructor( ) } - /** - * Logs when the tiles change in Settings. - * - * This could be caused by SystemUI, or restore. - */ - fun logTilesChangedInSettings(newTiles: String, @UserIdInt user: Int) { + fun logTilesRestoredAndReconciled( + currentTiles: List<TileSpec>, + reconciledTiles: List<TileSpec>, + user: Int, + ) { tileListLogBuffer.log( TILE_LIST_TAG, - LogLevel.VERBOSE, + LogLevel.DEBUG, { - str1 = newTiles + str1 = currentTiles.toString() + str2 = reconciledTiles.toString() int1 = user }, - { "Tiles changed in settings for user $int1: $str1" } + { "Tiles restored and reconciled for user: $int1\nWas: $str1\nSet to: $str2" } + ) + } + + fun logProcessTileChange( + action: UserTileSpecRepository.ChangeAction, + newList: List<TileSpec>, + userId: Int, + ) { + tileListLogBuffer.log( + TILE_LIST_TAG, + LogLevel.DEBUG, + { + str1 = action.toString() + str2 = newList.toString() + int1 = userId + }, + { "Processing $str1 for user $int1\nNew list: $str2" } ) } @@ -139,6 +160,30 @@ constructor( ) } + fun logAutoAddTilesParsed(userId: Int, tiles: Set<TileSpec>) { + tileAutoAddLogBuffer.log( + AUTO_ADD_TAG, + LogLevel.DEBUG, + { + str1 = tiles.toString() + int1 = userId + }, + { "Auto add tiles parsed for user $int1: $str1" } + ) + } + + fun logAutoAddTilesRestoredReconciled(userId: Int, tiles: Set<TileSpec>) { + tileAutoAddLogBuffer.log( + AUTO_ADD_TAG, + LogLevel.DEBUG, + { + str1 = tiles.toString() + int1 = userId + }, + { "Auto-add tiles reconciled for user $int1: $str1" } + ) + } + fun logTileAutoAdded(userId: Int, spec: TileSpec, position: Int) { tileAutoAddLogBuffer.log( AUTO_ADD_TAG, @@ -164,6 +209,23 @@ constructor( ) } + fun logSettingsRestored(restoreData: RestoreData) { + restoreLogBuffer.log( + RESTORE_TAG, + LogLevel.DEBUG, + { + int1 = restoreData.userId + str1 = restoreData.restoredTiles.toString() + str2 = restoreData.restoredAutoAddedTiles.toString() + }, + { + "Restored settings data for user $int1\n" + + "\tRestored tiles: $str1\n" + + "\tRestored auto added tiles: $str2" + } + ) + } + /** Reasons for destroying an existing tile. */ enum class TileDestroyedReason(val readable: String) { TILE_REMOVED("Tile removed from current set"), diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 9c7a73412518..38bbce45e143 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -51,15 +51,15 @@ public class QSFactoryImpl implements QSFactory { protected final Map<String, Provider<QSTileImpl<?>>> mTileMap; private final Lazy<QSHost> mQsHostLazy; - private final Provider<CustomTile.Builder> mCustomTileBuilderProvider; + private final Provider<CustomTile.Factory> mCustomTileFactoryProvider; @Inject public QSFactoryImpl( Lazy<QSHost> qsHostLazy, - Provider<CustomTile.Builder> customTileBuilderProvider, + Provider<CustomTile.Factory> customTileFactoryProvider, Map<String, Provider<QSTileImpl<?>>> tileMap) { mQsHostLazy = qsHostLazy; - mCustomTileBuilderProvider = customTileBuilderProvider; + mCustomTileFactoryProvider = customTileFactoryProvider; mTileMap = tileMap; } @@ -70,6 +70,7 @@ public class QSFactoryImpl implements QSFactory { if (tile != null) { tile.initialize(); tile.postStale(); // Tile was just created, must be stale. + tile.setTileSpec(tileSpec); } return tile; } @@ -86,7 +87,7 @@ public class QSFactoryImpl implements QSFactory { // Custom tiles if (tileSpec.startsWith(CustomTile.PREFIX)) { return CustomTile.create( - mCustomTileBuilderProvider.get(), tileSpec, mQsHostLazy.get().getUserContext()); + mCustomTileFactoryProvider.get(), tileSpec, mQsHostLazy.get().getUserContext()); } // Broken tiles. diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt index e9f907c4d8e7..9d100728e643 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt @@ -1,11 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.base.actions import android.content.Intent +import android.view.View import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import javax.inject.Inject /** @@ -17,9 +33,9 @@ class QSTileIntentUserActionHandler @Inject constructor(private val activityStarter: ActivityStarter) { - fun handle(userAction: QSTileUserAction, intent: Intent) { + fun handle(view: View?, intent: Intent) { val animationController: ActivityLaunchAnimator.Controller? = - userAction.view?.let { + view?.let { ActivityLaunchAnimator.Controller.fromView( it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt new file mode 100644 index 000000000000..056f967b09bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.base.interactor + +import android.content.Context +import androidx.annotation.VisibleForTesting +import androidx.annotation.WorkerThread +import com.android.settingslib.RestrictedLockUtils +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin +import com.android.settingslib.RestrictedLockUtilsInternal +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor.PolicyResult +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +/** + * Provides restrictions data for the tiles. This is used in + * [com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel] to determine if the tile is + * disabled based on the [com.android.systemui.qs.tiles.viewmodel.QSTileConfig.policy]. + */ +interface DisabledByPolicyInteractor { + + /** + * Checks if the tile is restricted by the policy for a specific user. Pass the result to the + * [handlePolicyResult] to let the user know that the tile is disable by the admin. + */ + suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult + + /** + * Returns true when [policyResult] is [PolicyResult.TileDisabled] and has been handled by this + * method. No further handling is required and the input event can be skipped at this point. + * + * Returns false when [policyResult] is [PolicyResult.TileEnabled] and this method has done + * nothing. + */ + fun handlePolicyResult(policyResult: PolicyResult): Boolean + + sealed interface PolicyResult { + /** Tile has no policy restrictions. */ + data object TileEnabled : PolicyResult + + /** + * Tile is disabled by policy. Pass this to [DisabledByPolicyInteractor.handlePolicyResult] + * to show the user info screen using + * [RestrictedLockUtils.getShowAdminSupportDetailsIntent]. + */ + data class TileDisabled(val admin: EnforcedAdmin) : PolicyResult + } +} + +@SysUISingleton +class DisabledByPolicyInteractorImpl +@Inject +constructor( + private val context: Context, + private val activityStarter: ActivityStarter, + private val restrictedLockProxy: RestrictedLockProxy, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) : DisabledByPolicyInteractor { + + override suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult = + withContext(backgroundDispatcher) { + val admin: EnforcedAdmin = + restrictedLockProxy.getEnforcedAdmin(userId, userRestriction) + ?: return@withContext PolicyResult.TileEnabled + + return@withContext if ( + !restrictedLockProxy.hasBaseUserRestriction(userId, userRestriction) + ) { + PolicyResult.TileDisabled(admin) + } else { + PolicyResult.TileEnabled + } + } + + override fun handlePolicyResult(policyResult: PolicyResult): Boolean = + when (policyResult) { + is PolicyResult.TileEnabled -> false + is PolicyResult.TileDisabled -> { + val intent = + RestrictedLockUtils.getShowAdminSupportDetailsIntent( + context, + policyResult.admin + ) + activityStarter.postStartActivityDismissingKeyguard(intent, 0) + true + } + } +} + +/** Mockable proxy for [RestrictedLockUtilsInternal] static methods. */ +@VisibleForTesting +class RestrictedLockProxy @Inject constructor(private val context: Context) { + + @WorkerThread + fun hasBaseUserRestriction(userId: Int, userRestriction: String?): Boolean = + RestrictedLockUtilsInternal.hasBaseUserRestriction( + context, + userRestriction, + userId, + ) + + @WorkerThread + fun getEnforcedAdmin(userId: Int, userRestriction: String?): EnforcedAdmin? = + RestrictedLockUtilsInternal.checkIfRestrictionEnforced( + context, + userRestriction, + userId, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt index 1a03481b9fca..7a22e3cf8bc8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.base.interactor import com.android.systemui.qs.tiles.viewmodel.QSTileState diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt index 82897044f06c..0aa6b0be5485 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.base.interactor data class QSTileDataRequest( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt index d6c9705a6aa1..2bc664311644 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.base.interactor import androidx.annotation.WorkerThread diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt index 8569fc73adb4..14fc639c8aa8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.base.interactor import android.annotation.WorkerThread diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt index ed7ec8e32de4..ffe38ddacfda 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.base.interactor import com.android.systemui.qs.tiles.viewmodel.QSTileState diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt index c2a75fac60f5..58a335e462a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt @@ -1,8 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.base.viewmodel import androidx.annotation.CallSuper import androidx.annotation.VisibleForTesting import com.android.internal.util.Preconditions +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper @@ -10,10 +28,13 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle +import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.util.kotlin.sample +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob @@ -25,6 +46,7 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -37,40 +59,35 @@ import kotlinx.coroutines.flow.stateIn * Provides a hassle-free way to implement new tiles according to current System UI architecture * standards. THis ViewModel is cheap to instantiate and does nothing until it's moved to * [QSTileLifecycle.ALIVE] state. + * + * Inject [BaseQSTileViewModel.Factory] to create a new instance of this class. */ -abstract class BaseQSTileViewModel<DATA_TYPE> +class BaseQSTileViewModel<DATA_TYPE> @VisibleForTesting constructor( - final override val config: QSTileConfig, + override val config: QSTileConfig, private val userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>, private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, private val mapper: QSTileDataToStateMapper<DATA_TYPE>, + private val disabledByPolicyInteractor: DisabledByPolicyInteractor, private val backgroundDispatcher: CoroutineDispatcher, private val tileScope: CoroutineScope, ) : QSTileViewModel { - /** - * @param config contains all the static information (like TileSpec) about the tile. - * @param userActionInteractor encapsulates user input processing logic. Use it to start - * activities, show dialogs or otherwise update the tile state. - * @param tileDataInteractor provides [DATA_TYPE] and its availability. - * @param backgroundDispatcher is used to run the internal [DATA_TYPE] processing and call - * interactors methods. This should likely to be @Background CoroutineDispatcher. - * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View layer. - * It's called in [backgroundDispatcher], so it's safe to perform long running operations - * there. - */ + @AssistedInject constructor( - config: QSTileConfig, - userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>, - tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, - mapper: QSTileDataToStateMapper<DATA_TYPE>, - backgroundDispatcher: CoroutineDispatcher, + @Assisted config: QSTileConfig, + @Assisted userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>, + @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, + @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>, + disabledByPolicyInteractor: DisabledByPolicyInteractor, + @Background backgroundDispatcher: CoroutineDispatcher, ) : this( config, userActionInteractor, tileDataInteractor, mapper, + disabledByPolicyInteractor, backgroundDispatcher, CoroutineScope(SupervisorJob()) ) @@ -92,7 +109,7 @@ constructor( .stateIn( tileScope, SharingStarted.WhileSubscribed(), - false, + true, ) private var currentLifeState: QSTileLifecycle = QSTileLifecycle.DEAD @@ -145,7 +162,7 @@ constructor( userIds .flatMapLatest { userId -> merge( - userInputFlow(), + userInputFlow(userId), forceUpdates.map { StateUpdateTrigger.ForceUpdate }, ) .onStart { emit(StateUpdateTrigger.InitialRequest) } @@ -172,14 +189,41 @@ constructor( replay = 1, // we only care about the most recent value ) - private fun userInputFlow(): Flow<StateUpdateTrigger> { + private fun userInputFlow(userId: Int): Flow<StateUpdateTrigger> { data class StateWithData<T>(val state: QSTileState, val data: T) + return when (config.policy) { + is QSTilePolicy.NoRestrictions -> userInputs + is QSTilePolicy.Restricted -> + userInputs.filter { + val result = + disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction) + !disabledByPolicyInteractor.handlePolicyResult(result) + } // Skip the input until there is some data - return userInputs.sample( - state.combine(tileData) { state, data -> StateWithData(state, data) } - ) { input, stateWithData -> + }.sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) { + input, + stateWithData -> StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data) } } + + interface Factory<T> { + + /** + * @param config contains all the static information (like TileSpec) about the tile. + * @param userActionInteractor encapsulates user input processing logic. Use it to start + * activities, show dialogs or otherwise update the tile state. + * @param tileDataInteractor provides [DATA_TYPE] and its availability. + * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View + * layer. It's called in [backgroundDispatcher], so it's safe to perform long running + * operations there. + */ + fun create( + config: QSTileConfig, + userActionInteractor: QSTileUserActionInteractor<T>, + tileDataInteractor: QSTileDataInteractor<T>, + mapper: QSTileDataToStateMapper<T>, + ): BaseQSTileViewModel<T> + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt new file mode 100644 index 000000000000..d0809c52acd9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.di + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.plugins.qs.QSFactory +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter +import javax.inject.Inject +import javax.inject.Provider + +// TODO(b/http://b/299909989): Rename the factory after rollout +@SysUISingleton +class NewQSTileFactory +@Inject +constructor( + private val adapterFactory: QSTileViewModelAdapter.Factory, + private val tileMap: + Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>, +) : QSFactory { + + override fun createTile(tileSpec: String): QSTile? = + tileMap[tileSpec]?.let { + val tile = it.get() + tile.onLifecycle(QSTileLifecycle.ALIVE) + adapterFactory.create(tile) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt index a5eaac154230..1a6cf99ab810 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt @@ -1,14 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.viewmodel -import android.graphics.drawable.Icon +import androidx.annotation.StringRes +import com.android.internal.logging.InstanceId +import com.android.systemui.common.shared.model.Icon import com.android.systemui.qs.pipeline.shared.TileSpec data class QSTileConfig( val tileSpec: TileSpec, val tileIcon: Icon, - val tileLabel: CharSequence, -// TODO(b/299908705): Fill necessary params -/* -val instanceId: InstanceId, - */ + @StringRes val tileLabelRes: Int, + val instanceId: InstanceId, + val policy: QSTilePolicy = QSTilePolicy.NoRestrictions, ) + +/** Represents policy restrictions that may be imposed on the tile. */ +sealed interface QSTilePolicy { + /** Tile has no policy restrictions */ + data object NoRestrictions : QSTilePolicy + + /** + * Tile might be disabled by policy. [userRestriction] is usually a constant from + * [android.os.UserManager] like [android.os.UserManager.DISALLOW_AIRPLANE_MODE]. + * [com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor] is commonly used + * to resolve this and show user a message when needed. + */ + data class Restricted(val userRestriction: String) : QSTilePolicy +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt index 1d5c1bcada1f..6d7c57605bec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.viewmodel enum class QSTileLifecycle { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt index 53f9edfb954c..0ccde741e2cc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -1,18 +1,112 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.viewmodel -import android.graphics.drawable.Icon +import android.service.quicksettings.Tile +import com.android.systemui.common.shared.model.Icon +/** + * Represents current a state of the tile to be displayed in on the view. Consider using + * [QSTileState.build] for better state creation experience and preset default values for certain + * fields. + * + * // TODO(b/http://b/299909989): Clean up legacy mappings after the transition + */ data class QSTileState( - val icon: Icon, + val icon: () -> Icon, val label: CharSequence, -// TODO(b/299908705): Fill necessary params -/* - val subtitle: CharSequence = "", - val activeState: ActivationState = Active, - val enabledState: Enabled = Enabled, - val loopIconAnimation: Boolean = false, - val secondaryIcon: Icon? = null, - val slashState: SlashState? = null, - val supportedActions: Collection<UserAction> = listOf(Click), clicks should be a default action -*/ -) + val activationState: ActivationState, + val secondaryLabel: CharSequence?, + val supportedActions: Set<UserAction>, + val contentDescription: CharSequence?, + val stateDescription: CharSequence?, + val sideViewIcon: SideViewIcon, + val enabledState: EnabledState, + val expandedAccessibilityClassName: String?, +) { + + companion object { + + fun build(icon: () -> Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState = + Builder(icon, label).apply(build).build() + + fun build(icon: Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState = + build({ icon }, label, build) + } + + enum class ActivationState(val legacyState: Int) { + // An unavailable state indicates that for some reason this tile is not currently available + // to the user, and will have no click action. The tile's icon will be tinted differently to + // reflect this state. + UNAVAILABLE(Tile.STATE_UNAVAILABLE), + // This represents a tile that is currently active. (e.g. wifi is connected, bluetooth is + // on, cast is casting). This is the default state. + ACTIVE(Tile.STATE_ACTIVE), + // This represents a tile that is currently in a disabled state but is still interactable. A + // disabled state indicates that the tile is not currently active (e.g. wifi disconnected or + // bluetooth disabled), but is still interactable by the user to modify this state. + INACTIVE(Tile.STATE_INACTIVE), + } + + /** + * Enabled tile behaves as usual where is disabled one is frozen and inactive in its current + * [ActivationState]. + */ + enum class EnabledState { + ENABLED, + DISABLED, + } + + enum class UserAction { + CLICK, + LONG_CLICK, + } + + sealed interface SideViewIcon { + data class Custom(val icon: Icon) : SideViewIcon + data object Chevron : SideViewIcon + data object None : SideViewIcon + } + + class Builder( + var icon: () -> Icon, + var label: CharSequence, + ) { + var activationState: ActivationState = ActivationState.INACTIVE + var secondaryLabel: CharSequence? = null + var supportedActions: Set<UserAction> = setOf(UserAction.CLICK) + var contentDescription: CharSequence? = null + var stateDescription: CharSequence? = null + var sideViewIcon: SideViewIcon = SideViewIcon.None + var enabledState: EnabledState = EnabledState.ENABLED + var expandedAccessibilityClassName: String? = null + + fun build(): QSTileState = + QSTileState( + icon, + label, + activationState, + secondaryLabel, + supportedActions, + contentDescription, + stateDescription, + sideViewIcon, + enabledState, + expandedAccessibilityClassName, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt index f1f8f0152c67..a1450420131b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt @@ -1,13 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.viewmodel -import android.content.Context import android.view.View sealed interface QSTileUserAction { - val context: Context val view: View? - class Click(override val context: Context, override val view: View?) : QSTileUserAction - class LongClick(override val context: Context, override val view: View?) : QSTileUserAction + class Click(override val view: View?) : QSTileUserAction + class LongClick(override val view: View?) : QSTileUserAction } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt index d66d0a195e02..e5cb7ea3e098 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.viewmodel import kotlinx.coroutines.flow.SharedFlow diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt new file mode 100644 index 000000000000..f6299e38ae18 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import android.content.Context +import android.util.Log +import android.view.View +import androidx.annotation.GuardedBy +import com.android.internal.logging.InstanceId +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon +import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.function.Supplier +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.flow.collectIndexed +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +// TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout +class QSTileViewModelAdapter +@AssistedInject +constructor( + private val qsHost: QSHost, + @Assisted private val qsTileViewModel: QSTileViewModel, +) : QSTile { + + private val context + get() = qsHost.context + + @GuardedBy("callbacks") + private val callbacks: MutableCollection<QSTile.Callback> = mutableSetOf() + @GuardedBy("listeningClients") + private val listeningClients: MutableCollection<Any> = mutableSetOf() + + // Cancels the jobs when the adapter is no longer alive + private val adapterScope = CoroutineScope(SupervisorJob()) + // Cancels the jobs when clients stop listening + private val listeningScope = CoroutineScope(SupervisorJob()) + + init { + adapterScope.launch { + qsTileViewModel.isAvailable.collectIndexed { index, isAvailable -> + if (!isAvailable) { + qsHost.removeTile(tileSpec) + } + // qsTileViewModel.isAvailable flow often starts with isAvailable == true. That's + // why we only allow isAvailable == true once and throw an exception afterwards. + if (index > 0 && isAvailable) { + // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for additional + // guidance on how to auto add your tile + throw UnsupportedOperationException("Turning on tile is not supported now") + } + } + } + + // QSTileHost doesn't call this when userId is initialized + userSwitch(qsHost.userId) + + if (DEBUG) { + Log.d(TAG, "Using new tiles for: $tileSpec") + } + } + + override fun isAvailable(): Boolean = qsTileViewModel.isAvailable.value + + override fun setTileSpec(tileSpec: String?) { + throw UnsupportedOperationException("Tile spec is immutable in new tiles") + } + + override fun refreshState() { + qsTileViewModel.forceUpdate() + } + + override fun addCallback(callback: QSTile.Callback?) { + callback ?: return + synchronized(callbacks) { callbacks.add(callback) } + } + + override fun removeCallback(callback: QSTile.Callback?) { + callback ?: return + synchronized(callbacks) { callbacks.remove(callback) } + } + + override fun removeCallbacks() { + synchronized(callbacks) { callbacks.clear() } + } + + override fun click(view: View?) { + if (isActionSupported(QSTileState.UserAction.CLICK)) { + qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view)) + } + } + + override fun secondaryClick(view: View?) { + if (isActionSupported(QSTileState.UserAction.CLICK)) { + qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view)) + } + } + + override fun longClick(view: View?) { + if (isActionSupported(QSTileState.UserAction.LONG_CLICK)) { + qsTileViewModel.onActionPerformed(QSTileUserAction.LongClick(view)) + } + } + + private fun isActionSupported(action: QSTileState.UserAction): Boolean = + qsTileViewModel.currentState?.supportedActions?.contains(action) == true + + override fun userSwitch(currentUser: Int) { + qsTileViewModel.onUserIdChanged(currentUser) + } + + @Deprecated( + "Not needed as {@link com.android.internal.logging.UiEvent} will use #getMetricsSpec", + replaceWith = ReplaceWith("getMetricsSpec"), + ) + override fun getMetricsCategory(): Int = 0 + + override fun setListening(client: Any?, listening: Boolean) { + client ?: return + synchronized(listeningClients) { + if (listening) { + listeningClients.add(client) + if (listeningClients.size == 1) { + qsTileViewModel.state + .map { mapState(context, it, qsTileViewModel.config) } + .onEach { legacyState -> + synchronized(callbacks) { + callbacks.forEach { it.onStateChanged(legacyState) } + } + } + .launchIn(listeningScope) + } + } else { + listeningClients.remove(client) + if (listeningClients.isEmpty()) { + listeningScope.coroutineContext.cancelChildren() + } + } + } + } + + override fun isListening(): Boolean = + synchronized(listeningClients) { listeningClients.isNotEmpty() } + + override fun setDetailListening(show: Boolean) { + // do nothing like QSTileImpl + } + + override fun destroy() { + adapterScope.cancel() + listeningScope.cancel() + qsTileViewModel.onLifecycle(QSTileLifecycle.DEAD) + } + + override fun getState(): QSTile.State? = + qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) } + + override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId + override fun getTileLabel(): CharSequence = + context.getString(qsTileViewModel.config.tileLabelRes) + override fun getTileSpec(): String = qsTileViewModel.config.tileSpec.spec + + private companion object { + + const val DEBUG = false + const val TAG = "QSTileVMAdapter" + + fun mapState( + context: Context, + viewModelState: QSTileState, + config: QSTileConfig + ): QSTile.State = + // we have to use QSTile.BooleanState to support different side icons + // which are bound to instanceof QSTile.BooleanState in QSTileView. + QSTile.BooleanState().apply { + spec = config.tileSpec.spec + label = viewModelState.label + // This value is synthetic and doesn't have any meaning + value = false + + secondaryLabel = viewModelState.secondaryLabel + handlesLongClick = + viewModelState.supportedActions.contains(QSTileState.UserAction.LONG_CLICK) + + iconSupplier = Supplier { + when (val stateIcon = viewModelState.icon()) { + is Icon.Loaded -> DrawableIcon(stateIcon.drawable) + is Icon.Resource -> ResourceIcon.get(stateIcon.res) + } + } + state = viewModelState.activationState.legacyState + + contentDescription = viewModelState.contentDescription + stateDescription = viewModelState.stateDescription + + disabledByPolicy = viewModelState.enabledState == QSTileState.EnabledState.DISABLED + expandedAccessibilityClassName = viewModelState.expandedAccessibilityClassName + + when (viewModelState.sideViewIcon) { + is QSTileState.SideViewIcon.Custom -> { + sideViewCustomDrawable = + when (viewModelState.sideViewIcon.icon) { + is Icon.Loaded -> viewModelState.sideViewIcon.icon.drawable + is Icon.Resource -> + context.getDrawable(viewModelState.sideViewIcon.icon.res) + } + } + is QSTileState.SideViewIcon.Chevron -> { + forceExpandIcon = true + } + is QSTileState.SideViewIcon.None -> { + forceExpandIcon = false + } + } + } + } + + @AssistedFactory + interface Factory { + + fun create(qsTileViewModel: QSTileViewModel): QSTileViewModelAdapter + } +} 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 722d3661d0ae..a3499bd5c264 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 @@ -26,6 +26,7 @@ import com.android.systemui.classifier.FalsingCollectorActual import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.DisplayId +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.model.SysUiState @@ -63,6 +64,7 @@ class SceneContainerStartable constructor( @Application private val applicationScope: CoroutineScope, private val sceneInteractor: SceneInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, private val authenticationInteractor: AuthenticationInteractor, private val keyguardInteractor: KeyguardInteractor, private val flags: SceneContainerFlags, @@ -119,7 +121,7 @@ constructor( /** Switches between scenes based on ever-changing application state. */ private fun automaticallySwitchScenes() { applicationScope.launch { - authenticationInteractor.isUnlocked + deviceEntryInteractor.isUnlocked .mapNotNull { isUnlocked -> val renderedScenes = when (val transitionState = sceneInteractor.transitionState.value) { @@ -130,7 +132,6 @@ constructor( transitionState.toScene, ) } - val isBypassEnabled = authenticationInteractor.isBypassEnabled() when { isUnlocked -> when { @@ -141,7 +142,7 @@ constructor( // When the device becomes unlocked in Lockscreen, go to Gone if // bypass is enabled. renderedScenes.contains(SceneKey.Lockscreen) -> - if (isBypassEnabled) { + if (deviceEntryInteractor.isBypassEnabled()) { SceneKey.Gone to "device unlocked in Lockscreen scene with bypass" } else { @@ -191,7 +192,7 @@ constructor( } WakefulnessState.STARTING_TO_WAKE -> { val authMethod = authenticationInteractor.getAuthenticationMethod() - val isUnlocked = authenticationInteractor.isUnlocked.value + val isUnlocked = deviceEntryInteractor.isUnlocked.value when { authMethod == AuthenticationMethodModel.None -> { switchToScene( @@ -241,7 +242,7 @@ constructor( /** Collects and reports signals into the falsing system. */ private fun collectFalsingSignals() { applicationScope.launch { - authenticationInteractor.isLockscreenDismissed.collect { isLockscreenDismissed -> + deviceEntryInteractor.isDeviceEntered.collect { isLockscreenDismissed -> if (isLockscreenDismissed) { falsingCollector.onSuccessfulUnlock() } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index f08eb143efb4..4b3bd0b44bc9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -53,9 +53,6 @@ import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; import android.hardware.display.DisplayManager; -import android.media.AudioAttributes; -import android.media.AudioSystem; -import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.os.Process; @@ -86,8 +83,6 @@ import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import android.window.WindowContext; -import androidx.concurrent.futures.CallbackToFutureAdapter; - import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; @@ -108,7 +103,6 @@ import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; -import java.io.File; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; @@ -116,11 +110,11 @@ import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Supplier; +import javax.inject.Provider; + /** * Controls the state and flow for screenshots. @@ -274,7 +268,8 @@ public class ScreenshotController { private final WindowManager mWindowManager; private final WindowManager.LayoutParams mWindowLayoutParams; private final AccessibilityManager mAccessibilityManager; - private final ListenableFuture<MediaPlayer> mCameraSound; + @Nullable + private final ScreenshotSoundController mScreenshotSoundController; private final ScrollCaptureClient mScrollCaptureClient; private final PhoneWindow mWindow; private final DisplayManager mDisplayManager; @@ -339,6 +334,7 @@ public class ScreenshotController { UserManager userManager, AssistContentRequester assistContentRequester, MessageContainerController messageContainerController, + Provider<ScreenshotSoundController> screenshotSoundController, @Assisted int displayId ) { mScreenshotSmartActions = screenshotSmartActions; @@ -387,8 +383,12 @@ public class ScreenshotController { mConfigChanges.applyNewConfig(context.getResources()); reloadAssets(); - // Setup the Camera shutter sound - mCameraSound = loadCameraSound(); + // Sound is only reproduced from the controller of the default display. + if (displayId == Display.DEFAULT_DISPLAY) { + mScreenshotSoundController = screenshotSoundController.get(); + } else { + mScreenshotSoundController = null; + } mCopyBroadcastReceiver = new BroadcastReceiver() { @Override @@ -573,17 +573,8 @@ public class ScreenshotController { } private void releaseMediaPlayer() { - // Note that this may block if the sound is still being loaded (very unlikely) but we can't - // reliably release in the background because the service is being destroyed. - try { - MediaPlayer player = mCameraSound.get(1, TimeUnit.SECONDS); - if (player != null) { - player.release(); - } - } catch (InterruptedException | ExecutionException | TimeoutException e) { - mCameraSound.cancel(true); - Log.w(TAG, "Error releasing shutter sound", e); - } + if (mScreenshotSoundController == null) return; + mScreenshotSoundController.releaseScreenshotSound(); } private void respondToKeyDismissal() { @@ -889,39 +880,10 @@ public class ScreenshotController { } } - private ListenableFuture<MediaPlayer> loadCameraSound() { - // The media player creation is slow and needs on the background thread. - return CallbackToFutureAdapter.getFuture((completer) -> { - mBgExecutor.execute(() -> { - try { - MediaPlayer player = MediaPlayer.create(mContext, - Uri.fromFile(new File(mContext.getResources().getString( - com.android.internal.R.string.config_cameraShutterSound))), - null, - new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .build(), AudioSystem.newAudioSessionId()); - completer.set(player); - } catch (IllegalStateException e) { - Log.w(TAG, "Screenshot sound initialization failed", e); - completer.set(null); - } - }); - return "ScreenshotController#loadCameraSound"; - }); - } - - private void playCameraSound() { - mCameraSound.addListener(() -> { - try { - MediaPlayer player = mCameraSound.get(); - if (player != null) { - player.start(); - } - } catch (InterruptedException | ExecutionException e) { - } - }, mBgExecutor); + private void playCameraSoundIfNeeded() { + if (mScreenshotSoundController == null) return; + // the controller is not-null only on the default display controller + mScreenshotSoundController.playCameraSound(); } /** @@ -930,7 +892,7 @@ public class ScreenshotController { */ private void saveScreenshotAndToast(UserHandle owner, Consumer<Uri> finisher) { // Play the shutter sound to notify that we've taken a screenshot - playCameraSound(); + playCameraSoundIfNeeded(); saveScreenshotInWorkerThread( owner, @@ -974,7 +936,7 @@ public class ScreenshotController { } // Play the shutter sound to notify that we've taken a screenshot - playCameraSound(); + playCameraSoundIfNeeded(); if (DEBUG_ANIM) { Log.d(TAG, "starting post-screenshot animation"); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt new file mode 100644 index 000000000000..cd0cab556c4d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt @@ -0,0 +1,79 @@ +/* + * 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.screenshot + +import android.media.MediaPlayer +import android.util.Log +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.TraceUtils.Companion.tracedAsync +import com.google.errorprone.annotations.CanIgnoreReturnValue +import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.withTimeout + +/** Controls sound reproduction after a screenshot is taken. */ +interface ScreenshotSoundController { + /** Reproduces the camera sound. */ + @CanIgnoreReturnValue fun playCameraSound(): Deferred<Unit> + + /** Releases the sound. [playCameraSound] behaviour is undefined after this has been called. */ + @CanIgnoreReturnValue fun releaseScreenshotSound(): Deferred<Unit> +} + +class ScreenshotSoundControllerImpl +@Inject +constructor( + private val soundProvider: ScreenshotSoundProvider, + @Application private val coroutineScope: CoroutineScope, + @Background private val bgDispatcher: CoroutineDispatcher +) : ScreenshotSoundController { + + val player: Deferred<MediaPlayer?> = + coroutineScope.tracedAsync("loadCameraSound", bgDispatcher) { + try { + soundProvider.getScreenshotSound() + } catch (e: IllegalStateException) { + Log.w(TAG, "Screenshot sound initialization failed", e) + null + } + } + + override fun playCameraSound(): Deferred<Unit> { + return coroutineScope.tracedAsync("playCameraSound", bgDispatcher) { + player.await()?.start() + } + } + override fun releaseScreenshotSound(): Deferred<Unit> { + return coroutineScope.tracedAsync("releaseScreenshotSound", bgDispatcher) { + try { + withTimeout(1.seconds) { player.await()?.release() } + } catch (e: TimeoutCancellationException) { + player.cancel() + Log.w(TAG, "Error releasing shutter sound", e) + } + } + } + + private companion object { + const val TAG = "ScreenshotSoundControllerImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundProvider.kt new file mode 100644 index 000000000000..73151d5298c9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundProvider.kt @@ -0,0 +1,57 @@ +/* + * 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.screenshot + +import android.content.Context +import android.media.AudioAttributes +import android.media.AudioSystem +import android.media.MediaPlayer +import android.net.Uri +import com.android.internal.R +import com.android.systemui.dagger.SysUISingleton +import java.io.File +import javax.inject.Inject + +/** Provides a [MediaPlayer] that reproduces the screenshot sound. */ +interface ScreenshotSoundProvider { + + /** + * Creates a new [MediaPlayer] that reproduces the screenshot sound. This should be called from + * a background thread, as it might take time. + */ + fun getScreenshotSound(): MediaPlayer +} + +@SysUISingleton +class ScreenshotSoundProviderImpl +@Inject +constructor( + private val context: Context, +) : ScreenshotSoundProvider { + override fun getScreenshotSound(): MediaPlayer { + return MediaPlayer.create( + context, + Uri.fromFile(File(context.resources.getString(R.string.config_cameraShutterSound))), + /* holder = */ null, + AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build(), + AudioSystem.newAudioSessionId() + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java index 7d17d4c72b76..3797b8b41e5a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java @@ -25,6 +25,10 @@ import com.android.systemui.screenshot.ScreenshotPolicy; import com.android.systemui.screenshot.ScreenshotPolicyImpl; import com.android.systemui.screenshot.ScreenshotProxyService; import com.android.systemui.screenshot.ScreenshotRequestProcessor; +import com.android.systemui.screenshot.ScreenshotSoundController; +import com.android.systemui.screenshot.ScreenshotSoundControllerImpl; +import com.android.systemui.screenshot.ScreenshotSoundProvider; +import com.android.systemui.screenshot.ScreenshotSoundProviderImpl; import com.android.systemui.screenshot.TakeScreenshotService; import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService; import com.android.systemui.screenshot.appclips.AppClipsService; @@ -69,4 +73,12 @@ public abstract class ScreenshotModule { @Binds abstract ScreenshotRequestProcessor bindScreenshotRequestProcessor( RequestProcessor requestProcessor); + + @Binds + abstract ScreenshotSoundProvider bindScreenshotSoundProvider( + ScreenshotSoundProviderImpl screenshotSoundProviderImpl); + + @Binds + abstract ScreenshotSoundController bindScreenshotSoundController( + ScreenshotSoundControllerImpl screenshotSoundProviderImpl); } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java index b7152370624e..6fa592c6dd78 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java @@ -30,12 +30,13 @@ import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; -import com.android.systemui.res.R; import com.android.systemui.classifier.Classifier; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.haptics.slider.SeekableSliderEventProducer; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.res.R; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.util.ViewController; @@ -283,6 +284,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV private final VibratorHelper mVibratorHelper; private final SystemClock mSystemClock; private final CoroutineDispatcher mMainDispatcher; + private final ActivityStarter mActivityStarter; @Inject public Factory( @@ -291,14 +293,15 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV VibratorHelper vibratorHelper, SystemClock clock, FeatureFlagsClassic featureFlags, - @Main CoroutineDispatcher mainDispatcher - ) { + @Main CoroutineDispatcher mainDispatcher, + ActivityStarter activityStarter) { mFalsingManager = falsingManager; mUiEventLogger = uiEventLogger; mFeatureFlags = featureFlags; mVibratorHelper = vibratorHelper; mSystemClock = clock; mMainDispatcher = mainDispatcher; + mActivityStarter = activityStarter; } /** @@ -314,6 +317,8 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV int layout = getLayout(); BrightnessSliderView root = (BrightnessSliderView) LayoutInflater.from(context) .inflate(layout, viewRoot, false); + root.setActivityStarter(mActivityStarter); + BrightnessSliderHapticPlugin plugin; if (mFeatureFlags.isEnabled(HAPTIC_BRIGHTNESS_SLIDER)) { plugin = new BrightnessSliderHapticPluginImpl( diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java index c88549224183..5ecf07f5a264 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java @@ -33,6 +33,7 @@ import androidx.annotation.Nullable; import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.res.R; /** @@ -41,6 +42,7 @@ import com.android.systemui.res.R; */ public class BrightnessSliderView extends FrameLayout { + private ActivityStarter mActivityStarter; @NonNull private ToggleSeekBar mSlider; private DispatchTouchEventListener mListener; @@ -57,6 +59,10 @@ public class BrightnessSliderView extends FrameLayout { super(context, attrs); } + public void setActivityStarter(@NonNull ActivityStarter activityStarter) { + mActivityStarter = activityStarter; + } + // Inflated from quick_settings_brightness_dialog @Override protected void onFinishInflate() { @@ -65,6 +71,7 @@ public class BrightnessSliderView extends FrameLayout { mSlider = requireViewById(R.id.slider); mSlider.setAccessibilityLabel(getContentDescription().toString()); + mSlider.setActivityStarter(mActivityStarter); // Finds the progress drawable. Assumes brightness_progress_drawable.xml try { diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java index a5a0ae70045e..6ec10da28000 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java @@ -23,8 +23,9 @@ import android.view.MotionEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.SeekBar; +import androidx.annotation.NonNull; + import com.android.settingslib.RestrictedLockUtils; -import com.android.systemui.Dependency; import com.android.systemui.plugins.ActivityStarter; public class ToggleSeekBar extends SeekBar { @@ -32,6 +33,8 @@ public class ToggleSeekBar extends SeekBar { private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null; + private ActivityStarter mActivityStarter; + public ToggleSeekBar(Context context) { super(context); } @@ -49,7 +52,7 @@ public class ToggleSeekBar extends SeekBar { if (mEnforcedAdmin != null) { Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent( mContext, mEnforcedAdmin); - Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0); + mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); return true; } if (!isEnabled()) { @@ -74,4 +77,8 @@ public class ToggleSeekBar extends SeekBar { public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) { mEnforcedAdmin = admin; } + + public void setActivityStarter(@NonNull ActivityStarter activityStarter) { + mActivityStarter = activityStarter; + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index d5934e614d4e..1038c6756432 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -114,7 +114,6 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; import com.android.systemui.Gefingerpoken; -import com.android.systemui.res.R; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.LaunchAnimator; import com.android.systemui.biometrics.AuthController; @@ -163,6 +162,7 @@ import com.android.systemui.plugins.FalsingManager.FalsingTapListener; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; +import com.android.systemui.res.R; import com.android.systemui.shade.data.repository.ShadeRepository; import com.android.systemui.shade.transition.ShadeTransitionController; import com.android.systemui.shared.system.QuickStepContract; @@ -393,7 +393,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private TrackingStartedListener mTrackingStartedListener; private OpenCloseListener mOpenCloseListener; private GestureRecorder mGestureRecorder; - private boolean mPanelExpanded; private boolean mKeyguardQsUserSwitchEnabled; private boolean mKeyguardUserSwitcherEnabled; @@ -1321,7 +1320,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // when we switch between split shade and regular shade we want to enforce setting qs to // the default state: expanded for split shade and collapsed otherwise - if (!isKeyguardShowing() && mPanelExpanded) { + if (!isKeyguardShowing() && isPanelExpanded()) { mQsController.setExpanded(mSplitShadeEnabled); } if (isKeyguardShowing() && mQsController.getExpanded() && mSplitShadeEnabled) { @@ -2630,10 +2629,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void updatePanelExpanded() { boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown; - if (mPanelExpanded != isExpanded) { - mPanelExpanded = isExpanded; + if (isPanelExpanded() != isExpanded) { + setExpandedOrAwaitingInputTransfer(isExpanded); updateSystemUiStateFlags(); - mShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(mPanelExpanded); mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded); if (!isExpanded) { mQsController.closeQsCustomizer(); @@ -2641,9 +2639,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } + private void setExpandedOrAwaitingInputTransfer(boolean expandedOrAwaitingInputTransfer) { + mShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(expandedOrAwaitingInputTransfer); + } + @Override public boolean isPanelExpanded() { - return mPanelExpanded; + return mShadeRepository.getLegacyExpandedOrAwaitingInputTransfer().getValue(); } private int calculatePanelHeightShade() { @@ -3392,7 +3394,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ipw.print("mMaxAllowedKeyguardNotifications="); ipw.println(mMaxAllowedKeyguardNotifications); ipw.print("mAnimateNextPositionUpdate="); ipw.println(mAnimateNextPositionUpdate); - ipw.print("mPanelExpanded="); ipw.println(mPanelExpanded); + ipw.print("isPanelExpanded()="); ipw.println(isPanelExpanded()); ipw.print("mKeyguardQsUserSwitchEnabled="); ipw.println(mKeyguardQsUserSwitchEnabled); ipw.print("mKeyguardUserSwitcherEnabled="); ipw.println(mKeyguardUserSwitcherEnabled); ipw.print("mDozing="); ipw.println(mDozing); @@ -3606,7 +3608,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump + isFullyExpanded() + " inQs=" + mQsController.getExpanded()); } mSysUiState - .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, mPanelExpanded) + .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, isPanelExpanded()) .setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED, isFullyExpanded() && !mQsController.getExpanded()) .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index c803e6f0c9f3..6f5e41f626de 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONT import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.app.IActivityManager; import android.content.Context; @@ -48,7 +49,6 @@ import android.view.WindowManagerGlobal; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; @@ -58,7 +58,9 @@ import com.android.systemui.dump.DumpsysTableLogger; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; +import com.android.systemui.res.R; import com.android.systemui.scene.ui.view.WindowRootViewComponent; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -71,6 +73,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.KeyguardStateController; +import dagger.Lazy; + import java.io.PrintWriter; import java.lang.ref.Reference; import java.lang.ref.WeakReference; @@ -108,6 +112,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final KeyguardBypassController mKeyguardBypassController; private final Executor mBackgroundExecutor; private final AuthController mAuthController; + private final Lazy<ShadeInteractor> mShadeInteractorLazy; private ViewGroup mWindowRootView; private LayoutParams mLp; private boolean mHasTopUi; @@ -151,6 +156,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW ScreenOffAnimationController screenOffAnimationController, AuthController authController, ShadeExpansionStateManager shadeExpansionStateManager, + Lazy<ShadeInteractor> shadeInteractorLazy, ShadeWindowLogger logger) { mContext = context; mWindowRootViewComponentFactory = windowRootViewComponentFactory; @@ -171,12 +177,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed(); mLockScreenDisplayTimeout = context.getResources() .getInteger(R.integer.config_lockScreenDisplayTimeout); + mShadeInteractorLazy = shadeInteractorLazy; ((SysuiStatusBarStateController) statusBarStateController) .addCallback(mStateListener, SysuiStatusBarStateController.RANK_STATUS_BAR_WINDOW_CONTROLLER); configurationController.addCallback(this); shadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged); - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); float desiredPreferredRefreshRate = context.getResources() .getInteger(R.integer.config_keyguardRefreshRate); @@ -224,9 +230,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } @VisibleForTesting - void onShadeExpansionFullyChanged(Boolean isExpanded) { - if (mCurrentState.panelExpanded != isExpanded) { - mCurrentState.panelExpanded = isExpanded; + void onShadeOrQsExpanded(Boolean isExpanded) { + if (mCurrentState.shadeOrQsExpanded != isExpanded) { + mCurrentState.shadeOrQsExpanded = isExpanded; apply(mCurrentState); } } @@ -289,6 +295,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW public void fetchWindowRootView() { WindowRootViewComponent component = mWindowRootViewComponentFactory.create(); mWindowRootView = component.getWindowRootView(); + collectFlow( + mWindowRootView, + mShadeInteractorLazy.get().isAnyExpanded(), + this::onShadeOrQsExpanded + ); } @Override @@ -384,7 +395,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } private void applyFocusableFlag(NotificationShadeWindowState state) { - boolean panelFocusable = state.notificationShadeFocusable && state.panelExpanded; + boolean panelFocusable = state.notificationShadeFocusable && state.shadeOrQsExpanded; if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput) || ENABLE_REMOTE_INPUT && state.remoteInputActive // Make the panel focusable if we're doing the screen off animation, since the light @@ -408,7 +419,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } private void applyForceShowNavigationFlag(NotificationShadeWindowState state) { - if (state.panelExpanded || state.bouncerShowing + if (state.shadeOrQsExpanded || state.bouncerShowing || ENABLE_REMOTE_INPUT && state.remoteInputActive) { mLpChanged.forciblyShownTypes |= WindowInsets.Type.navigationBars(); } else { @@ -544,7 +555,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW state.keyguardOccluded, state.keyguardNeedsInput, state.panelVisible, - state.panelExpanded, + state.shadeOrQsExpanded, state.notificationShadeFocusable, state.bouncerShowing, state.keyguardFadingAway, @@ -582,7 +593,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mCurrentState.keyguardGoingAway, mCurrentState.bouncerShowing, mCurrentState.dozing, - mCurrentState.panelExpanded, + mCurrentState.shadeOrQsExpanded, mCurrentState.dreaming); } } @@ -833,7 +844,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW */ @Override public boolean getPanelExpanded() { - return mCurrentState.panelExpanded; + return mCurrentState.shadeOrQsExpanded; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt index e3010ca72194..fbe164a8077f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt @@ -32,7 +32,7 @@ class NotificationShadeWindowState( @JvmField var keyguardNeedsInput: Boolean = false, @JvmField var panelVisible: Boolean = false, /** shade panel is expanded (expansion fraction > 0) */ - @JvmField var panelExpanded: Boolean = false, + @JvmField var shadeOrQsExpanded: Boolean = false, @JvmField var notificationShadeFocusable: Boolean = false, @JvmField var bouncerShowing: Boolean = false, @JvmField var keyguardFadingAway: Boolean = false, @@ -70,7 +70,7 @@ class NotificationShadeWindowState( keyguardOccluded.toString(), keyguardNeedsInput.toString(), panelVisible.toString(), - panelExpanded.toString(), + shadeOrQsExpanded.toString(), notificationShadeFocusable.toString(), bouncerShowing.toString(), keyguardFadingAway.toString(), @@ -137,7 +137,7 @@ class NotificationShadeWindowState( this.keyguardOccluded = keyguardOccluded this.keyguardNeedsInput = keyguardNeedsInput this.panelVisible = panelVisible - this.panelExpanded = panelExpanded + this.shadeOrQsExpanded = panelExpanded this.notificationShadeFocusable = notificationShadeFocusable this.bouncerShowing = bouncerShowing this.keyguardFadingAway = keyguardFadingAway diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index e8be40eaf3fe..9b74ac4afed4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -791,7 +791,8 @@ public class QuickSettingsController implements Dumpable { /** update Qs height state */ public void setExpansionHeight(float height) { // TODO(b/277909752): remove below log when bug is fixed - if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0) { + if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0 + && mBarState == SHADE) { Log.wtf(TAG, "setting QS height to 0 in split shade while shade is open(ing). " + "Value of mExpandImmediate = " + mExpandImmediate); diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 251cc168161c..ac8333ae84ad 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -129,6 +129,9 @@ constructor( combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) } .stateIn(scope, SharingStarted.Eagerly, 0f) + /** Whether either the shade or QS is fully expanded. */ + val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged() + /** Whether either the shade or QS is expanding from a fully collapsed state. */ val isAnyExpanding: Flow<Boolean> = anyExpansion 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 index 068d5a59ca2e..9c5a20189dd2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -16,10 +16,10 @@ package com.android.systemui.shade.ui.viewmodel -import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.scene.shared.model.SceneKey import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -34,15 +34,15 @@ class ShadeSceneViewModel @Inject constructor( @Application private val applicationScope: CoroutineScope, - authenticationInteractor: AuthenticationInteractor, + deviceEntryInteractor: DeviceEntryInteractor, private val bouncerInteractor: BouncerInteractor, val shadeHeaderViewModel: ShadeHeaderViewModel, ) { /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: StateFlow<SceneKey> = combine( - authenticationInteractor.isUnlocked, - authenticationInteractor.canSwipeToDismiss, + deviceEntryInteractor.isUnlocked, + deviceEntryInteractor.canSwipeToEnter, ) { isUnlocked, canSwipeToDismiss -> upDestinationSceneKey( isUnlocked = isUnlocked, @@ -54,8 +54,8 @@ constructor( started = SharingStarted.WhileSubscribed(), initialValue = upDestinationSceneKey( - isUnlocked = authenticationInteractor.isUnlocked.value, - canSwipeToDismiss = authenticationInteractor.canSwipeToDismiss.value, + isUnlocked = deviceEntryInteractor.isUnlocked.value, + canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value, ), ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 670fb1289357..664103fa05d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -172,6 +172,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_LOCK_TASK_MODE_CHANGED = 75 << MSG_SHIFT; private static final int MSG_CONFIRM_IMMERSIVE_PROMPT = 77 << MSG_SHIFT; private static final int MSG_IMMERSIVE_CHANGED = 78 << MSG_SHIFT; + private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1; @@ -301,6 +302,8 @@ public class CommandQueue extends IStatusBar.Stub implements default void addQsTile(ComponentName tile) { } default void remQsTile(ComponentName tile) { } + + default void setQsTiles(String[] tiles) {} default void clickTile(ComponentName tile) { } default void handleSystemKey(KeyEvent arg1) { } @@ -439,6 +442,7 @@ public class CommandQueue extends IStatusBar.Stub implements * @see IStatusBar#requestAddTile */ default void requestAddTile( + int callingUid, @NonNull ComponentName componentName, @NonNull CharSequence appName, @NonNull CharSequence label, @@ -903,6 +907,13 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override + public void setQsTiles(String[] tiles) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_SET_QS_TILES, tiles).sendToTarget(); + } + } + + @Override public void clickQsTile(ComponentName tile) { synchronized (mLock) { mHandler.obtainMessage(MSG_CLICK_QS_TILE, tile).sendToTarget(); @@ -1304,6 +1315,7 @@ public class CommandQueue extends IStatusBar.Stub implements @Override public void requestAddTile( + int callingUid, @NonNull ComponentName componentName, @NonNull CharSequence appName, @NonNull CharSequence label, @@ -1316,6 +1328,7 @@ public class CommandQueue extends IStatusBar.Stub implements args.arg3 = label; args.arg4 = icon; args.arg5 = callback; + args.arg6 = callingUid; mHandler.obtainMessage(MSG_TILE_SERVICE_REQUEST_ADD, args).sendToTarget(); } @@ -1533,6 +1546,11 @@ public class CommandQueue extends IStatusBar.Stub implements mCallbacks.get(i).remQsTile((ComponentName) msg.obj); } break; + case MSG_SET_QS_TILES: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).setQsTiles((String[]) msg.obj); + } + break; case MSG_CLICK_QS_TILE: for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).clickTile((ComponentName) msg.obj); @@ -1757,8 +1775,9 @@ public class CommandQueue extends IStatusBar.Stub implements CharSequence label = (CharSequence) args.arg3; Icon icon = (Icon) args.arg4; IAddTileResultCallback callback = (IAddTileResultCallback) args.arg5; + int callingUid = (int) args.arg6; for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).requestAddTile( + mCallbacks.get(i).requestAddTile(callingUid, componentName, appName, label, icon, callback); } args.recycle(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java index d90824351c70..93c55de7d480 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -24,8 +24,6 @@ public interface NotificationLockscreenUserManager { String NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION = "com.android.systemui.statusbar.work_challenge_unlocked_notification_action"; - boolean shouldAllowLockscreenRemoteInput(); - /** * @param userId user Id * @return true if we re on a secure lock screen diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index ea5ca276a8cf..2147510bbde5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -49,8 +49,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.recents.OverviewProxyService; @@ -63,14 +61,14 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ListenerSet; import com.android.systemui.util.settings.SecureSettings; -import dagger.Lazy; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; +import dagger.Lazy; + /** * Handles keeping track of the current user, profiles, and various things related to hiding * contents, redacting notifications, and the lockscreen. @@ -81,8 +79,6 @@ public class NotificationLockscreenUserManagerImpl implements NotificationLockscreenUserManager, StateListener { private static final String TAG = "LockscreenUserManager"; - private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false; - private final DeviceProvisionedController mDeviceProvisionedController; private final KeyguardStateController mKeyguardStateController; private final SecureSettings mSecureSettings; @@ -103,9 +99,7 @@ public class NotificationLockscreenUserManagerImpl implements private final BroadcastDispatcher mBroadcastDispatcher; private final NotificationClickNotifier mClickNotifier; private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy; - private final FeatureFlags mFeatureFlags; private boolean mShowLockscreenNotifications; - private boolean mAllowLockscreenRemoteInput; private LockPatternUtils mLockPatternUtils; protected KeyguardManager mKeyguardManager; private int mState = StatusBarState.SHADE; @@ -181,22 +175,7 @@ public class NotificationLockscreenUserManagerImpl implements protected final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { @Override - public void onUserChanged(int newUser, @NonNull Context userContext) { - if (!mFeatureFlags.isEnabled( - Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE)) { - handleUserChange(newUser); - } - } - - @Override public void onUserChanging(int newUser, @NonNull Context userContext) { - if (mFeatureFlags.isEnabled( - Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE)) { - handleUserChange(newUser); - } - } - - private void handleUserChange(int newUser) { mCurrentUserId = newUser; updateCurrentProfilesCache(); @@ -239,8 +218,7 @@ public class NotificationLockscreenUserManagerImpl implements KeyguardStateController keyguardStateController, SecureSettings secureSettings, DumpManager dumpManager, - LockPatternUtils lockPatternUtils, - FeatureFlags featureFlags) { + LockPatternUtils lockPatternUtils) { mContext = context; mMainHandler = mainHandler; mDevicePolicyManager = devicePolicyManager; @@ -258,7 +236,6 @@ public class NotificationLockscreenUserManagerImpl implements mDeviceProvisionedController = deviceProvisionedController; mSecureSettings = secureSettings; mKeyguardStateController = keyguardStateController; - mFeatureFlags = featureFlags; dumpManager.registerDumpable(this); } @@ -305,14 +282,6 @@ public class NotificationLockscreenUserManagerImpl implements Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, mSettingsObserver); - if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) { - mContext.getContentResolver().registerContentObserver( - mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT), - false, - mSettingsObserver, - UserHandle.USER_ALL); - } - mBroadcastDispatcher.registerReceiver(mAllUsersReceiver, new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), null /* handler */, UserHandle.ALL); @@ -343,10 +312,6 @@ public class NotificationLockscreenUserManagerImpl implements return mShowLockscreenNotifications; } - public boolean shouldAllowLockscreenRemoteInput() { - return mAllowLockscreenRemoteInput; - } - public boolean isCurrentProfile(int userId) { synchronized (mLock) { return userId == UserHandle.USER_ALL || mCurrentProfiles.get(userId) != null; @@ -357,10 +322,6 @@ public class NotificationLockscreenUserManagerImpl implements mShowLockscreenNotifications = show; } - private void setLockscreenAllowRemoteInput(boolean allowLockscreenRemoteInput) { - mAllowLockscreenRemoteInput = allowLockscreenRemoteInput; - } - protected void updateLockscreenNotificationSetting() { final boolean show = mSecureSettings.getIntForUser( Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, @@ -372,19 +333,6 @@ public class NotificationLockscreenUserManagerImpl implements & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; setShowLockscreenNotifications(show && allowedByDpm); - - if (ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT) { - final boolean remoteInput = mSecureSettings.getIntForUser( - Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, - 0, - mCurrentUserId) != 0; - final boolean remoteInputDpm = - (dpmFlags & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0; - - setLockscreenAllowRemoteInput(remoteInput && remoteInputDpm); - } else { - setLockscreenAllowRemoteInput(false); - } } /** @@ -472,7 +420,6 @@ public class NotificationLockscreenUserManagerImpl implements mUsersAllowingNotifications.append(userHandle, allowed); return allowed; } - return mUsersAllowingNotifications.get(userHandle); } @@ -639,37 +586,6 @@ public class NotificationLockscreenUserManagerImpl implements } } -// public void updatePublicMode() { -// //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns -// // false when it should be true. Therefore, if we are not on the SHADE, don't even bother -// // asking if the keyguard is showing. We still need to check it though because showing the -// // camera on the keyguard has a state of SHADE but the keyguard is still showing. -// final boolean showingKeyguard = mState != StatusBarState.SHADE -// || mKeyguardStateController.isShowing(); -// final boolean devicePublic = showingKeyguard && isSecure(getCurrentUserId()); -// -// -// // Look for public mode users. Users are considered public in either case of: -// // - device keyguard is shown in secure mode; -// // - profile is locked with a work challenge. -// SparseArray<UserInfo> currentProfiles = getCurrentProfiles(); -// for (int i = currentProfiles.size() - 1; i >= 0; i--) { -// final int userId = currentProfiles.valueAt(i).id; -// boolean isProfilePublic = devicePublic; -// if (!devicePublic && userId != getCurrentUserId()) { -// // We can't rely on KeyguardManager#isDeviceLocked() for unified profile challenge -// // due to a race condition where this code could be called before -// // TrustManagerService updates its internal records, resulting in an incorrect -// // state being cached in mLockscreenPublicMode. (b/35951989) -// if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) -// && isSecure(userId)) { -// isProfilePublic = mKeyguardManager.isDeviceLocked(userId); -// } -// } -// setLockscreenPublicMode(isProfilePublic, userId); -// } -// } - @Override public void dump(PrintWriter pw, String[] args) { pw.println("NotificationLockscreenUserManager state:"); @@ -677,8 +593,6 @@ public class NotificationLockscreenUserManagerImpl implements pw.println(mCurrentUserId); pw.print(" mShowLockscreenNotifications="); pw.println(mShowLockscreenNotifications); - pw.print(" mAllowLockscreenRemoteInput="); - pw.println(mAllowLockscreenRemoteInput); pw.print(" mCurrentProfiles="); synchronized (mLock) { for (int i = mCurrentProfiles.size() - 1; i >= 0; i--) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index befc64d07426..d4b6dfb9b625 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -482,9 +482,6 @@ public class NotificationRemoteInputManager implements Dumpable { private boolean showBouncerForRemoteInput(View view, PendingIntent pendingIntent, ExpandableNotificationRow row) { - if (mLockscreenUserManager.shouldAllowLockscreenRemoteInput()) { - return false; - } final int userId = pendingIntent.getCreatorUserHandle().getIdentifier(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 8baab25e5c59..f616b91c4712 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -244,12 +244,16 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi } final float scaledImageWidth = drawableWidth * scaleToFitIconView; final float scaledImageHeight = drawableHeight * scaleToFitIconView; - // if the scaled image size <= mOriginalStatusBarIconSize, we don't need to enlarge it scaleToOriginalDrawingSize = Math.min( (float) mOriginalStatusBarIconSize / scaledImageWidth, (float) mOriginalStatusBarIconSize / scaledImageHeight); if (scaleToOriginalDrawingSize > 1.0f) { - scaleToOriginalDrawingSize = 1.0f; + // per b/296026932, if the scaled image size <= mOriginalStatusBarIconSize, we need + // to scale up the scaled image to fit in mOriginalStatusBarIconSize. But if both + // the raw drawable intrinsic width/height are less than mOriginalStatusBarIconSize, + // then we just scale up the scaled image back to the raw drawable size. + scaleToOriginalDrawingSize = Math.min( + scaleToOriginalDrawingSize, 1f / scaleToFitIconView); } } iconScale = scaleToOriginalDrawingSize; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index e632214bcb2b..37a4ef168423 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -104,8 +104,9 @@ public class StatusBarStateControllerImpl implements // Record the HISTORY_SIZE most recent states private int mHistoryIndex = 0; private HistoricalState[] mHistoricalRecords = new HistoricalState[HISTORY_SIZE]; - // This is used by InteractionJankMonitor to get callback from HWUI. + // These views are used by InteractionJankMonitor to get callback from HWUI. private View mView; + private KeyguardClockSwitch mClockSwitchView; /** * If any of the system bars is hidden. @@ -334,6 +335,7 @@ public class StatusBarStateControllerImpl implements if ((mView == null || !mView.isAttachedToWindow()) && (view != null && view.isAttachedToWindow())) { mView = view; + mClockSwitchView = view.findViewById(R.id.keyguard_clock_container); } mDozeAmountTarget = dozeAmount; if (animated) { @@ -416,21 +418,12 @@ public class StatusBarStateControllerImpl implements /** Returns the id of the currently rendering clock */ public String getClockId() { - if (mView == null) { - return KeyguardClockSwitch.MISSING_CLOCK_ID; - } - - View clockSwitch = mView.findViewById(R.id.keyguard_clock_container); - if (clockSwitch == null) { + if (mClockSwitchView == null) { Log.e(TAG, "Clock container was missing"); return KeyguardClockSwitch.MISSING_CLOCK_ID; } - if (!(clockSwitch instanceof KeyguardClockSwitch)) { - Log.e(TAG, "Clock container was incorrect type: " + clockSwitch); - return KeyguardClockSwitch.MISSING_CLOCK_ID; - } - return ((KeyguardClockSwitch) clockSwitch).getClockId(); + return mClockSwitchView.getClockId(); } private void beginInteractionJankMonitor() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING index 10e7573a757e..718c1c0f683b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING +++ b/packages/SystemUI/src/com/android/systemui/statusbar/TEST_MAPPING @@ -4,9 +4,6 @@ "name": "CtsNotificationTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { @@ -14,9 +11,6 @@ }, { "exclude-annotation": "androidx.test.filters.LargeTest" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" } ] } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java index d089252759c7..64dfc6c9eb3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java @@ -167,11 +167,7 @@ public class VibratorHelper { * @see Vibrator#getPrimitiveDurations(int...) */ public int[] getPrimitiveDurations(int... primitiveIds) { - if (!hasVibrator()) { - return new int[]{0}; - } else { - return mVibrator.getPrimitiveDurations(primitiveIds); - } + return mVibrator.getPrimitiveDurations(primitiveIds); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index d1464ede2bd5..249c83166c51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -17,13 +17,16 @@ package com.android.systemui.statusbar.dagger import com.android.systemui.CoreStartable +import com.android.systemui.statusbar.core.StatusBarInitializer import com.android.systemui.statusbar.data.repository.StatusBarModeRepository import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl +import com.android.systemui.statusbar.phone.LightBarController import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap +import dagger.multibindings.IntoSet /** * A module for **only** classes related to the status bar **UI element**. This module specifically @@ -49,4 +52,15 @@ abstract class StatusBarModule { @IntoMap @ClassKey(OngoingCallController::class) abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable + + @Binds + @IntoMap + @ClassKey(LightBarController::class) + abstract fun bindLightBarController(impl: LightBarController): CoreStartable + + @Binds + @IntoSet + abstract fun statusBarInitializedListener( + statusBarModeRepository: StatusBarModeRepository, + ): StatusBarInitializer.OnStatusBarViewInitializedListener } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarAppearance.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarAppearance.kt new file mode 100644 index 000000000000..0cd31d06f71a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarAppearance.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.model + +import com.android.internal.view.AppearanceRegion +import com.android.systemui.statusbar.phone.BoundsPair + +/** Keeps track of various parameters coordinating the appearance of the status bar. */ +data class StatusBarAppearance( + /** The current mode of the status bar. */ + val mode: StatusBarMode, + /** The current bounds of the status bar. */ + val bounds: BoundsPair, + /** + * A list of appearance regions for the appearance of the status bar background. Used to + * determine the correct coloring of status bar icons to ensure contrast. See + * [com.android.systemui.statusbar.phone.LightBarController]. + */ + val appearanceRegions: List<AppearanceRegion>, + /** + * The navigation bar color as set by + * [com.android.systemui.statusbar.CommandQueue.onSystemBarAttributesChanged]. + * + * TODO(b/277764509): This likely belongs in a "NavigationBarAppearance"-type class, not a + * status bar class. + */ + val navbarColorManagedByIme: Boolean, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt new file mode 100644 index 000000000000..747efe3fb2c9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.data.model + +import com.android.systemui.statusbar.phone.BarTransitions +import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT +import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT +import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE +import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT +import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT +import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode + +/** + * The possible status bar modes. + * + * See the associated [BarTransitions] mode documentation for information about each of the modes + * and how they're used. + */ +enum class StatusBarMode { + /** Use a semi-transparent (aka translucent) background for the status bar. */ + SEMI_TRANSPARENT, + /** + * A mode where notification icons in the status bar are hidden and replaced by a dot (this mode + * can be requested by apps). See + * [com.android.systemui.statusbar.phone.LightsOutNotifController]. + */ + LIGHTS_OUT, + /** Similar to [LIGHTS_OUT], but also with a transparent background for the status bar. */ + LIGHTS_OUT_TRANSPARENT, + /** Use an opaque background for the status bar. */ + OPAQUE, + /** Use a transparent background for the status bar. */ + TRANSPARENT; + + /** Converts a [StatusBarMode] to its [BarTransitions] integer. */ + @TransitionMode + fun toTransitionModeInt(): Int { + return when (this) { + SEMI_TRANSPARENT -> MODE_SEMI_TRANSPARENT + LIGHTS_OUT -> MODE_LIGHTS_OUT + LIGHTS_OUT_TRANSPARENT -> MODE_LIGHTS_OUT_TRANSPARENT + OPAQUE -> MODE_OPAQUE + TRANSPARENT -> MODE_TRANSPARENT + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt index 9d730715c56d..2b059944798f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt @@ -16,8 +16,13 @@ package com.android.systemui.statusbar.data.repository +import android.graphics.Rect import android.view.WindowInsets import android.view.WindowInsetsController +import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS +import android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS +import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS +import android.view.WindowInsetsController.Appearance import com.android.internal.statusbar.LetterboxDetails import com.android.internal.view.AppearanceRegion import com.android.systemui.CoreStartable @@ -25,12 +30,22 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.DisplayId import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.core.StatusBarInitializer +import com.android.systemui.statusbar.data.model.StatusBarAppearance +import com.android.systemui.statusbar.data.model.StatusBarMode +import com.android.systemui.statusbar.phone.BoundsPair +import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator +import com.android.systemui.statusbar.phone.StatusBarBoundsProvider +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent +import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository +import java.io.PrintWriter import javax.inject.Inject 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.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -41,7 +56,7 @@ import kotlinx.coroutines.flow.stateIn * Note: These status bar modes are status bar *window* states that are sent to us from * WindowManager, not determined internally. */ -interface StatusBarModeRepository { +interface StatusBarModeRepository : StatusBarInitializer.OnStatusBarViewInitializedListener { /** * True if the status bar window is showing transiently and will disappear soon, and false * otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR @@ -62,6 +77,16 @@ interface StatusBarModeRepository { val isInFullscreenMode: StateFlow<Boolean> /** + * The current status bar appearance parameters. + * + * Null at system startup, but non-null once the first system callback has been received. + */ + val statusBarAppearance: StateFlow<StatusBarAppearance?> + + /** The current mode of the status bar. */ + val statusBarMode: StateFlow<StatusBarMode> + + /** * Requests for the status bar to be shown transiently. * * TODO(b/277764509): Don't allow [CentralSurfaces] to set the transient mode; have it @@ -85,6 +110,8 @@ constructor( @Application scope: CoroutineScope, @DisplayId thisDisplayId: Int, private val commandQueue: CommandQueue, + private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator, + ongoingCallRepository: OngoingCallRepository, ) : StatusBarModeRepository, CoreStartable { private val commandQueueCallback = @@ -114,7 +141,7 @@ constructor( override fun onSystemBarAttributesChanged( displayId: Int, - @WindowInsetsController.Appearance appearance: Int, + @Appearance appearance: Int, appearanceRegions: Array<AppearanceRegion>, navbarColorManagedByIme: Boolean, @WindowInsetsController.Behavior behavior: Int, @@ -125,7 +152,11 @@ constructor( if (displayId != thisDisplayId) return _originalStatusBarAttributes.value = StatusBarAttributes( + appearance, + appearanceRegions.toList(), + navbarColorManagedByIme, requestedVisibleTypes, + letterboxDetails.toList(), ) } } @@ -139,6 +170,19 @@ constructor( private val _originalStatusBarAttributes = MutableStateFlow<StatusBarAttributes?>(null) + private val _statusBarBounds = MutableStateFlow(BoundsPair(Rect(), Rect())) + + override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) { + val statusBarBoundsProvider = component.boundsProvider + val listener = + object : StatusBarBoundsProvider.BoundsChangeListener { + override fun onStatusBarBoundsChanged(bounds: BoundsPair) { + _statusBarBounds.value = bounds + } + } + statusBarBoundsProvider.addChangeListener(listener) + } + override val isInFullscreenMode: StateFlow<Boolean> = _originalStatusBarAttributes .map { params -> @@ -148,6 +192,89 @@ constructor( } .stateIn(scope, SharingStarted.Eagerly, false) + /** Modifies the raw [StatusBarAttributes] if letterboxing is needed. */ + private val modifiedStatusBarAttributes: StateFlow<ModifiedStatusBarAttributes?> = + combine( + _originalStatusBarAttributes, + _statusBarBounds, + ) { originalAttributes, statusBarBounds -> + if (originalAttributes == null) { + null + } else { + val (newAppearance, newAppearanceRegions) = + modifyAppearanceIfNeeded( + originalAttributes.appearance, + originalAttributes.appearanceRegions, + originalAttributes.letterboxDetails, + statusBarBounds, + ) + ModifiedStatusBarAttributes( + newAppearance, + newAppearanceRegions, + originalAttributes.navbarColorManagedByIme, + statusBarBounds, + ) + } + } + .stateIn(scope, SharingStarted.Eagerly, initialValue = null) + + override val statusBarAppearance: StateFlow<StatusBarAppearance?> = + combine( + modifiedStatusBarAttributes, + isTransientShown, + isInFullscreenMode, + ongoingCallRepository.hasOngoingCall, + ) { modifiedAttributes, isTransientShown, isInFullscreenMode, hasOngoingCall -> + if (modifiedAttributes == null) { + null + } else { + val statusBarMode = + toBarMode( + modifiedAttributes.appearance, + isTransientShown, + isInFullscreenMode, + hasOngoingCall, + ) + StatusBarAppearance( + statusBarMode, + modifiedAttributes.statusBarBounds, + modifiedAttributes.appearanceRegions, + modifiedAttributes.navbarColorManagedByIme, + ) + } + } + .stateIn(scope, SharingStarted.Eagerly, initialValue = null) + + override val statusBarMode: StateFlow<StatusBarMode> = + statusBarAppearance + .map { it?.mode ?: StatusBarMode.TRANSPARENT } + .stateIn(scope, SharingStarted.Eagerly, initialValue = StatusBarMode.TRANSPARENT) + + private fun toBarMode( + appearance: Int, + isTransientShown: Boolean, + isInFullscreenMode: Boolean, + hasOngoingCall: Boolean, + ): StatusBarMode { + return when { + hasOngoingCall && isInFullscreenMode -> StatusBarMode.SEMI_TRANSPARENT + isTransientShown -> StatusBarMode.SEMI_TRANSPARENT + else -> appearance.toBarMode() + } + } + + @Appearance + private fun Int.toBarMode(): StatusBarMode { + val lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS or APPEARANCE_OPAQUE_STATUS_BARS + return when { + this and lightsOutOpaque == lightsOutOpaque -> StatusBarMode.LIGHTS_OUT + this and APPEARANCE_LOW_PROFILE_BARS != 0 -> StatusBarMode.LIGHTS_OUT_TRANSPARENT + this and APPEARANCE_OPAQUE_STATUS_BARS != 0 -> StatusBarMode.OPAQUE + this and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS != 0 -> StatusBarMode.SEMI_TRANSPARENT + else -> StatusBarMode.TRANSPARENT + } + } + override fun showTransient() { _isTransientShown.value = true } @@ -156,11 +283,54 @@ constructor( _isTransientShown.value = false } + private fun modifyAppearanceIfNeeded( + appearance: Int, + appearanceRegions: List<AppearanceRegion>, + letterboxDetails: List<LetterboxDetails>, + statusBarBounds: BoundsPair, + ): Pair<Int, List<AppearanceRegion>> = + if (shouldUseLetterboxAppearance(letterboxDetails)) { + val letterboxAppearance = + letterboxAppearanceCalculator.getLetterboxAppearance( + appearance, + appearanceRegions, + letterboxDetails, + statusBarBounds, + ) + Pair(letterboxAppearance.appearance, letterboxAppearance.appearanceRegions) + } else { + Pair(appearance, appearanceRegions) + } + + private fun shouldUseLetterboxAppearance(letterboxDetails: List<LetterboxDetails>) = + letterboxDetails.isNotEmpty() + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("originalStatusBarAttributes: ${_originalStatusBarAttributes.value}") + pw.println("modifiedStatusBarAttributes: ${modifiedStatusBarAttributes.value}") + pw.println("statusBarMode: ${statusBarMode.value}") + } + /** * Internal class keeping track of the raw status bar attributes received from the callback. * Should never be exposed. */ private data class StatusBarAttributes( + @Appearance val appearance: Int, + val appearanceRegions: List<AppearanceRegion>, + val navbarColorManagedByIme: Boolean, @WindowInsets.Type.InsetsType val requestedVisibleTypes: Int, + val letterboxDetails: List<LetterboxDetails>, + ) + + /** + * Internal class keeping track of how [StatusBarAttributes] were transformed into new + * attributes based on letterboxing and other factors. Should never be exposed. + */ + private data class ModifiedStatusBarAttributes( + @Appearance val appearance: Int, + val appearanceRegions: List<AppearanceRegion>, + val navbarColorManagedByIme: Boolean, + val statusBarBounds: BoundsPair, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java index eb31bd3a95fe..2d5afd56da72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java @@ -50,7 +50,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl * Set of summary keys whose groups are expanded. * NOTE: This should not be modified without notifying listeners, so prefer using * {@code setGroupExpanded} when making changes. - */ + */ private final Set<NotificationEntry> mExpandedGroups = new HashSet<>(); private final FeatureFlags mFeatureFlags; @@ -104,7 +104,18 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl @Override public void setGroupExpanded(NotificationEntry entry, boolean expanded) { - final NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry); + NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry); + if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) + && entry.getParent() == null) { + if (expanded) { + throw new IllegalArgumentException("Cannot expand group that is not attached"); + } else { + // The entry is no longer attached, but we still want to make sure we don't have + // a stale expansion state. + groupSummary = entry; + } + } + boolean changed; if (expanded) { changed = mExpandedGroups.add(groupSummary); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java index c33e8ab8cdd4..3158782e6fea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManager.java @@ -25,18 +25,18 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.List; /** - * Helper that determines the group states (parent, summary, children) of a notification. + * Helper that determines the group states (parent, summary, children) of a notification. This + * generally assumes that the notification is attached (aka its parent is not null). */ public interface GroupMembershipManager { /** - * @return whether a given notification is a top level entry or is the summary in a group which - * has children + * @return whether a given notification is the summary in a group which has children */ boolean isGroupSummary(@NonNull NotificationEntry entry); /** * Get the summary of a specified status bar notification. For an isolated notification this - * returns itself. + * returns null, but if called directly on a summary it returns itself. */ @Nullable NotificationEntry getGroupSummary(@NonNull NotificationEntry entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java index a6b855f9b838..cb7935369564 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java @@ -22,7 +22,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; @@ -38,47 +38,50 @@ import javax.inject.Inject; */ @SysUISingleton public class GroupMembershipManagerImpl implements GroupMembershipManager { - FeatureFlags mFeatureFlags; + FeatureFlagsClassic mFeatureFlags; @Inject - public GroupMembershipManagerImpl(FeatureFlags featureFlags) { + public GroupMembershipManagerImpl(FeatureFlagsClassic featureFlags) { mFeatureFlags = featureFlags; } @Override public boolean isGroupSummary(@NonNull NotificationEntry entry) { - return getGroupSummary(entry) == entry; + if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { + if (entry.getParent() == null) { + // The entry is not attached, so it doesn't count. + return false; + } + // If entry is a summary, its parent is a GroupEntry with summary = entry. + return entry.getParent().getSummary() == entry; + } else { + return getGroupSummary(entry) == entry; + } } @Nullable @Override public NotificationEntry getGroupSummary(@NonNull NotificationEntry entry) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - if (!isChildInGroup(entry)) { - return entry.getRepresentativeEntry(); - } - } else { - if (isEntryTopLevel(entry) || entry.getParent() == null) { - return null; - } + if (isTopLevelEntry(entry) || entry.getParent() == null) { + return null; } - - return entry.getParent().getRepresentativeEntry(); + return entry.getParent().getSummary(); } @Override public boolean isChildInGroup(@NonNull NotificationEntry entry) { if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - return !isEntryTopLevel(entry) && entry.getParent() != null; + // An entry is a child if it's not a summary or top level entry, but it is attached. + return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null; } else { - return !isEntryTopLevel(entry); + return !isTopLevelEntry(entry); } } @Override public boolean isOnlyChildInGroup(@NonNull NotificationEntry entry) { if (entry.getParent() == null) { - return false; + return false; // The entry is not attached. } return !isGroupSummary(entry) && entry.getParent().getChildren().size() == 1; @@ -103,7 +106,7 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager { return null; } - private boolean isEntryTopLevel(@NonNull NotificationEntry entry) { + private boolean isTopLevelEntry(@NonNull NotificationEntry entry) { return entry.getParent() == ROOT_ENTRY; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java index 88994b9eec04..6ec9dbe003a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java @@ -100,7 +100,11 @@ public interface NotificationInterruptStateProvider { /** * The notification is coming from a suspended packages, so FSI is suppressed. */ - NO_FSI_SUSPENDED(false); + NO_FSI_SUSPENDED(false), + /** + * The device is not provisioned, launch FSI. + */ + FSI_NOT_PROVISIONED(true); public final boolean shouldLaunch; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 0c43da066fdb..3819843aa7b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -75,6 +76,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; private final UiEventLogger mUiEventLogger; private final UserTracker mUserTracker; + private final DeviceProvisionedController mDeviceProvisionedController; @VisibleForTesting protected boolean mUseHeadsUp = false; @@ -121,7 +123,8 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter NotifPipelineFlags flags, KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider, UiEventLogger uiEventLogger, - UserTracker userTracker) { + UserTracker userTracker, + DeviceProvisionedController deviceProvisionedController) { mContentResolver = contentResolver; mPowerManager = powerManager; mBatteryController = batteryController; @@ -163,6 +166,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter headsUpObserver); } headsUpObserver.onChange(true); // set up + mDeviceProvisionedController = deviceProvisionedController; } @Override @@ -334,6 +338,12 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } } + // The device is not provisioned, launch FSI. + if (!mDeviceProvisionedController.isDeviceProvisioned()) { + return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_NOT_PROVISIONED, + suppressedByDND); + } + // Detect the case determined by b/231322873 to launch FSI while device is in use, // as blocked by the correct implementation, and report the event. return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/CallLayoutSetDataAsyncFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/CallLayoutSetDataAsyncFactory.kt new file mode 100644 index 000000000000..4deebdb8de7d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/CallLayoutSetDataAsyncFactory.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import com.android.internal.widget.CallLayout +import javax.inject.Inject + +class CallLayoutSetDataAsyncFactory @Inject constructor() : NotifRemoteViewsFactory { + override fun instantiate( + row: ExpandableNotificationRow, + @NotificationRowContentBinder.InflationFlag layoutType: Int, + parent: View?, + name: String, + context: Context, + attrs: AttributeSet + ): View? = + if (name == CallLayout::class.java.name) + CallLayout(context, attrs).apply { setSetDataAsyncEnabled(true) } + else null +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt index 4114eb2a7896..a8d59d83d1e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt @@ -71,15 +71,15 @@ class ChannelEditorDialogController @Inject constructor( private var appUid: Int? = null private var packageName: String? = null private var appName: String? = null + private var channel: NotificationChannel? = null private var onSettingsClickListener: NotificationInfo.OnSettingsClickListener? = null // Caller should set this if they care about when we dismiss var onFinishListener: OnChannelEditorDialogFinishedListener? = null - @VisibleForTesting - internal val paddedChannels = mutableListOf<NotificationChannel>() // Channels handed to us from NotificationInfo - private val providedChannels = mutableListOf<NotificationChannel>() + @VisibleForTesting + internal val channelList = mutableListOf<NotificationChannel>() // Map from NotificationChannel to importance private val edits = mutableMapOf<NotificationChannel, Int>() @@ -93,14 +93,14 @@ class ChannelEditorDialogController @Inject constructor( private val channelGroupList = mutableListOf<NotificationChannelGroup>() /** - * Give the controller all of the information it needs to present the dialog + * Give the controller all the information it needs to present the dialog * for a given app. Does a bunch of querying of NoMan, but won't present anything yet */ fun prepareDialogForApp( appName: String, packageName: String, uid: Int, - channels: Set<NotificationChannel>, + channel: NotificationChannel, appIcon: Drawable, onSettingsClickListener: NotificationInfo.OnSettingsClickListener? ) { @@ -110,6 +110,7 @@ class ChannelEditorDialogController @Inject constructor( this.appIcon = appIcon this.appNotificationsEnabled = checkAreAppNotificationsOn() this.onSettingsClickListener = onSettingsClickListener + this.channel = channel // These will always start out the same appNotificationsCurrentlyEnabled = appNotificationsEnabled @@ -117,9 +118,7 @@ class ChannelEditorDialogController @Inject constructor( channelGroupList.clear() channelGroupList.addAll(fetchNotificationChannelGroups()) buildGroupNameLookup() - providedChannels.clear() - providedChannels.addAll(channels) - padToFourChannels(channels) + populateChannelList() initDialog() prepared = true @@ -133,36 +132,26 @@ class ChannelEditorDialogController @Inject constructor( } } - private fun padToFourChannels(channels: Set<NotificationChannel>) { - paddedChannels.clear() - // First, add all of the given channels - paddedChannels.addAll(channels.asSequence().take(4)) - - // Then pad to 4 if we haven't been given that many - paddedChannels.addAll(getDisplayableChannels(channelGroupList.asSequence()) - .filterNot { paddedChannels.contains(it) } - .distinct() - .take(4 - paddedChannels.size)) - - // If we only got one channel and it has the default miscellaneous tag, then we actually - // are looking at an app with a targetSdk <= O, and it doesn't make much sense to show the - // channel - if (paddedChannels.size == 1 && DEFAULT_CHANNEL_ID == paddedChannels[0].id) { - paddedChannels.clear() + private fun populateChannelList() { + channelList.clear() + if (DEFAULT_CHANNEL_ID != channel!!.id) { + channelList.add(0, channel!!) + channelList.addAll(getDisplayableChannels(channelGroupList.asSequence()) + .filterNot { it.id == channel!!.id } + .distinct()) } } private fun getDisplayableChannels( groupList: Sequence<NotificationChannelGroup> ): Sequence<NotificationChannel> { - - // TODO (b/194833441): remove channel level settings when we move to a permission val channels = groupList .flatMap { group -> - group.channels.asSequence().filterNot { channel -> - channel.importance == IMPORTANCE_NONE || + group.channels.asSequence() + .sortedWith(compareBy {group.name?.toString() ?: group.id}) + .filterNot { channel -> channel.isImportanceLockedByCriticalDeviceFunction - } + } } // TODO: sort these by avgSentWeekly, but for now let's just do alphabetical (why not) @@ -196,8 +185,7 @@ class ChannelEditorDialogController @Inject constructor( appNotificationsCurrentlyEnabled = null edits.clear() - paddedChannels.clear() - providedChannels.clear() + channelList.clear() groupNameLookup.clear() } @@ -231,7 +219,7 @@ class ChannelEditorDialogController @Inject constructor( @Suppress("unchecked_cast") private fun fetchNotificationChannelGroups(): List<NotificationChannelGroup> { return try { - noMan.getNotificationChannelGroupsForPackage(packageName!!, appUid!!, false) + noMan.getRecentBlockedNotificationChannelGroupsForPackage(packageName!!, appUid!!) .list as? List<NotificationChannelGroup> ?: listOf() } catch (e: Exception) { Log.e(TAG, "Error fetching channel groups", e) @@ -280,7 +268,6 @@ class ChannelEditorDialogController @Inject constructor( @VisibleForTesting fun launchSettings(sender: View) { - val channel = if (providedChannels.size == 1) providedChannels[0] else null onSettingsClickListener?.onClick(sender, channel, appUid!!) } @@ -301,14 +288,12 @@ class ChannelEditorDialogController @Inject constructor( controller = this@ChannelEditorDialogController appIcon = this@ChannelEditorDialogController.appIcon appName = this@ChannelEditorDialogController.appName - channels = paddedChannels + channels = channelList } setOnShowListener { - // play a highlight animation for the given channels - for (channel in providedChannels) { - listView?.highlightChannel(channel) - } + // play a highlight animation for the given channel + listView?.highlightChannel(channel!!) } findViewById<TextView>(R.id.done_button)?.setOnClickListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt index 2cfd075a17a9..10e67a40ebc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt @@ -20,6 +20,7 @@ import android.animation.ArgbEvaluator import android.animation.ValueAnimator import android.app.NotificationChannel import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.app.NotificationManager.IMPORTANCE_LOW import android.app.NotificationManager.IMPORTANCE_NONE import android.app.NotificationManager.IMPORTANCE_UNSPECIFIED import android.content.Context @@ -55,12 +56,14 @@ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, a // The first row is for the entire app private lateinit var appControlRow: AppControlView + private lateinit var channelListView: LinearLayout private val channelRows = mutableListOf<ChannelRow>() override fun onFinishInflate() { super.onFinishInflate() appControlRow = requireViewById(R.id.app_control) + channelListView = requireViewById(R.id.scrollView) } /** @@ -102,7 +105,7 @@ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, a // Remove any rows for (row in channelRows) { - removeView(row) + channelListView.removeView(row) } channelRows.clear() @@ -122,7 +125,7 @@ class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, a row.channel = channel channelRows.add(row) - addView(row) + channelListView.addView(row) } private fun updateAppControlRow(enabled: Boolean) { @@ -179,7 +182,9 @@ class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) { switch = requireViewById(R.id.toggle) switch.setOnCheckedChangeListener { _, b -> channel?.let { - controller.proposeEditForChannel(it, if (b) it.importance else IMPORTANCE_NONE) + controller.proposeEditForChannel(it, + if (b) it.originalImportance.coerceAtLeast(IMPORTANCE_LOW) + else IMPORTANCE_NONE) } } setOnClickListener { switch.toggle() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index fb8024c88191..d18f9919604d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2658,42 +2658,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Returns the number of channels covered by the notification row (including its children if - * it's a summary notification). - */ - public int getNumUniqueChannels() { - return getUniqueChannels().size(); - } - - /** - * Returns the channels covered by the notification row (including its children if - * it's a summary notification). - */ - public ArraySet<NotificationChannel> getUniqueChannels() { - ArraySet<NotificationChannel> channels = new ArraySet<>(); - - channels.add(mEntry.getChannel()); - - // If this is a summary, then add in the children notification channels for the - // same user and pkg. - if (mIsSummaryWithChildren) { - final List<ExpandableNotificationRow> childrenRows = getAttachedChildren(); - final int numChildren = childrenRows.size(); - for (int i = 0; i < numChildren; i++) { - final ExpandableNotificationRow childRow = childrenRows.get(i); - final NotificationChannel childChannel = childRow.getEntry().getChannel(); - final StatusBarNotification childSbn = childRow.getEntry().getSbn(); - if (childSbn.getUser().equals(mEntry.getSbn().getUser()) - && childSbn.getPackageName().equals(mEntry.getSbn().getPackageName())) { - channels.add(childChannel); - } - } - } - - return channels; - } - - /** * If this is a group, update the appearance of the children. */ public void updateChildrenAppearance() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 1dd3739d0e84..6d6566058aed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -403,7 +403,6 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta mChannelEditorDialogController, packageName, row.getEntry().getChannel(), - row.getUniqueChannels(), row.getEntry(), onSettingsClick, onAppSettingsClick, @@ -449,7 +448,6 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta mChannelEditorDialogController, packageName, row.getEntry().getChannel(), - row.getUniqueChannels(), row.getEntry(), onSettingsClick, mDeviceProvisionedController.isDeviceProvisioned(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index d8f31d4fb8a2..d8ebd4209c7d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -104,8 +104,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private String mAppName; private int mAppUid; private String mDelegatePkg; - private int mNumUniqueChannelsInRow; - private Set<NotificationChannel> mUniqueChannelsInRow; private NotificationChannel mSingleNotificationChannel; private int mStartingChannelImportance; private boolean mWasShownHighPriority; @@ -196,7 +194,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G ChannelEditorDialogController channelEditorDialogController, String pkg, NotificationChannel notificationChannel, - Set<NotificationChannel> uniqueChannelsInRow, NotificationEntry entry, OnSettingsClickListener onSettingsClick, OnAppSettingsClickListener onAppSettingsClick, @@ -213,8 +210,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mChannelEditorDialogController = channelEditorDialogController; mAssistantFeedbackController = assistantFeedbackController; mPackageName = pkg; - mUniqueChannelsInRow = uniqueChannelsInRow; - mNumUniqueChannelsInRow = uniqueChannelsInRow.size(); mEntry = entry; mSbn = entry.getSbn(); mPm = pm; @@ -236,15 +231,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage( pkg, mAppUid, false /* includeDeleted */); - if (mNumUniqueChannelsInRow == 0) { - throw new IllegalArgumentException("bindNotification requires at least one channel"); - } else { - // Special behavior for the Default channel if no other channels have been defined. - mIsSingleDefaultChannel = mNumUniqueChannelsInRow == 1 - && mSingleNotificationChannel.getId().equals( - NotificationChannel.DEFAULT_CHANNEL_ID) - && numTotalChannels == 1; - } + mIsSingleDefaultChannel = mSingleNotificationChannel.getId().equals( + NotificationChannel.DEFAULT_CHANNEL_ID) && numTotalChannels == 1; mIsAutomaticChosen = getAlertingBehavior() == BEHAVIOR_AUTOMATIC; bindHeader(); @@ -271,11 +259,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G findViewById(R.id.interruptiveness_settings).setVisibility(GONE); ((TextView) findViewById(R.id.done)).setText(R.string.inline_done_button); findViewById(R.id.turn_off_notifications).setVisibility(GONE); - } else if (mNumUniqueChannelsInRow > 1) { - findViewById(R.id.non_configurable_call_text).setVisibility(GONE); - findViewById(R.id.non_configurable_text).setVisibility(GONE); - findViewById(R.id.interruptiveness_settings).setVisibility(GONE); - findViewById(R.id.non_configurable_multichannel_text).setVisibility(VISIBLE); } else { findViewById(R.id.non_configurable_call_text).setVisibility(GONE); findViewById(R.id.non_configurable_text).setVisibility(GONE); @@ -361,9 +344,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) { final int appUidF = mAppUid; return ((View view) -> { - mOnSettingsClickListener.onClick(view, - mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel, - appUidF); + mOnSettingsClickListener.onClick(view, mSingleNotificationChannel, appUidF); }); } return null; @@ -375,7 +356,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mPresentingChannelEditorDialog = true; mChannelEditorDialogController.prepareDialogForApp(mAppName, mPackageName, mAppUid, - mUniqueChannelsInRow, mPkgIcon, mOnSettingsClickListener); + mSingleNotificationChannel, mPkgIcon, mOnSettingsClickListener); mChannelEditorDialogController.setOnFinishListener(() -> { mPresentingChannelEditorDialog = false; mGutsContainer.closeControls(this, false); @@ -392,7 +373,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private void bindName() { final TextView channelName = findViewById(R.id.channel_name); - if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) { + if (mIsSingleDefaultChannel) { channelName.setVisibility(View.GONE); } else { channelName.setText(mSingleNotificationChannel.getName()); @@ -459,7 +440,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); bgHandler.post( new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid, - mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null, + mSingleNotificationChannel, mStartingChannelImportance, newImportance, mIsAutomaticChosen)); mOnUserInteractionCallback.onImportanceChanged(mEntry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java index 0239afc08ec5..3a59978d415c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -61,7 +61,8 @@ public abstract class NotificationRowModule { static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories( FeatureFlags featureFlags, PrecomputedTextViewFactory precomputedTextViewFactory, - BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory + BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory, + CallLayoutSetDataAsyncFactory callLayoutSetDataAsyncFactory ) { final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>(); if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) { @@ -70,6 +71,9 @@ public abstract class NotificationRowModule { if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) { replacementFactories.add(bigPictureLayoutInflaterFactory); } + if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) { + replacementFactories.add(callLayoutSetDataAsyncFactory); + } return replacementFactories; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java index 06c3b7951db3..53f7d4bde70f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java @@ -57,7 +57,6 @@ public class PartialConversationInfo extends LinearLayout implements private StatusBarNotification mSbn; private boolean mIsDeviceProvisioned; private boolean mIsNonBlockable; - private Set<NotificationChannel> mUniqueChannelsInRow; private Drawable mPkgIcon; private boolean mPresentingChannelEditorDialog = false; @@ -83,7 +82,6 @@ public class PartialConversationInfo extends LinearLayout implements ChannelEditorDialogController channelEditorDialogController, String pkg, NotificationChannel notificationChannel, - Set<NotificationChannel> uniqueChannelsInRow, NotificationEntry entry, NotificationInfo.OnSettingsClickListener onSettingsClick, boolean isDeviceProvisioned, @@ -100,7 +98,6 @@ public class PartialConversationInfo extends LinearLayout implements mIsDeviceProvisioned = isDeviceProvisioned; mIsNonBlockable = isNonBlockable; mChannelEditorDialogController = channelEditorDialogController; - mUniqueChannelsInRow = uniqueChannelsInRow; bindHeader(); bindActions(); @@ -149,7 +146,7 @@ public class PartialConversationInfo extends LinearLayout implements mPresentingChannelEditorDialog = true; mChannelEditorDialogController.prepareDialogForApp(mAppName, mPackageName, mAppUid, - mUniqueChannelsInRow, mPkgIcon, mOnSettingsClickListener); + mNotificationChannel, mPkgIcon, mOnSettingsClickListener); mChannelEditorDialogController.setOnFinishListener(() -> { mPresentingChannelEditorDialog = false; mGutsContainer.closeControls(this, false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java index 875a409c07f0..91b12ccf919a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java @@ -20,6 +20,9 @@ import static android.view.View.VISIBLE; import static com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.DEFAULT_HEADER_VISIBLE_AMOUNT; +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; @@ -34,8 +37,7 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import androidx.annotation.Nullable; - +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ContrastColorUtil; import com.android.internal.widget.NotificationActionListLayout; import com.android.systemui.Dependency; @@ -49,6 +51,8 @@ import com.android.systemui.statusbar.notification.TransformState; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.HybridNotificationView; +import java.util.function.Consumer; + /** * Wraps a notification view inflated from a template. */ @@ -66,9 +70,13 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp private int mContentHeight; private int mMinHeightHint; + @Nullable private NotificationActionListLayout mActions; - private ArraySet<PendingIntent> mCancelledPendingIntents = new ArraySet<>(); - private UiOffloadThread mUiOffloadThread; + // Holds list of pending intents that have been cancelled by now - we only keep hash codes + // to avoid holding full binder proxies for intents that may have been removed by now. + @NonNull + @VisibleForTesting + final ArraySet<Integer> mCancelledPendingIntents = new ArraySet<>(); private View mRemoteInputHistory; private boolean mCanHideHeader; private float mHeaderTranslation; @@ -147,6 +155,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp com.android.internal.R.dimen.notification_content_margin_top); } + @MainThread private void resolveTemplateViews(StatusBarNotification sbn) { mRightIcon = mView.findViewById(com.android.internal.R.id.right_icon); if (mRightIcon != null) { @@ -195,34 +204,57 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp return getLargeIcon(n); } + @MainThread private void updatePendingIntentCancellations() { if (mActions != null) { int numActions = mActions.getChildCount(); + final ArraySet<Integer> currentlyActivePendingIntents = new ArraySet<>(numActions); for (int i = 0; i < numActions; i++) { Button action = (Button) mActions.getChildAt(i); - performOnPendingIntentCancellation(action, () -> { - if (action.isEnabled()) { - action.setEnabled(false); - // The visual appearance doesn't look disabled enough yet, let's add the - // alpha as well. Since Alpha doesn't play nicely right now with the - // transformation, we rather blend it manually with the background color. - ColorStateList textColors = action.getTextColors(); - int[] colors = textColors.getColors(); - int[] newColors = new int[colors.length]; - float disabledAlpha = mView.getResources().getFloat( - com.android.internal.R.dimen.notification_action_disabled_alpha); - for (int j = 0; j < colors.length; j++) { - int color = colors[j]; - color = blendColorWithBackground(color, disabledAlpha); - newColors[j] = color; - } - ColorStateList newColorStateList = new ColorStateList( - textColors.getStates(), newColors); - action.setTextColor(newColorStateList); + PendingIntent pendingIntent = getPendingIntentForAction(action); + // Check if passed intent has already been cancelled in this class and immediately + // disable the action to avoid temporary race with enable/disable. + if (pendingIntent != null) { + int pendingIntentHashCode = getHashCodeForPendingIntent(pendingIntent); + currentlyActivePendingIntents.add(pendingIntentHashCode); + if (mCancelledPendingIntents.contains(pendingIntentHashCode)) { + disableActionView(action); } - }); + } + updatePendingIntentCancellationListener(action, pendingIntent); + } + + // This cleanup ensures that the size of this set doesn't grow into unreasonable sizes. + // There are scenarios where applications updated notifications with different + // PendingIntents which could cause this Set to grow to 1000+ elements. + mCancelledPendingIntents.retainAll(currentlyActivePendingIntents); + } + } + + @MainThread + private void updatePendingIntentCancellationListener(Button action, + @Nullable PendingIntent pendingIntent) { + ActionPendingIntentCancellationHandler cancellationHandler = null; + if (pendingIntent != null) { + // Attach listeners to handle intent cancellation to this view. + cancellationHandler = new ActionPendingIntentCancellationHandler(pendingIntent, action, + this::disableActionViewWithIntent); + action.addOnAttachStateChangeListener(cancellationHandler); + // Immediately fire the event if the view is already attached to register + // pending intent cancellation listener. + if (action.isAttachedToWindow()) { + cancellationHandler.onViewAttachedToWindow(action); } } + + // If the view has an old attached listener, remove it to avoid leaking intents. + ActionPendingIntentCancellationHandler previousHandler = + (ActionPendingIntentCancellationHandler) action.getTag( + R.id.pending_intent_listener_tag); + if (previousHandler != null) { + previousHandler.remove(); + } + action.setTag(R.id.pending_intent_listener_tag, cancellationHandler); } private int blendColorWithBackground(int color, float alpha) { @@ -231,42 +263,6 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp Color.red(color), Color.green(color), Color.blue(color)), resolveBackgroundColor()); } - private void performOnPendingIntentCancellation(View view, Runnable cancellationRunnable) { - PendingIntent pendingIntent = (PendingIntent) view.getTag( - com.android.internal.R.id.pending_intent_tag); - if (pendingIntent == null) { - return; - } - if (mCancelledPendingIntents.contains(pendingIntent)) { - cancellationRunnable.run(); - } else { - PendingIntent.CancelListener listener = (PendingIntent intent) -> { - mView.post(() -> { - mCancelledPendingIntents.add(pendingIntent); - cancellationRunnable.run(); - }); - }; - if (mUiOffloadThread == null) { - mUiOffloadThread = Dependency.get(UiOffloadThread.class); - } - if (view.isAttachedToWindow()) { - mUiOffloadThread.execute(() -> pendingIntent.registerCancelListener(listener)); - } - view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - mUiOffloadThread.execute(() -> pendingIntent.registerCancelListener(listener)); - } - - @Override - public void onViewDetachedFromWindow(View v) { - mUiOffloadThread.execute( - () -> pendingIntent.unregisterCancelListener(listener)); - } - }); - } - } - @Override public void onContentUpdated(ExpandableNotificationRow row) { // Reinspect the notification. Before the super call, because the super call also updates @@ -364,4 +360,141 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp } return extra + super.getExtraMeasureHeight(); } + + /** + * This finds Action view with a given intent and disables it. + * With maximum of 3 views, this is sufficiently fast to iterate on main thread every time. + */ + @MainThread + private void disableActionViewWithIntent(PendingIntent intent) { + mCancelledPendingIntents.add(getHashCodeForPendingIntent(intent)); + if (mActions != null) { + int numActions = mActions.getChildCount(); + for (int i = 0; i < numActions; i++) { + Button action = (Button) mActions.getChildAt(i); + PendingIntent pendingIntent = getPendingIntentForAction(action); + if (intent.equals(pendingIntent)) { + disableActionView(action); + } + } + } + } + + /** + * Disables Action view when, e.g., its PendingIntent is disabled. + */ + @MainThread + private void disableActionView(Button action) { + if (action.isEnabled()) { + action.setEnabled(false); + // The visual appearance doesn't look disabled enough yet, let's add the + // alpha as well. Since Alpha doesn't play nicely right now with the + // transformation, we rather blend it manually with the background color. + ColorStateList textColors = action.getTextColors(); + int[] colors = textColors.getColors(); + int[] newColors = new int[colors.length]; + float disabledAlpha = mView.getResources().getFloat( + com.android.internal.R.dimen.notification_action_disabled_alpha); + for (int j = 0; j < colors.length; j++) { + int color = colors[j]; + color = blendColorWithBackground(color, disabledAlpha); + newColors[j] = color; + } + ColorStateList newColorStateList = new ColorStateList( + textColors.getStates(), newColors); + action.setTextColor(newColorStateList); + } + } + + /** + * Returns the hashcode of underlying target of PendingIntent. We can get multiple + * Java PendingIntent wrapper objects pointing to the same cancelled PI in system_server. + * This makes sure we treat them equally. + */ + private static int getHashCodeForPendingIntent(PendingIntent pendingIntent) { + return System.identityHashCode(pendingIntent.getTarget().asBinder()); + } + + /** + * Returns PendingIntent contained in the action tag. May be null. + */ + @Nullable + private static PendingIntent getPendingIntentForAction(View action) { + return (PendingIntent) action.getTag(com.android.internal.R.id.pending_intent_tag); + } + + /** + * Registers listeners for pending intent cancellation when Action views are attached + * to window. + * It calls onCancelPendingIntentForActionView when a PendingIntent is cancelled. + */ + @VisibleForTesting + static final class ActionPendingIntentCancellationHandler + implements View.OnAttachStateChangeListener { + + @Nullable + private static UiOffloadThread sUiOffloadThread = null; + + @NonNull + private static UiOffloadThread getUiOffloadThread() { + if (sUiOffloadThread == null) { + sUiOffloadThread = Dependency.get(UiOffloadThread.class); + } + return sUiOffloadThread; + } + + private final View mView; + private final Consumer<PendingIntent> mOnCancelledCallback; + + private final PendingIntent mPendingIntent; + + ActionPendingIntentCancellationHandler(PendingIntent pendingIntent, View actionView, + Consumer<PendingIntent> onCancelled) { + this.mPendingIntent = pendingIntent; + this.mView = actionView; + this.mOnCancelledCallback = onCancelled; + } + + private final PendingIntent.CancelListener mCancelListener = + new PendingIntent.CancelListener() { + @Override + public void onCanceled(PendingIntent pendingIntent) { + mView.post(() -> { + mOnCancelledCallback.accept(pendingIntent); + // We don't need this listener anymore once the intent was cancelled. + remove(); + }); + } + }; + + @MainThread + @Override + public void onViewAttachedToWindow(View view) { + // This is safe to call multiple times with the same listener instance. + getUiOffloadThread().execute(() -> { + mPendingIntent.registerCancelListener(mCancelListener); + }); + } + + @MainThread + @Override + public void onViewDetachedFromWindow(View view) { + // This is safe to call multiple times with the same listener instance. + getUiOffloadThread().execute(() -> + mPendingIntent.unregisterCancelListener(mCancelListener)); + } + + /** + * Removes this listener from callbacks and releases the held PendingIntent. + */ + @MainThread + public void remove() { + mView.removeOnAttachStateChangeListener(this); + if (mView.getTag(R.id.pending_intent_listener_tag) == this) { + mView.setTag(R.id.pending_intent_listener_tag, null); + } + getUiOffloadThread().execute(() -> + mPendingIntent.unregisterCancelListener(mCancelListener)); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 3e1f09f1077d..90cba409a787 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -290,10 +290,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { void acquireGestureWakeLock(long time); - boolean setAppearance(int appearance); - - int getBarMode(); - void resendMessage(int msg); void resendMessage(Object msg); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 485ab3262725..22b9298b629d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -36,16 +36,11 @@ import android.util.Log; import android.util.Slog; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; -import android.view.WindowInsets.Type.InsetsType; -import android.view.WindowInsetsController.Appearance; -import android.view.WindowInsetsController.Behavior; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.statusbar.LetterboxDetails; -import com.android.internal.view.AppearanceRegion; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.res.R; import com.android.systemui.assist.AssistManager; @@ -75,6 +70,7 @@ import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import dagger.Lazy; +import java.util.Arrays; import java.util.Optional; import javax.inject.Inject; @@ -108,7 +104,6 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba private final UserTracker mUserTracker; private final boolean mVibrateOnOpening; private final VibrationEffect mCameraLaunchGestureVibrationEffect; - private final SystemBarAttributesListener mSystemBarAttributesListener; private final ActivityStarter mActivityStarter; private final Lazy<CameraLauncher> mCameraLauncherLazy; private final QuickSettingsController mQsController; @@ -148,7 +143,6 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba Optional<Vibrator> vibratorOptional, DisableFlagsLogger disableFlagsLogger, @DisplayId int displayId, - SystemBarAttributesListener systemBarAttributesListener, Lazy<CameraLauncher> cameraLauncherLazy, UserTracker userTracker, QSHost qsHost, @@ -186,7 +180,6 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation); mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect( mVibratorOptional, resources); - mSystemBarAttributesListener = systemBarAttributesListener; mActivityStarter = activityStarter; } @@ -201,6 +194,11 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba } @Override + public void setQsTiles(String[] tiles) { + mQSHost.changeTilesByUser(mQSHost.getSpecs(), Arrays.stream(tiles).toList()); + } + + @Override public void clickTile(ComponentName tile) { // Can't inject this because it changes with the QS fragment QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController(); @@ -451,29 +449,6 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mCentralSurfaces.setInteracting(StatusBarManager.WINDOW_NAVIGATION_BAR, running); } - - @Override - public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance, - AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, - @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName, - LetterboxDetails[] letterboxDetails) { - if (displayId != mDisplayId) { - return; - } - // SystemBarAttributesListener should __always__ be the top-level listener for system bar - // attributes changed. - mSystemBarAttributesListener.onSystemBarAttributesChanged( - displayId, - appearance, - appearanceRegions, - navbarColorManagedByIme, - behavior, - requestedVisibleTypes, - packageName, - letterboxDetails - ); - } - @Override public void toggleKeyboardShortcutsMenu(int deviceId) { mCentralSurfaces.resendMessage(new CentralSurfaces.KeyboardShortcutsMessage(deviceId)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt index 3cb5e1f63a82..7dc4b96ea154 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt @@ -85,8 +85,6 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun getRotation() = 0 override fun setBarStateForTest(state: Int) {} override fun acquireGestureWakeLock(time: Long) {} - override fun setAppearance(appearance: Int) = false - override fun getBarMode() = 0 override fun resendMessage(msg: Int) {} override fun resendMessage(msg: Any?) {} override fun setLastCameraLaunchSource(source: Int) {} 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 6f69ea8170c0..3e2f10dd1a0c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -21,22 +21,15 @@ import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; -import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; -import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS; -import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS; + import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; import static androidx.lifecycle.Lifecycle.State.RESUMED; + import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; import static com.android.systemui.statusbar.StatusBarState.SHADE; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import android.annotation.Nullable; import android.app.ActivityManager; @@ -90,7 +83,6 @@ import android.view.IWindowManager; import android.view.ThreadedRenderer; import android.view.View; import android.view.WindowInsets; -import android.view.WindowInsetsController.Appearance; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; @@ -121,7 +113,6 @@ import com.android.systemui.DejankUtils; import com.android.systemui.EventLogTags; import com.android.systemui.InitController; import com.android.systemui.Prefs; -import com.android.systemui.res.R; import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.assist.AssistManager; @@ -165,8 +156,9 @@ import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; -import com.android.systemui.qs.QSFragment; +import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.qs.QSPanelController; +import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.UserTracker; @@ -206,6 +198,7 @@ import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.core.StatusBarInitializer; +import com.android.systemui.statusbar.data.model.StatusBarMode; import com.android.systemui.statusbar.data.repository.StatusBarModeRepository; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -220,7 +213,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule; -import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -244,8 +236,6 @@ import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.startingsurface.SplashscreenContentDrawer; import com.android.wm.shell.startingsurface.StartingSurface; -import dagger.Lazy; - import java.io.PrintWriter; import java.io.StringWriter; import java.util.List; @@ -257,6 +247,8 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; +import dagger.Lazy; + /** * A class handling initialization and coordination between some of the key central surfaces in * System UI: The notification shade, the keyguard (lockscreen), and the status bar. @@ -327,21 +319,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public boolean setAppearance(int appearance) { - if (mAppearance != appearance) { - mAppearance = appearance; - return updateBarMode(barMode(isTransientShown(), appearance)); - } - - return false; - } - - @Override - public int getBarMode() { - return mStatusBarMode; - } - - @Override public void resendMessage(int msg) { mMessageRouter.cancelMessages(msg); mMessageRouter.sendMessage(msg); @@ -462,7 +439,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final UserInfoControllerImpl mUserInfoControllerImpl; private final DemoModeController mDemoModeController; private final NotificationsController mNotificationsController; - private final OngoingCallController mOngoingCallController; private final StatusBarSignalPolicy mStatusBarSignalPolicy; private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy; @@ -495,9 +471,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final Provider<FingerprintManager> mFingerprintManager; private final ActivityStarter mActivityStarter; - /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */ - private @Appearance int mAppearance; - private final DisplayMetrics mDisplayMetrics; // XXX: gesture research @@ -552,7 +525,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final DelayableExecutor mMainExecutor; private int mInteractingWindows; - private @TransitionMode int mStatusBarMode; private final ViewMediatorCallback mKeyguardViewMediatorCallback; private final ScrimController mScrimController; @@ -712,7 +684,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { BrightnessSliderController.Factory brightnessSliderFactory, ScreenOffAnimationController screenOffAnimationController, WallpaperController wallpaperController, - OngoingCallController ongoingCallController, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, LockscreenShadeTransitionController lockscreenShadeTransitionController, FeatureFlags featureFlags, @@ -818,7 +789,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationIconAreaController = notificationIconAreaController; mBrightnessSliderFactory = brightnessSliderFactory; mWallpaperController = wallpaperController; - mOngoingCallController = ongoingCallController; mStatusBarSignalPolicy = statusBarSignalPolicy; mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mFeatureFlags = featureFlags; @@ -852,9 +822,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mActivityIntentHelper = new ActivityIntentHelper(mContext); mActivityLaunchAnimator = activityLaunchAnimator; - // The status bar background may need updating when the ongoing call status changes. - mOngoingCallController.addCallback((animate) -> maybeUpdateBarMode()); - // TODO(b/190746471): Find a better home for this. DateTimeView.setReceiverHandler(timeTickHandler); @@ -1190,8 +1157,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mJavaAdapter.alwaysCollectFlow( mStatusBarModeRepository.isTransientShown(), this::onTransientShownChanged); mJavaAdapter.alwaysCollectFlow( - mStatusBarModeRepository.isInFullscreenMode(), - this::onStatusBarFullscreenChanged); + mStatusBarModeRepository.getStatusBarMode(), + this::updateBarMode); mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get(); mCommandQueue.addCallback(mCommandQueueCallbacks); @@ -1346,9 +1313,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }); fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> { QS qs = (QS) f; - if (qs instanceof QSFragment) { - mQSPanelController = ((QSFragment) qs).getQSPanelController(); - ((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController); + if (qs instanceof QSFragmentLegacy) { + QSFragmentLegacy qsFragment = (QSFragmentLegacy) qs; + mQSPanelController = qsFragment.getQSPanelController(); + qsFragment.setBrightnessMirrorController(mBrightnessMirrorController); } }); } @@ -1502,7 +1470,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { protected QS createDefaultQSFragment() { return mFragmentService .getFragmentHostManager(getNotificationShadeWindowView()) - .create(QSFragment.class); + .create(QSFragmentLegacy.class); } private void setUpPresenter() { @@ -1709,50 +1677,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (transientShown) { mNoAnimationOnNextBarModeChange = true; } - maybeUpdateBarMode(); - } - - private void onStatusBarFullscreenChanged(boolean isWindowShown) { - maybeUpdateBarMode(); - } - - private void maybeUpdateBarMode() { - final int barMode = barMode(isTransientShown(), mAppearance); - if (updateBarMode(barMode)) { - mLightBarController.onStatusBarModeChanged(barMode); - updateBubblesVisibility(); - } - } - - private boolean updateBarMode(int barMode) { - if (mStatusBarMode != barMode) { - mStatusBarMode = barMode; - checkBarModes(); - mAutoHideController.touchAutoHide(); - return true; - } - return false; } - private @TransitionMode int barMode(boolean isTransient, int appearance) { - boolean isFullscreen = mStatusBarModeRepository.isInFullscreenMode().getValue(); - final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_STATUS_BARS; - if (mOngoingCallController.hasOngoingCall() && isFullscreen) { - // Force show the status bar if there's an ongoing call. - return MODE_SEMI_TRANSPARENT; - } else if (isTransient) { - return MODE_SEMI_TRANSPARENT; - } else if ((appearance & lightsOutOpaque) == lightsOutOpaque) { - return MODE_LIGHTS_OUT; - } else if ((appearance & APPEARANCE_LOW_PROFILE_BARS) != 0) { - return MODE_LIGHTS_OUT_TRANSPARENT; - } else if ((appearance & APPEARANCE_OPAQUE_STATUS_BARS) != 0) { - return MODE_OPAQUE; - } else if ((appearance & APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS) != 0) { - return MODE_SEMI_TRANSPARENT; - } else { - return MODE_TRANSPARENT; - } + private void updateBarMode(StatusBarMode barMode) { + checkBarModes(); + mAutoHideController.touchAutoHide(); + updateBubblesVisibility(); } @Override @@ -1782,7 +1712,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { public void checkBarModes() { if (mDemoModeController.isInDemoMode()) return; if (mStatusBarTransitions != null) { - checkBarMode(mStatusBarMode, mStatusBarWindowState, mStatusBarTransitions); + checkBarMode( + mStatusBarModeRepository.getStatusBarMode().getValue(), + mStatusBarWindowState, + mStatusBarTransitions); } mNavigationBarController.checkNavBarModes(mDisplayId); mNoAnimationOnNextBarModeChange = false; @@ -1791,16 +1724,19 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { /** Temporarily hides Bubbles if the status bar is hidden. */ @Override public void updateBubblesVisibility() { + StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue(); mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged( - mStatusBarMode != MODE_LIGHTS_OUT - && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT)); + mode != StatusBarMode.LIGHTS_OUT + && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT)); } - void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState, + void checkBarMode( + StatusBarMode mode, + @WindowVisibleState int windowState, BarTransitions transitions) { final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive && windowState != WINDOW_STATE_HIDDEN; - transitions.transitionTo(mode, anim); + transitions.transitionTo(mode.toTransitionModeInt(), anim); } private void finishBarAnimations() { @@ -1847,8 +1783,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { pw.print(" mInteractingWindows="); pw.println(mInteractingWindows); pw.print(" mStatusBarWindowState="); pw.println(windowStateToString(mStatusBarWindowState)); - pw.print(" mStatusBarMode="); - pw.println(BarTransitions.modeToString(mStatusBarMode)); pw.print(" mDozing="); pw.println(mDozing); pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt index a61914a70f59..231a8c65a246 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone import android.annotation.ColorInt +import android.content.Context import android.graphics.Rect import android.view.InsetsFlags import android.view.ViewDebug @@ -29,20 +30,17 @@ import com.android.internal.view.AppearanceRegion import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager -import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener -import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent import java.io.PrintWriter -import java.util.Arrays import javax.inject.Inject -class LetterboxAppearance( +data class LetterboxAppearance( @Appearance val appearance: Int, - val appearanceRegions: Array<AppearanceRegion> + val appearanceRegions: List<AppearanceRegion>, ) { override fun toString(): String { val appearanceString = ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance) - return "LetterboxAppearance{$appearanceString, ${appearanceRegions.contentToString()}}" + return "LetterboxAppearance{$appearanceString, $appearanceRegions}" } } @@ -54,69 +52,81 @@ class LetterboxAppearance( class LetterboxAppearanceCalculator @Inject constructor( - private val lightBarController: LightBarController, + context: Context, dumpManager: DumpManager, private val letterboxBackgroundProvider: LetterboxBackgroundProvider, -) : OnStatusBarViewInitializedListener, Dumpable { +) : Dumpable { + + private val darkAppearanceIconColor = context.getColor( + // For a dark background status bar, use a *light* icon color. + com.android.settingslib.R.color.light_mode_icon_color_single_tone + ) + private val lightAppearanceIconColor = context.getColor( + // For a light background status bar, use a *dark* icon color. + com.android.settingslib.R.color.dark_mode_icon_color_single_tone + ) init { dumpManager.registerCriticalDumpable(this) } - private var statusBarBoundsProvider: StatusBarBoundsProvider? = null - private var lastAppearance: Int? = null - private var lastAppearanceRegions: Array<AppearanceRegion>? = null - private var lastLetterboxes: Array<LetterboxDetails>? = null + private var lastAppearanceRegions: List<AppearanceRegion>? = null + private var lastLetterboxes: List<LetterboxDetails>? = null private var lastLetterboxAppearance: LetterboxAppearance? = null fun getLetterboxAppearance( @Appearance originalAppearance: Int, - originalAppearanceRegions: Array<AppearanceRegion>, - letterboxes: Array<LetterboxDetails> + originalAppearanceRegions: List<AppearanceRegion>, + letterboxes: List<LetterboxDetails>, + statusBarBounds: BoundsPair, ): LetterboxAppearance { lastAppearance = originalAppearance lastAppearanceRegions = originalAppearanceRegions lastLetterboxes = letterboxes return getLetterboxAppearanceInternal( - letterboxes, originalAppearance, originalAppearanceRegions) + letterboxes, originalAppearance, originalAppearanceRegions, statusBarBounds) .also { lastLetterboxAppearance = it } } private fun getLetterboxAppearanceInternal( - letterboxes: Array<LetterboxDetails>, + letterboxes: List<LetterboxDetails>, originalAppearance: Int, - originalAppearanceRegions: Array<AppearanceRegion> + originalAppearanceRegions: List<AppearanceRegion>, + statusBarBounds: BoundsPair, ): LetterboxAppearance { - if (isScrimNeeded(letterboxes)) { + if (isScrimNeeded(letterboxes, statusBarBounds)) { return originalAppearanceWithScrim(originalAppearance, originalAppearanceRegions) } val appearance = appearanceWithoutScrim(originalAppearance) val appearanceRegions = getAppearanceRegions(originalAppearanceRegions, letterboxes) - return LetterboxAppearance(appearance, appearanceRegions.toTypedArray()) + return LetterboxAppearance(appearance, appearanceRegions) } - private fun isScrimNeeded(letterboxes: Array<LetterboxDetails>): Boolean { + private fun isScrimNeeded( + letterboxes: List<LetterboxDetails>, + statusBarBounds: BoundsPair, + ): Boolean { if (isOuterLetterboxMultiColored()) { return true } return letterboxes.any { letterbox -> - letterbox.letterboxInnerBounds.overlapsWith(getStartSideIconBounds()) || - letterbox.letterboxInnerBounds.overlapsWith(getEndSideIconsBounds()) + letterbox.letterboxInnerBounds.overlapsWith(statusBarBounds.start) || + letterbox.letterboxInnerBounds.overlapsWith(statusBarBounds.end) } } private fun getAppearanceRegions( - originalAppearanceRegions: Array<AppearanceRegion>, - letterboxes: Array<LetterboxDetails> + originalAppearanceRegions: List<AppearanceRegion>, + letterboxes: List<LetterboxDetails> ): List<AppearanceRegion> { return sanitizeAppearanceRegions(originalAppearanceRegions, letterboxes) + getAllOuterAppearanceRegions(letterboxes) } private fun sanitizeAppearanceRegions( - originalAppearanceRegions: Array<AppearanceRegion>, - letterboxes: Array<LetterboxDetails> + originalAppearanceRegions: List<AppearanceRegion>, + letterboxes: List<LetterboxDetails> ): List<AppearanceRegion> = originalAppearanceRegions.map { appearanceRegion -> val matchingLetterbox = @@ -134,7 +144,7 @@ constructor( private fun originalAppearanceWithScrim( @Appearance originalAppearance: Int, - originalAppearanceRegions: Array<AppearanceRegion> + originalAppearanceRegions: List<AppearanceRegion> ): LetterboxAppearance { return LetterboxAppearance( originalAppearance or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS, @@ -146,7 +156,7 @@ constructor( originalAppearance and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS.inv() private fun getAllOuterAppearanceRegions( - letterboxes: Array<LetterboxDetails> + letterboxes: List<LetterboxDetails> ): List<AppearanceRegion> = letterboxes.map(this::getOuterAppearanceRegions).flatten() private fun getOuterAppearanceRegions( @@ -172,11 +182,9 @@ constructor( private fun getOuterAppearance(): Int { val backgroundColor = outerLetterboxBackgroundColor() val darkAppearanceContrast = - ContrastColorUtil.calculateContrast( - lightBarController.darkAppearanceIconColor, backgroundColor) + ContrastColorUtil.calculateContrast(darkAppearanceIconColor, backgroundColor) val lightAppearanceContrast = - ContrastColorUtil.calculateContrast( - lightBarController.lightAppearanceIconColor, backgroundColor) + ContrastColorUtil.calculateContrast(lightAppearanceIconColor, backgroundColor) return if (lightAppearanceContrast > darkAppearanceContrast) { WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS } else { @@ -193,18 +201,6 @@ constructor( return letterboxBackgroundProvider.isLetterboxBackgroundMultiColored } - private fun getEndSideIconsBounds(): Rect { - return statusBarBoundsProvider?.visibleEndSideBounds ?: Rect() - } - - private fun getStartSideIconBounds(): Rect { - return statusBarBoundsProvider?.visibleStartSideBounds ?: Rect() - } - - override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) { - statusBarBoundsProvider = component.boundsProvider - } - private fun Rect.overlapsWith(other: Rect): Boolean { if (this.contains(other) || other.contains(this)) { return false @@ -216,8 +212,8 @@ constructor( pw.println( """ lastAppearance: ${lastAppearance?.toAppearanceString()} - lastAppearanceRegion: ${Arrays.toString(lastAppearanceRegions)}, - lastLetterboxes: ${Arrays.toString(lastLetterboxes)}, + lastAppearanceRegion: $lastAppearanceRegions, + lastLetterboxes: $lastLetterboxes, lastLetterboxAppearance: $lastLetterboxAppearance """.trimIndent()) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt index 61377e20a9e1..2e3f0d0abc0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt @@ -18,12 +18,10 @@ package com.android.systemui.statusbar.phone import com.android.systemui.CoreStartable -import com.android.systemui.statusbar.core.StatusBarInitializer import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap -import dagger.multibindings.IntoSet @Module abstract class LetterboxModule { @@ -31,10 +29,4 @@ abstract class LetterboxModule { @IntoMap @ClassKey(LetterboxBackgroundProvider::class) abstract fun bindFeature(impl: LetterboxBackgroundProvider): CoreStartable - - @Binds - @IntoSet - abstract fun statusBarInitializedListener( - letterboxAppearanceCalculator: LetterboxAppearanceCalculator - ): StatusBarInitializer.OnStatusBarViewInitializedListener } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index c2dd05940864..4d3e2ad4813a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -22,7 +22,6 @@ import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; -import android.annotation.ColorInt; import android.content.Context; import android.graphics.Rect; import android.util.Log; @@ -31,17 +30,22 @@ import android.view.ViewDebug; import android.view.WindowInsetsController.Appearance; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.view.AppearanceRegion; +import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.settings.DisplayTracker; +import com.android.systemui.statusbar.data.model.StatusBarAppearance; +import com.android.systemui.statusbar.data.repository.StatusBarModeRepository; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.Compile; +import com.android.systemui.util.kotlin.JavaAdapter; import java.io.PrintWriter; import java.util.ArrayList; @@ -52,7 +56,8 @@ import javax.inject.Inject; * Controls how light status bar flag applies to the icons. */ @SysUISingleton -public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable { +public class LightBarController implements + BatteryController.BatteryStateChangeCallback, Dumpable, CoreStartable { private static final String TAG = "LightBarController"; private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG; @@ -60,18 +65,19 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f; + private final JavaAdapter mJavaAdapter; private final SysuiDarkIconDispatcher mStatusBarIconController; private final BatteryController mBatteryController; + private final StatusBarModeRepository mStatusBarModeRepository; private BiometricUnlockController mBiometricUnlockController; private LightBarTransitionsController mNavigationBarController; private @Appearance int mAppearance; private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0]; private int mStatusBarMode; + private BoundsPair mStatusBarBounds = new BoundsPair(new Rect(), new Rect()); private int mNavigationBarMode; private int mNavigationMode; - private final int mDarkIconColor; - private final int mLightIconColor; /** * Whether the navigation bar should be light factoring in already how much alpha the scrim has. @@ -116,18 +122,18 @@ public class LightBarController implements BatteryController.BatteryStateChangeC @Inject public LightBarController( Context ctx, + JavaAdapter javaAdapter, DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, + StatusBarModeRepository statusBarModeRepository, DumpManager dumpManager, DisplayTracker displayTracker) { - mDarkIconColor = ctx.getColor( - com.android.settingslib.R.color.dark_mode_icon_color_single_tone); - mLightIconColor = ctx.getColor( - com.android.settingslib.R.color.light_mode_icon_color_single_tone); + mJavaAdapter = javaAdapter; mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher; mBatteryController = batteryController; mBatteryController.addCallback(this); + mStatusBarModeRepository = statusBarModeRepository; mNavigationMode = navModeController.addListener((mode) -> { mNavigationMode = mode; }); @@ -137,14 +143,11 @@ public class LightBarController implements BatteryController.BatteryStateChangeC } } - @ColorInt - int getLightAppearanceIconColor() { - return mDarkIconColor; - } - - @ColorInt - int getDarkAppearanceIconColor() { - return mLightIconColor; + @Override + public void start() { + mJavaAdapter.alwaysCollectFlow( + mStatusBarModeRepository.getStatusBarAppearance(), + this::onStatusBarAppearanceChanged); } public void setNavigationBar(LightBarTransitionsController navigationBar) { @@ -157,26 +160,48 @@ public class LightBarController implements BatteryController.BatteryStateChangeC mBiometricUnlockController = biometricUnlockController; } - void onStatusBarAppearanceChanged(AppearanceRegion[] appearanceRegions, boolean sbModeChanged, - int statusBarMode, boolean navbarColorManagedByIme) { + private void onStatusBarAppearanceChanged(@Nullable StatusBarAppearance params) { + if (params == null) { + return; + } + int newStatusBarMode = params.getMode().toTransitionModeInt(); + boolean sbModeChanged = mStatusBarMode != newStatusBarMode; + mStatusBarMode = newStatusBarMode; + + boolean sbBoundsChanged = !mStatusBarBounds.equals(params.getBounds()); + mStatusBarBounds = params.getBounds(); + + onStatusBarAppearanceChanged( + params.getAppearanceRegions().toArray(new AppearanceRegion[0]), + sbModeChanged, + sbBoundsChanged, + params.getNavbarColorManagedByIme()); + } + + private void onStatusBarAppearanceChanged( + AppearanceRegion[] appearanceRegions, + boolean sbModeChanged, + boolean sbBoundsChanged, + boolean navbarColorManagedByIme) { final int numStacks = appearanceRegions.length; boolean stackAppearancesChanged = mAppearanceRegions.length != numStacks; for (int i = 0; i < numStacks && !stackAppearancesChanged; i++) { stackAppearancesChanged |= !appearanceRegions[i].equals(mAppearanceRegions[i]); } - if (stackAppearancesChanged || sbModeChanged || mIsCustomizingForBackNav) { + + if (stackAppearancesChanged + || sbModeChanged + // Be sure to re-draw when the status bar bounds have changed because the status bar + // icons may have moved to be part of a different appearance region. See b/301605450 + || sbBoundsChanged + || mIsCustomizingForBackNav) { mAppearanceRegions = appearanceRegions; - onStatusBarModeChanged(statusBarMode); + updateStatus(mAppearanceRegions); mIsCustomizingForBackNav = false; } mNavbarColorManagedByIme = navbarColorManagedByIme; } - void onStatusBarModeChanged(int newBarMode) { - mStatusBarMode = newBarMode; - updateStatus(mAppearanceRegions); - } - public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged, int navigationBarMode, boolean navbarColorManagedByIme) { int diff = appearance ^ mAppearance; @@ -224,7 +249,10 @@ public class LightBarController implements BatteryController.BatteryStateChangeC } private void reevaluate() { - onStatusBarAppearanceChanged(mAppearanceRegions, true /* sbModeChange */, mStatusBarMode, + onStatusBarAppearanceChanged( + mAppearanceRegions, + /* sbModeChanged= */ true, + /* sbBoundsChanged= */ true, mNavbarColorManagedByIme); onNavigationBarAppearanceChanged(mAppearance, true /* nbModeChanged */, mNavigationBarMode, mNavbarColorManagedByIme); @@ -444,31 +472,43 @@ public class LightBarController implements BatteryController.BatteryStateChangeC * Injectable factory for creating a {@link LightBarController}. */ public static class Factory { + private final JavaAdapter mJavaAdapter; private final DarkIconDispatcher mDarkIconDispatcher; private final BatteryController mBatteryController; private final NavigationModeController mNavModeController; + private final StatusBarModeRepository mStatusBarModeRepository; private final DumpManager mDumpManager; private final DisplayTracker mDisplayTracker; @Inject public Factory( + JavaAdapter javaAdapter, DarkIconDispatcher darkIconDispatcher, BatteryController batteryController, NavigationModeController navModeController, + StatusBarModeRepository statusBarModeRepository, DumpManager dumpManager, DisplayTracker displayTracker) { - + mJavaAdapter = javaAdapter; mDarkIconDispatcher = darkIconDispatcher; mBatteryController = batteryController; mNavModeController = navModeController; + mStatusBarModeRepository = statusBarModeRepository; mDumpManager = dumpManager; mDisplayTracker = displayTracker; } /** Create an {@link LightBarController} */ public LightBarController create(Context context) { - return new LightBarController(context, mDarkIconDispatcher, mBatteryController, - mNavModeController, mDumpManager, mDisplayTracker); + return new LightBarController( + context, + mJavaAdapter, + mDarkIconDispatcher, + mBatteryController, + mNavModeController, + mStatusBarModeRepository, + mDumpManager, + mDisplayTracker); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index b53939e594fb..a27e67b965a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -112,6 +112,9 @@ public class PhoneStatusBarView extends FrameLayout { mDisplayCutout = null; } + // Per b/300629388, we let the PhoneStatusBarView detect onConfigurationChanged to + // updateResources, instead of letting the PhoneStatusBarViewController detect onConfigChanged + // then notify PhoneStatusBarView. @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index e1096e2d3482..f9702dd12535 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone import android.app.StatusBarManager.WINDOW_STATUS_BAR -import android.content.res.Configuration import android.graphics.Point import android.util.Log import android.view.MotionEvent @@ -72,10 +71,6 @@ private constructor( private val configurationListener = object : ConfigurationController.ConfigurationListener { - override fun onConfigChanged(newConfig: Configuration?) { - mView.updateResources() - } - override fun onDensityOrFontScaleChanged() { mView.onDensityOrFontScaleChanged() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt index f5ba399fe51a..00b08f097e97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt @@ -22,22 +22,34 @@ import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentCom import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.END_SIDE_CONTENT import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.START_SIDE_CONTENT import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope +import com.android.systemui.util.ListenerSet import com.android.systemui.util.boundsOnScreen import javax.inject.Inject import javax.inject.Named -/** Provides various bounds within the status bar. */ +/** + * Provides the bounds of the **content** on each side of the status bar. + * + * This is distinct from [StatusBarContentInsetsProvider], which provides the bounds of full status + * bar after accounting for system insets. + */ @StatusBarFragmentScope class StatusBarBoundsProvider @Inject constructor( - private val changeListeners: Set<@JvmSuppressWildcards BoundsChangeListener>, @Named(START_SIDE_CONTENT) private val startSideContent: View, @Named(END_SIDE_CONTENT) private val endSideContent: View, ) : StatusBarFragmentComponent.Startable { interface BoundsChangeListener { - fun onStatusBarBoundsChanged() + fun onStatusBarBoundsChanged(bounds: BoundsPair) + } + + private val changeListeners = ListenerSet<BoundsChangeListener>() + + fun addChangeListener(listener: BoundsChangeListener) { + changeListeners.addIfAbsent(listener) + listener.onStatusBarBoundsChanged(previousBounds) } private var previousBounds = @@ -48,7 +60,7 @@ constructor( val newBounds = BoundsPair(start = visibleStartSideBounds, end = visibleEndSideBounds) if (previousBounds != newBounds) { previousBounds = newBounds - changeListeners.forEach { it.onStatusBarBoundsChanged() } + changeListeners.forEach { it.onStatusBarBoundsChanged(newBounds) } } } @@ -89,4 +101,10 @@ constructor( get() = startSideContent.boundsOnScreen } -private data class BoundsPair(val start: Rect, val end: Rect) +/** + * Stores bounds of the status content. + * + * @property start the bounds of the status bar content on the start side (clock & notif icons). + * @property end the bounds of the status bar content on the end side (system icons & battery). + */ +data class BoundsPair(val start: Rect, val end: Rect) 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 8d86d729d958..eedf35f1e9d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import static android.view.WindowInsets.Type.navigationBars; + import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; @@ -66,7 +67,6 @@ import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor; -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor; import com.android.systemui.keyguard.shared.model.DismissAction; @@ -134,7 +134,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // dranw its first frame. private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000; - private static String TAG = "StatusBarKeyguardViewManager"; + private static final String TAG = "StatusBarKeyguardViewManager"; private static final boolean DEBUG = false; protected final Context mContext; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt deleted file mode 100644 index 829577b64c17..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone - -import android.view.InsetsFlags -import android.view.ViewDebug -import android.view.WindowInsets.Type.InsetsType -import android.view.WindowInsetsController.Appearance -import android.view.WindowInsetsController.Behavior -import com.android.internal.statusbar.LetterboxDetails -import com.android.internal.view.AppearanceRegion -import com.android.systemui.Dumpable -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dump.DumpManager -import java.io.PrintWriter -import javax.inject.Inject - -/** - * Top-level listener of system attributes changed. This class is __always the first__ one to be - * notified about changes. - * - * It is responsible for modifying any attributes if necessary, and then notifying the other - * downstream listeners. - */ -@SysUISingleton -class SystemBarAttributesListener -@Inject -internal constructor( - private val centralSurfaces: CentralSurfaces, - private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator, - private val lightBarController: LightBarController, - dumpManager: DumpManager, -) : Dumpable, StatusBarBoundsProvider.BoundsChangeListener { - - private var lastLetterboxAppearance: LetterboxAppearance? = null - private var lastSystemBarAttributesParams: SystemBarAttributesParams? = null - - init { - dumpManager.registerCriticalDumpable(this) - } - - override fun onStatusBarBoundsChanged() { - val params = lastSystemBarAttributesParams - if (params != null && shouldUseLetterboxAppearance(params.letterboxesArray)) { - onSystemBarAttributesChanged( - params.displayId, - params.appearance, - params.appearanceRegionsArray, - params.navbarColorManagedByIme, - params.behavior, - params.requestedVisibleTypes, - params.packageName, - params.letterboxesArray) - } - } - - fun onSystemBarAttributesChanged( - displayId: Int, - @Appearance originalAppearance: Int, - originalAppearanceRegions: Array<AppearanceRegion>, - navbarColorManagedByIme: Boolean, - @Behavior behavior: Int, - @InsetsType requestedVisibleTypes: Int, - packageName: String, - letterboxDetails: Array<LetterboxDetails> - ) { - lastSystemBarAttributesParams = - SystemBarAttributesParams( - displayId, - originalAppearance, - originalAppearanceRegions.toList(), - navbarColorManagedByIme, - behavior, - requestedVisibleTypes, - packageName, - letterboxDetails.toList()) - - val (appearance, appearanceRegions) = - modifyAppearanceIfNeeded( - originalAppearance, originalAppearanceRegions, letterboxDetails) - - val barModeChanged = centralSurfaces.setAppearance(appearance) - - lightBarController.onStatusBarAppearanceChanged( - appearanceRegions, barModeChanged, centralSurfaces.barMode, navbarColorManagedByIme) - - centralSurfaces.updateBubblesVisibility() - } - - private fun modifyAppearanceIfNeeded( - appearance: Int, - appearanceRegions: Array<AppearanceRegion>, - letterboxDetails: Array<LetterboxDetails> - ): Pair<Int, Array<AppearanceRegion>> = - if (shouldUseLetterboxAppearance(letterboxDetails)) { - val letterboxAppearance = - letterboxAppearanceCalculator.getLetterboxAppearance( - appearance, appearanceRegions, letterboxDetails) - lastLetterboxAppearance = letterboxAppearance - Pair(letterboxAppearance.appearance, letterboxAppearance.appearanceRegions) - } else { - lastLetterboxAppearance = null - Pair(appearance, appearanceRegions) - } - - private fun shouldUseLetterboxAppearance(letterboxDetails: Array<LetterboxDetails>) = - letterboxDetails.isNotEmpty() - - override fun dump(printWriter: PrintWriter, strings: Array<String>) { - printWriter.println("lastSystemBarAttributesParams: $lastSystemBarAttributesParams") - printWriter.println("lastLetterboxAppearance: $lastLetterboxAppearance") - } -} - -/** - * Keeps track of the parameters passed in - * [SystemBarAttributesListener.onSystemBarAttributesChanged]. - */ -private data class SystemBarAttributesParams( - val displayId: Int, - @Appearance val appearance: Int, - val appearanceRegions: List<AppearanceRegion>, - val navbarColorManagedByIme: Boolean, - @Behavior val behavior: Int, - @InsetsType val requestedVisibleTypes: Int, - val packageName: String, - val letterboxes: List<LetterboxDetails>, -) { - val letterboxesArray = letterboxes.toTypedArray() - val appearanceRegionsArray = appearanceRegions.toTypedArray() - override fun toString(): String { - val appearanceToString = - ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", appearance) - return """SystemBarAttributesParams( - displayId=$displayId, - appearance=$appearanceToString, - appearanceRegions=$appearanceRegions, - navbarColorManagedByIme=$navbarColorManagedByIme, - behavior=$behavior, - requestedVisibleTypes=$requestedVisibleTypes, - packageName='$packageName', - letterboxes=$letterboxes, - letterboxesArray=${letterboxesArray.contentToString()}, - appearanceRegionsArray=${appearanceRegionsArray.contentToString()} - )""".trimMargin() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index 07c23d1f66a0..6ef877bd57bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -26,22 +26,16 @@ import com.android.systemui.statusbar.HeadsUpStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; -import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; import com.android.systemui.statusbar.phone.StatusBarLocation; -import com.android.systemui.statusbar.phone.SystemBarAttributesListener; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; -import dagger.Binds; import dagger.Module; import dagger.Provides; -import dagger.multibindings.IntoSet; -import dagger.multibindings.Multibinds; import java.util.Optional; -import java.util.Set; import javax.inject.Named; @@ -159,14 +153,4 @@ public interface StatusBarFragmentModule { static HeadsUpStatusBarView providesHeasdUpStatusBarView(@RootView PhoneStatusBarView view) { return view.findViewById(R.id.heads_up_status_bar_view); } - - /** */ - @Multibinds - Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners(); - - /** */ - @Binds - @IntoSet - StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener( - SystemBarAttributesListener systemBarAttributesListener); } 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 4b1e7a46836d..b0532ce1817b 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 @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.time.SystemClock @@ -57,6 +58,7 @@ import javax.inject.Inject class OngoingCallController @Inject constructor( @Application private val scope: CoroutineScope, private val context: Context, + private val ongoingCallRepository: OngoingCallRepository, private val notifCollection: CommonNotifCollection, private val systemClock: SystemClock, private val activityStarter: ActivityStarter, @@ -207,7 +209,7 @@ class OngoingCallController @Inject constructor( statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(true) } updateGestureListening() - mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) } + sendStateChangeEvent() } else { // If we failed to update the chip, don't store the call info. Then [hasOngoingCall] // will return false and we fall back to typical notification handling. @@ -261,7 +263,7 @@ class OngoingCallController @Inject constructor( tearDownChipView() statusBarWindowController.setOngoingProcessRequiresStatusBarVisible(false) swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG) - mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) } + sendStateChangeEvent() uidObserver.unregister() } @@ -288,6 +290,11 @@ class OngoingCallController @Inject constructor( swipeStatusBarAwayGestureHandler.removeOnGestureDetectedCallback(TAG) } + private fun sendStateChangeEvent() { + ongoingCallRepository.setHasOngoingCall(hasOngoingCall()) + mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) } + } + private data class CallNotificationInfo( val key: String, val callStartTime: Long, @@ -374,9 +381,7 @@ class OngoingCallController @Inject constructor( if (oldIsCallAppVisible != isCallAppVisible) { // Animations may be run as a result of the call's state change, so ensure // the listener is notified on the main thread. - mainExecutor.execute { - mListeners.forEach { l -> l.onOngoingCallStateChanged(animate = true) } - } + mainExecutor.execute { sendStateChangeEvent() } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt new file mode 100644 index 000000000000..da9c45ad5ada --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone.ongoingcall.data.repository + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** + * Repository storing whether there's current an ongoing call notification. + * + * This class is used to break a dependency cycle between + * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController] and + * [com.android.systemui.statusbar.data.repository.StatusBarModeRepository]. Instead, those two + * classes both refer to this repository. + */ +@SysUISingleton +class OngoingCallRepository @Inject constructor() { + private val _hasOngoingCall = MutableStateFlow(false) + /** True if there's currently an ongoing call notification and false otherwise. */ + val hasOngoingCall: StateFlow<Boolean> = _hasOngoingCall.asStateFlow() + + /** + * Sets whether there's currently an ongoing call notification. Should only be set from + * [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController]. + */ + fun setHasOngoingCall(hasOngoingCall: Boolean) { + _hasOngoingCall.value = hasOngoingCall + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index 02473f276b4b..aacdc6323179 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -186,6 +186,10 @@ constructor( } ) } + + fun logOnSimStateChanged() { + buffer.log(TAG, LogLevel.INFO, "onSimStateChanged") + } } private const val TAG = "MobileInputLog" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt index ea77163f0556..cf1c97c7271f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt @@ -90,4 +90,12 @@ interface MobileConnectionsRepository { /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */ val defaultMobileIconGroup: Flow<MobileIconGroup> + + /** + * If any active SIM on the device is in + * [android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED] or + * [android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED] or + * [android.telephony.TelephonyManager.SIM_STATE_PERM_DISABLED] + */ + val isAnySimSecure: Flow<Boolean> } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt index 991ff56e683c..22916315d274 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt @@ -151,6 +151,8 @@ constructor( override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> = activeRepo.flatMapLatest { it.defaultMobileIconGroup } + override val isAnySimSecure: Flow<Boolean> = activeRepo.flatMapLatest { it.isAnySimSecure } + override val defaultDataSubId: StateFlow<Int> = activeRepo .flatMapLatest { it.defaultDataSubId } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt index ee13d93e735d..c7987e2f8eb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt @@ -134,6 +134,8 @@ constructor( override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G) + override val isAnySimSecure: Flow<Boolean> = flowOf(false) + override val defaultMobileIconMapping = MutableStateFlow(TelephonyIcons.ICON_NAME_TO_ICON) /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index cd6862113ee9..dc50990d002a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -71,6 +71,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapNotNull @@ -181,6 +182,7 @@ class MobileConnectionRepositoryImpl( telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback) awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } } + .flowOn(bgDispatcher) .scan(initial = initial) { state, event -> state.applyEvent(event) } .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initial) } @@ -358,6 +360,7 @@ class MobileConnectionRepositoryImpl( awaitClose { context.unregisterReceiver(receiver) } } + .flowOn(bgDispatcher) .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName) override val dataEnabled = run { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 74a849a8c01f..ecb80f28de3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -28,9 +28,10 @@ import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener import android.telephony.TelephonyManager import androidx.annotation.VisibleForTesting import com.android.internal.telephony.PhoneConstants +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.MobileMappings.Config -import com.android.systemui.res.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -38,6 +39,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.res.R import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger @@ -94,6 +96,7 @@ constructor( // See [CarrierMergedConnectionRepository] for details. wifiRepository: WifiRepository, private val fullMobileRepoFactory: FullMobileConnectionRepository.Factory, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : MobileConnectionsRepository { private var subIdRepositoryCache: MutableMap<Int, FullMobileConnectionRepository> = mutableMapOf() @@ -134,22 +137,24 @@ constructor( ) .stateIn(scope, started = SharingStarted.WhileSubscribed(), null) - private val mobileSubscriptionsChangeEvent: Flow<Unit> = conflatedCallbackFlow { - val callback = - object : SubscriptionManager.OnSubscriptionsChangedListener() { - override fun onSubscriptionsChanged() { - logger.logOnSubscriptionsChanged() - trySend(Unit) - } - } + private val mobileSubscriptionsChangeEvent: Flow<Unit> = + conflatedCallbackFlow { + val callback = + object : SubscriptionManager.OnSubscriptionsChangedListener() { + override fun onSubscriptionsChanged() { + logger.logOnSubscriptionsChanged() + trySend(Unit) + } + } - subscriptionManager.addOnSubscriptionsChangedListener( - bgDispatcher.asExecutor(), - callback, - ) + subscriptionManager.addOnSubscriptionsChangedListener( + bgDispatcher.asExecutor(), + callback, + ) - awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) } - } + awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) } + } + .flowOn(bgDispatcher) /** * State flow that emits the set of mobile data subscriptions, each represented by its own @@ -184,6 +189,7 @@ constructor( telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback) awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } } + .flowOn(bgDispatcher) .distinctUntilChanged() .logDiffsForTable( tableLogger, @@ -250,6 +256,27 @@ constructor( .distinctUntilChanged() .onEach { logger.logDefaultMobileIconGroup(it) } + override val isAnySimSecure: Flow<Boolean> = + conflatedCallbackFlow { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onSimStateChanged(subId: Int, slotId: Int, simState: Int) { + logger.logOnSimStateChanged() + trySend(keyguardUpdateMonitor.isSimPinSecure) + } + } + keyguardUpdateMonitor.registerCallback(callback) + trySend(false) + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } + } + .logDiffsForTable( + tableLogger, + LOGGING_PREFIX, + columnName = "isAnySimSecure", + initialValue = false, + ) + .distinctUntilChanged() + override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository = getOrCreateRepoForSubId(subId) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt index efa092bb3f40..250fe53a2df6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt @@ -22,11 +22,12 @@ import androidx.annotation.StringRes import androidx.annotation.VisibleForTesting import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION -import com.android.systemui.res.R +import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger +import com.android.systemui.res.R import com.android.systemui.statusbar.connectivity.WifiIcons import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel @@ -90,50 +91,56 @@ sealed interface WifiIcon : Diffable<WifiIcon> { )}" ) ) - is WifiNetworkModel.Active -> { - val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[model.level]) - val contentDescription = - ContentDescription.Loaded( - if (model.isValidated) { - (levelDesc) - } else { - "$levelDesc,${context.getString(NO_INTERNET)}" - } - ) - Visible(model.toIcon(showHotspotInfo), contentDescription) - } + is WifiNetworkModel.Active -> model.toIcon(showHotspotInfo, context) } - @DrawableRes - private fun WifiNetworkModel.Active.toIcon(showHotspotInfo: Boolean): Int { - return if (!showHotspotInfo) { - this.toBasicIcon() + private fun WifiNetworkModel.Active.toIcon( + showHotspotInfo: Boolean, + context: Context, + ): Visible { + return if ( + !showHotspotInfo || + this.hotspotDeviceType == WifiNetworkModel.HotspotDeviceType.NONE + ) { + this.toBasicIcon(context) } else { - when (this.hotspotDeviceType) { - WifiNetworkModel.HotspotDeviceType.NONE -> this.toBasicIcon() - WifiNetworkModel.HotspotDeviceType.TABLET -> - com.android.settingslib.R.drawable.ic_hotspot_tablet - WifiNetworkModel.HotspotDeviceType.LAPTOP -> - com.android.settingslib.R.drawable.ic_hotspot_laptop - WifiNetworkModel.HotspotDeviceType.WATCH -> - com.android.settingslib.R.drawable.ic_hotspot_watch - WifiNetworkModel.HotspotDeviceType.AUTO -> - com.android.settingslib.R.drawable.ic_hotspot_auto - // Use phone as the default drawable - WifiNetworkModel.HotspotDeviceType.PHONE, - WifiNetworkModel.HotspotDeviceType.UNKNOWN, - WifiNetworkModel.HotspotDeviceType.INVALID -> - com.android.settingslib.R.drawable.ic_hotspot_phone - } + val icon = + when (this.hotspotDeviceType) { + WifiNetworkModel.HotspotDeviceType.TABLET -> + com.android.settingslib.R.drawable.ic_hotspot_tablet + WifiNetworkModel.HotspotDeviceType.LAPTOP -> + com.android.settingslib.R.drawable.ic_hotspot_laptop + WifiNetworkModel.HotspotDeviceType.WATCH -> + com.android.settingslib.R.drawable.ic_hotspot_watch + WifiNetworkModel.HotspotDeviceType.AUTO -> + com.android.settingslib.R.drawable.ic_hotspot_auto + // Use phone as the default drawable + WifiNetworkModel.HotspotDeviceType.PHONE, + WifiNetworkModel.HotspotDeviceType.UNKNOWN, + WifiNetworkModel.HotspotDeviceType.INVALID -> + com.android.settingslib.R.drawable.ic_hotspot_phone + WifiNetworkModel.HotspotDeviceType.NONE -> + throw IllegalStateException("NONE checked earlier") + } + Visible( + icon, + ContentDescription.Loaded(context.getString(WIFI_OTHER_DEVICE_CONNECTION)), + ) } } - @DrawableRes - private fun WifiNetworkModel.Active.toBasicIcon(): Int { + private fun WifiNetworkModel.Active.toBasicIcon(context: Context): Visible { + val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level]) return if (this.isValidated) { - WifiIcons.WIFI_FULL_ICONS[this.level] + Visible( + WifiIcons.WIFI_FULL_ICONS[this.level], + ContentDescription.Loaded(levelDesc), + ) } else { - WifiIcons.WIFI_NO_INTERNET_ICONS[this.level] + Visible( + WifiIcons.WIFI_NO_INTERNET_ICONS[this.level], + ContentDescription.Loaded("$levelDesc,${context.getString(NO_INTERNET)}"), + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java index ae8128d20d3d..27f8121e6e66 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -67,7 +67,7 @@ public class ToastUI implements CoreStartable, CommandQueue.Callbacks { private final ToastLogger mToastLogger; @Nullable private ToastPresenter mPresenter; @Nullable private ITransientNotificationCallback mCallback; - private ToastOutAnimatorListener mToastOutAnimatorListener; + @VisibleForTesting ToastOutAnimatorListener mToastOutAnimatorListener; @VisibleForTesting SystemUIToast mToast; private int mOrientation = ORIENTATION_PORTRAIT; @@ -172,7 +172,7 @@ public class ToastUI implements CoreStartable, CommandQueue.Callbacks { if (mToast.getOutAnimation() != null) { Animator animator = mToast.getOutAnimation(); mToastOutAnimatorListener = new ToastOutAnimatorListener(mPresenter, mCallback, - runnable); + runnable, animator); animator.addListener(mToastOutAnimatorListener); animator.start(); } else { @@ -211,14 +211,17 @@ public class ToastUI implements CoreStartable, CommandQueue.Callbacks { final ToastPresenter mPrevPresenter; final ITransientNotificationCallback mPrevCallback; @Nullable Runnable mShowNextToastRunnable; + @NonNull private final Animator mAnimator; ToastOutAnimatorListener( @NonNull ToastPresenter presenter, @NonNull ITransientNotificationCallback callback, - @Nullable Runnable runnable) { + @Nullable Runnable runnable, + @NonNull Animator animator) { mPrevPresenter = presenter; mPrevCallback = callback; mShowNextToastRunnable = runnable; + mAnimator = animator; } void setShowNextToastRunnable(Runnable runnable) { @@ -231,6 +234,8 @@ public class ToastUI implements CoreStartable, CommandQueue.Callbacks { if (mShowNextToastRunnable != null) { mShowNextToastRunnable.run(); } + mAnimator.removeListener(this); + mShowNextToastRunnable = null; mToastOutAnimatorListener = null; } } diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt index e4894992b49f..ce9d6fd752c5 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt @@ -37,7 +37,6 @@ import com.android.internal.logging.UiEventLogger import com.android.internal.util.UserIcons import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.systemui.res.R import com.android.systemui.SystemUISecondaryUserService import com.android.systemui.animation.Expandable import com.android.systemui.broadcast.BroadcastDispatcher @@ -50,6 +49,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController +import com.android.systemui.res.R import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.CreateUserActivity import com.android.systemui.user.data.model.UserSwitcherSettingsModel @@ -62,6 +62,7 @@ import com.android.systemui.user.shared.model.UserModel import com.android.systemui.user.utils.MultiUserActionsEvent import com.android.systemui.user.utils.MultiUserActionsEventHelper import com.android.systemui.util.kotlin.pairwise +import com.android.systemui.utils.UserRestrictionChecker import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -103,6 +104,7 @@ constructor( private val refreshUsersScheduler: RefreshUsersScheduler, private val guestUserInteractor: GuestUserInteractor, private val uiEventLogger: UiEventLogger, + private val userRestrictionChecker: UserRestrictionChecker, ) { /** * Defines interface for classes that can be notified when the state of users on the device is @@ -593,6 +595,7 @@ constructor( ) && // If the user is auto-created is must not be currently resetting. !(isGuestUserAutoCreated && isGuestUserResetting), + userRestrictionChecker = userRestrictionChecker, ) } diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt index 93573fa42c96..80139bd6ac0c 100644 --- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt @@ -22,10 +22,10 @@ import android.content.pm.UserInfo import android.graphics.Bitmap import android.os.UserManager import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin -import com.android.settingslib.RestrictedLockUtilsInternal import com.android.systemui.res.R import com.android.systemui.user.data.source.UserRecord import com.android.systemui.user.shared.model.UserActionModel +import com.android.systemui.utils.UserRestrictionChecker /** * Defines utility functions for helping with legacy data code for users. @@ -68,6 +68,7 @@ object LegacyUserDataHelper { actionType: UserActionModel, isRestricted: Boolean, isSwitchToEnabled: Boolean, + userRestrictionChecker: UserRestrictionChecker, ): UserRecord { return UserRecord( isGuest = actionType == UserActionModel.ENTER_GUEST_MODE, @@ -79,6 +80,7 @@ object LegacyUserDataHelper { getEnforcedAdmin( context = context, selectedUserId = selectedUserId, + userRestrictionChecker = userRestrictionChecker, ), isManageUsers = actionType == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, ) @@ -103,9 +105,10 @@ object LegacyUserDataHelper { private fun getEnforcedAdmin( context: Context, selectedUserId: Int, + userRestrictionChecker: UserRestrictionChecker ): EnforcedAdmin? { val admin = - RestrictedLockUtilsInternal.checkIfRestrictionEnforced( + userRestrictionChecker.checkIfRestrictionEnforced( context, UserManager.DISALLOW_ADD_USER, selectedUserId, @@ -113,7 +116,7 @@ object LegacyUserDataHelper { ?: return null return if ( - !RestrictedLockUtilsInternal.hasBaseUserRestriction( + !userRestrictionChecker.hasBaseUserRestriction( context, UserManager.DISALLOW_ADD_USER, selectedUserId, diff --git a/packages/SystemUI/src/com/android/systemui/utils/UserRestrictionChecker.kt b/packages/SystemUI/src/com/android/systemui/utils/UserRestrictionChecker.kt new file mode 100644 index 000000000000..3f8346b98d39 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/utils/UserRestrictionChecker.kt @@ -0,0 +1,25 @@ +package com.android.systemui.utils + +import android.content.Context +import com.android.settingslib.RestrictedLockUtils +import com.android.settingslib.RestrictedLockUtilsInternal +import javax.inject.Inject + +/** Proxy to call [RestrictedLockUtilsInternal] */ +class UserRestrictionChecker @Inject constructor() { + fun checkIfRestrictionEnforced( + context: Context, + userRestriction: String, + userId: Int + ): RestrictedLockUtils.EnforcedAdmin? { + return RestrictedLockUtilsInternal.checkIfRestrictionEnforced( + context, + userRestriction, + userId + ) + } + + fun hasBaseUserRestriction(context: Context, userRestriction: String, userId: Int): Boolean { + return RestrictedLockUtilsInternal.hasBaseUserRestriction(context, userRestriction, userId) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 727d649c8118..3c6d90dc08d2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -135,6 +135,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.AlphaTintDrawableWrapper; import com.android.systemui.util.RoundedCornerProgressDrawable; +import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; import java.util.ArrayList; @@ -304,6 +305,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private @DevicePostureController.DevicePostureInt int mDevicePosture; private int mOrientation; private final FeatureFlags mFeatureFlags; + private final SecureSettings mSecureSettings; + private int mDialogTimeoutMillis; public VolumeDialogImpl( Context context, @@ -320,7 +323,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, DevicePostureController devicePostureController, Looper looper, DumpManager dumpManager, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + SecureSettings secureSettings) { mFeatureFlags = featureFlags; mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); @@ -351,6 +355,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mUseBackgroundBlur = mContext.getResources().getBoolean(R.bool.config_volumeDialogUseBackgroundBlur); mInteractionJankMonitor = interactionJankMonitor; + mSecureSettings = secureSettings; + mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS; dumpManager.registerDumpable("VolumeDialogImpl", this); @@ -515,6 +521,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mDialog.setContentView(R.layout.volume_dialog); mDialogView = mDialog.findViewById(R.id.volume_dialog); mDialogView.setAlpha(0); + mDialogTimeoutMillis = mSecureSettings.getInt(Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, + DIALOG_TIMEOUT_MILLIS); mDialog.setCanceledOnTouchOutside(true); mDialog.setOnShowListener(dialog -> { mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); @@ -527,7 +535,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, .alpha(1) .translationX(0) .setDuration(mDialogShowAnimationDurationMs) - .setListener(getJankListener(getDialogView(), TYPE_SHOW, DIALOG_TIMEOUT_MILLIS)) + .setListener(getJankListener(getDialogView(), TYPE_SHOW, mDialogTimeoutMillis)) .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator()) .withEndAction(() -> { if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) { @@ -1514,7 +1522,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, AccessibilityManager.FLAG_CONTENT_TEXT | AccessibilityManager.FLAG_CONTENT_CONTROLS); } - return mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS, + return mAccessibilityMgr.getRecommendedTimeoutMillis(mDialogTimeoutMillis, AccessibilityManager.FLAG_CONTENT_CONTROLS); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index cc9f3e14216e..624691b19704 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -31,6 +31,7 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.volume.CsdWarningDialog; import com.android.systemui.volume.VolumeComponent; import com.android.systemui.volume.VolumeDialogComponent; @@ -63,7 +64,8 @@ public interface VolumeModule { CsdWarningDialog.Factory csdFactory, DevicePostureController devicePostureController, DumpManager dumpManager, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + SecureSettings secureSettings) { VolumeDialogImpl impl = new VolumeDialogImpl( context, volumeDialogController, @@ -79,7 +81,8 @@ public interface VolumeModule { devicePostureController, Looper.getMainLooper(), dumpManager, - featureFlags); + featureFlags, + secureSettings); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); impl.setSilentMode(false); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index f6649bdc9d3f..d54843d39d21 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -36,10 +36,8 @@ import com.android.internal.logging.UiEventLogger import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback import com.android.keyguard.KeyguardSecurityModel.SecurityMode -import com.android.systemui.res.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate import com.android.systemui.biometrics.SideFpsController import com.android.systemui.biometrics.SideFpsUiRequestSource @@ -47,6 +45,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants import com.android.systemui.classifier.FalsingA11yDelegate import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -54,6 +53,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.log.SessionTracker import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.ObservableTransitionState @@ -157,7 +157,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { private lateinit var sceneTestUtils: SceneTestUtils private lateinit var sceneInteractor: SceneInteractor private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor - private lateinit var authenticationInteractor: AuthenticationInteractor + private lateinit var deviceEntryInteractor: DeviceEntryInteractor @Mock private lateinit var primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor> private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState> @@ -229,10 +229,10 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { sceneTransitionStateFlow = MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen)) sceneInteractor.setTransitionState(sceneTransitionStateFlow) - authenticationInteractor = - sceneTestUtils.authenticationInteractor( - repository = sceneTestUtils.authenticationRepository(), - sceneInteractor = sceneInteractor + deviceEntryInteractor = + sceneTestUtils.deviceEntryInteractor( + authenticationInteractor = sceneTestUtils.authenticationInteractor(), + sceneInteractor = sceneInteractor, ) underTest = @@ -268,7 +268,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { keyguardTransitionInteractor, primaryBouncerInteractor, ) { - authenticationInteractor + deviceEntryInteractor } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index ba3dbf0a0cb7..484b1194eb8b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -20,8 +20,10 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.animation.LayoutTransition; import android.view.View; import android.view.ViewTreeObserver; +import android.widget.FrameLayout; import com.android.internal.jank.InteractionJankMonitor; import com.android.keyguard.logging.KeyguardLogger; @@ -44,6 +46,7 @@ import org.mockito.MockitoAnnotations; public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardStatusView mKeyguardStatusView; + @Mock protected KeyguardSliceViewController mKeyguardSliceViewController; @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController; @Mock protected KeyguardStateController mKeyguardStateController; @@ -61,6 +64,10 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { protected KeyguardStatusViewController mController; + @Mock protected KeyguardClockSwitch mKeyguardClockSwitch; + @Mock protected FrameLayout mMediaHostContainer; + @Mock protected LayoutTransition mMediaLayoutTransition; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -93,6 +100,8 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { }; when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver); + + when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch); } protected void givenViewAttached() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index b8b0198f94df..e4e2b0a8d89f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -18,14 +18,18 @@ package com.android.keyguard; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.animation.LayoutTransition; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.View; +import com.android.systemui.res.R; import com.android.systemui.plugins.ClockConfig; import com.android.systemui.plugins.ClockController; import com.android.systemui.statusbar.notification.AnimatableProperty; @@ -124,4 +128,112 @@ public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControll mController.onDestroy(); verify(mDumpManager, times(1)).unregisterDumpable(eq(mController.getInstanceName())); } + + @Test + public void onInit_addsOnLayoutChangeListenerToClockSwitch() { + when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn( + mMediaHostContainer); + + mController.onInit(); + + ArgumentCaptor<View.OnLayoutChangeListener> captor = + ArgumentCaptor.forClass(View.OnLayoutChangeListener.class); + verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture()); + } + + @Test + public void onInit_addsOnLayoutChangeListenerToMediaHostContainer() { + when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn( + mMediaHostContainer); + + mController.onInit(); + + ArgumentCaptor<View.OnLayoutChangeListener> captor = + ArgumentCaptor.forClass(View.OnLayoutChangeListener.class); + verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture()); + } + + @Test + public void clockSwitchHeightChanged_mediaChangingLayoutTransitionEnabled() { + when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn( + mMediaHostContainer); + + mController.onInit(); + + ArgumentCaptor<View.OnLayoutChangeListener> captor = + ArgumentCaptor.forClass(View.OnLayoutChangeListener.class); + verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture()); + + // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`. + // Below here is the actual test. + + View.OnLayoutChangeListener listener = captor.getValue(); + + mController.setSplitShadeEnabled(true); + when(mKeyguardClockSwitch.getSplitShadeCentered()).thenReturn(false); + when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true); + when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE); + when(mMediaHostContainer.getHeight()).thenReturn(200); + when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition); + + when(mKeyguardClockSwitch.getHeight()).thenReturn(0); + listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */ + 0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */ + 0, /* oldBottom = */ 200); + verify(mMediaLayoutTransition).enableTransitionType(LayoutTransition.CHANGING); + } + + @Test + public void clockSwitchHeightNotChanged_mediaChangingLayoutTransitionNotEnabled() { + when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn( + mMediaHostContainer); + + mController.onInit(); + + ArgumentCaptor<View.OnLayoutChangeListener> captor = + ArgumentCaptor.forClass(View.OnLayoutChangeListener.class); + verify(mKeyguardClockSwitch).addOnLayoutChangeListener(captor.capture()); + + // Above here is the same as `onInit_addsOnLayoutChangeListenerToClockSwitch`. + // Below here is the actual test. + + View.OnLayoutChangeListener listener = captor.getValue(); + + mController.setSplitShadeEnabled(true); + when(mKeyguardClockSwitch.getSplitShadeCentered()).thenReturn(false); + when(mKeyguardUpdateMonitor.isKeyguardVisible()).thenReturn(true); + when(mMediaHostContainer.getVisibility()).thenReturn(View.VISIBLE); + when(mMediaHostContainer.getHeight()).thenReturn(200); + when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition); + + when(mKeyguardClockSwitch.getHeight()).thenReturn(200); + listener.onLayoutChange(mKeyguardClockSwitch, /* left= */ 0, /* top= */ 0, /* right= */ + 0, /* bottom= */ 0, /* oldLeft= */ 0, /* oldTop= */ 0, /* oldRight= */ + 0, /* oldBottom = */ 200); + verify(mMediaLayoutTransition, never()).enableTransitionType(LayoutTransition.CHANGING); + } + + @Test + public void onMediaHostContainerLayout_disablesChangingLayoutTransition() { + when(mKeyguardStatusView.findViewById(R.id.status_view_media_container)).thenReturn( + mMediaHostContainer); + + mController.onInit(); + + ArgumentCaptor<View.OnLayoutChangeListener> captor = + ArgumentCaptor.forClass(View.OnLayoutChangeListener.class); + verify(mMediaHostContainer).addOnLayoutChangeListener(captor.capture()); + + // Above here is the same as `onInit_addsOnLayoutChangeListenerToMediaHostContainer`. + // Below here is the actual test. + + View.OnLayoutChangeListener listener = captor.getValue(); + + when(mMediaHostContainer.getLayoutTransition()).thenReturn(mMediaLayoutTransition); + + when(mMediaLayoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).thenReturn( + true); + listener.onLayoutChange(mMediaHostContainer, 1, 2, 3, 4, 1, 2, 3, 4); + verify(mMediaLayoutTransition).disableTransitionType(LayoutTransition.CHANGING); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt index 86439e557f8b..58d372c68c55 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt @@ -1,5 +1,6 @@ package com.android.keyguard +import android.animation.LayoutTransition import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper @@ -35,6 +36,20 @@ class KeyguardStatusViewTest : SysuiTestCase() { } @Test + fun mediaViewHasLayoutTransitionInDisabledState() { + val layoutTransition = (mediaView as ViewGroup).layoutTransition + assertThat(layoutTransition).isNotNull() + assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_APPEARING)) + .isFalse() + assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGE_DISAPPEARING)) + .isFalse() + assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.APPEARING)).isFalse() + assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.DISAPPEARING)) + .isFalse() + assertThat(layoutTransition.isTransitionTypeEnabled(LayoutTransition.CHANGING)).isFalse() + } + + @Test fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() { val translationY = 1234f diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 187f0986353e..b8d2bdbfa9c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -88,9 +88,9 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; +import com.android.systemui.res.R; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.util.leak.ReferenceTestUtils; import com.android.systemui.util.settings.SecureSettings; @@ -121,6 +121,9 @@ import java.util.concurrent.atomic.AtomicInteger; public class WindowMagnificationControllerTest extends SysuiTestCase { private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000; + // The duration couldn't too short, otherwise the animation check on bounce effect + // won't work in expectation. (b/299537784) + private static final int BOUNCE_EFFECT_DURATION_MS = 2000; private static final long ANIMATION_DURATION_MS = 300; private final long mWaitingAnimationPeriod = 2 * ANIMATION_DURATION_MS; @Mock @@ -205,6 +208,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { mSysUiState, () -> mWindowSessionSpy, mSecureSettings); + mWindowMagnificationController.setBounceEffectDuration(BOUNCE_EFFECT_DURATION_MS); verify(mMirrorWindowControl).setWindowDelegate( any(MirrorWindowControl.MirrorWindowDelegate.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index d3a2a73959dd..0283382d02be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt @@ -74,7 +74,6 @@ class AuthenticationRepositoryTest : SysuiTestCase() { getSecurityMode = getSecurityMode, backgroundDispatcher = testUtils.testDispatcher, userRepository = userRepository, - keyguardRepository = testUtils.keyguardRepository, lockPatternUtils = lockPatternUtils, broadcastDispatcher = fakeBroadcastDispatcher, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 874053a83654..a102890db7b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -20,15 +20,12 @@ import android.app.admin.DevicePolicyManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel -import com.android.systemui.authentication.data.repository.AuthenticationRepository import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -47,13 +44,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val repository: AuthenticationRepository = utils.authenticationRepository() - private val sceneInteractor = utils.sceneInteractor() - private val underTest = - utils.authenticationInteractor( - repository = repository, - sceneInteractor = sceneInteractor, - ) + private val underTest = utils.authenticationInteractor() @Test fun authenticationMethod() = @@ -79,10 +70,10 @@ class AuthenticationInteractorTest : SysuiTestCase() { val authMethod by collectLastValue(underTest.authenticationMethod) runCurrent() - utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.None) - setLockscreenEnabled(true) - } + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe) assertThat(underTest.getAuthenticationMethod()) @@ -95,10 +86,10 @@ class AuthenticationInteractorTest : SysuiTestCase() { val authMethod by collectLastValue(underTest.authenticationMethod) runCurrent() - utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.None) - setLockscreenEnabled(false) - } + utils.authenticationRepository.setAuthenticationMethod( + DataLayerAuthenticationMethodModel.None + ) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(false) assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None) assertThat(underTest.getAuthenticationMethod()) @@ -106,130 +97,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test - fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() = - testScope.runTest { - val isUnlocked by collectLastValue(underTest.isUnlocked) - utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.None) - setLockscreenEnabled(false) - // Toggle isUnlocked, twice. - // - // This is done because the underTest.isUnlocked flow doesn't receive values from - // just changing the state above; the actual isUnlocked state needs to change to - // cause the logic under test to "pick up" the current state again. - // - // It is done twice to make sure that we don't actually change the isUnlocked state - // from what it originally was. - setUnlocked(!utils.authenticationRepository.isUnlocked.value) - runCurrent() - setUnlocked(!utils.authenticationRepository.isUnlocked.value) - runCurrent() - } - - assertThat(isUnlocked).isTrue() - } - - @Test - fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() = - testScope.runTest { - utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.None) - setLockscreenEnabled(true) - } - - val isUnlocked by collectLastValue(underTest.isUnlocked) - assertThat(isUnlocked).isTrue() - } - - @Test - fun canSwipeToDismiss_onLockscreenWithSwipe_isTrue() = - testScope.runTest { - utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.None) - setLockscreenEnabled(true) - } - switchToScene(SceneKey.Lockscreen) - - val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss) - assertThat(canSwipeToDismiss).isTrue() - } - - @Test - fun canSwipeToDismiss_onLockscreenWithPin_isFalse() = - testScope.runTest { - utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) - setLockscreenEnabled(true) - } - switchToScene(SceneKey.Lockscreen) - - val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss) - assertThat(canSwipeToDismiss).isFalse() - } - - @Test - fun canSwipeToDismiss_afterLockscreenDismissedInSwipeMode_isFalse() = - testScope.runTest { - utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.None) - setLockscreenEnabled(true) - } - switchToScene(SceneKey.Lockscreen) - switchToScene(SceneKey.Gone) - - val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss) - assertThat(canSwipeToDismiss).isFalse() - } - - @Test - fun isAuthenticationRequired_lockedAndSecured_true() = - testScope.runTest { - utils.authenticationRepository.apply { - setUnlocked(false) - runCurrent() - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password) - } - - assertThat(underTest.isAuthenticationRequired()).isTrue() - } - - @Test - fun isAuthenticationRequired_lockedAndNotSecured_false() = - testScope.runTest { - utils.authenticationRepository.apply { - setUnlocked(false) - runCurrent() - setAuthenticationMethod(DataLayerAuthenticationMethodModel.None) - } - - assertThat(underTest.isAuthenticationRequired()).isFalse() - } - - @Test - fun isAuthenticationRequired_unlockedAndSecured_false() = - testScope.runTest { - utils.authenticationRepository.apply { - setUnlocked(true) - runCurrent() - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password) - } - - assertThat(underTest.isAuthenticationRequired()).isFalse() - } - - @Test - fun isAuthenticationRequired_unlockedAndNotSecured_false() = - testScope.runTest { - utils.authenticationRepository.apply { - setUnlocked(true) - runCurrent() - setAuthenticationMethod(DataLayerAuthenticationMethodModel.None) - } - - assertThat(underTest.isAuthenticationRequired()).isFalse() - } - - @Test fun authenticate_withCorrectPin_returnsTrue() = testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) @@ -366,7 +233,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isUnlocked) utils.authenticationRepository.apply { setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) setAutoConfirmEnabled(true) @@ -378,13 +244,13 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.FAILED) + val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) assertThat(isUnlocked).isFalse() } @Test fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isUnlocked) utils.authenticationRepository.apply { setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) setAutoConfirmEnabled(true) @@ -396,13 +262,13 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.FAILED) + val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) assertThat(isUnlocked).isFalse() } @Test fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isUnlocked) utils.authenticationRepository.apply { setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) setAutoConfirmEnabled(true) @@ -414,13 +280,13 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SUCCEEDED) + val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) assertThat(isUnlocked).isTrue() } @Test fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isUnlocked) utils.authenticationRepository.apply { setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) setAutoConfirmEnabled(false) @@ -432,26 +298,27 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SKIPPED) + val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) assertThat(isUnlocked).isFalse() } @Test fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isUnlocked) utils.authenticationRepository.setAuthenticationMethod( DataLayerAuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)) .isEqualTo(AuthenticationResult.SKIPPED) + val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) assertThat(isUnlocked).isFalse() } @Test fun throttling() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isUnlocked) + val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) val throttling by collectLastValue(underTest.throttling) val isThrottled by collectLastValue(underTest.isThrottled) utils.authenticationRepository.setAuthenticationMethod( @@ -462,7 +329,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) assertThat(isUnlocked).isFalse() assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) @@ -605,8 +472,4 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertThat(hintedPinLength).isNull() } - - private fun switchToScene(sceneKey: SceneKey) { - sceneInteractor.changeScene(SceneModel(sceneKey), "reason") - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt index 8fc63b246c9d..73654609ad7f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt @@ -44,6 +44,7 @@ import com.android.systemui.user.domain.interactor.GuestUserInteractor import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode import com.android.systemui.user.domain.interactor.RefreshUsersScheduler import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.util.mockito.mock import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -126,6 +127,7 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = guestInteractor, uiEventLogger = uiEventLogger, + userRestrictionChecker = mock(), ) shadeInteractor = ShadeInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index c0257df08949..a9ba36a9fde4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -47,13 +47,16 @@ class BouncerInteractorTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val authenticationInteractor = - utils.authenticationInteractor( - repository = utils.authenticationRepository(), - ) + private val authenticationInteractor = utils.authenticationInteractor() private val sceneInteractor = utils.sceneInteractor() + private val deviceEntryInteractor = + utils.deviceEntryInteractor( + authenticationInteractor = authenticationInteractor, + sceneInteractor = sceneInteractor, + ) private val underTest = utils.bouncerInteractor( + deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ) @@ -76,7 +79,7 @@ class BouncerInteractorTest : SysuiTestCase() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) @@ -111,7 +114,7 @@ class BouncerInteractorTest : SysuiTestCase() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() utils.authenticationRepository.setAutoConfirmEnabled(true) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) @@ -148,7 +151,7 @@ class BouncerInteractorTest : SysuiTestCase() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.clearMessage() @@ -180,7 +183,7 @@ class BouncerInteractorTest : SysuiTestCase() { AuthenticationMethodModel.Password ) runCurrent() - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) @@ -215,7 +218,7 @@ class BouncerInteractorTest : SysuiTestCase() { AuthenticationMethodModel.Pattern ) runCurrent() - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) underTest.showOrUnlockDevice() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) @@ -268,7 +271,7 @@ class BouncerInteractorTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(true) + utils.deviceEntryRepository.setUnlocked(true) runCurrent() underTest.showOrUnlockDevice() @@ -281,8 +284,8 @@ class BouncerInteractorTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.authenticationRepository.setLockscreenEnabled(true) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setUnlocked(false) underTest.showOrUnlockDevice() @@ -298,7 +301,7 @@ class BouncerInteractorTest : SysuiTestCase() { AuthenticationMethodModel.Password ) runCurrent() - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) val customMessage = "Hello there!" underTest.showOrUnlockDevice(customMessage) @@ -395,7 +398,7 @@ class BouncerInteractorTest : SysuiTestCase() { val bouncerSceneKey = currentScene?.key assertThat(bouncerSceneKey).isEqualTo(SceneKey.Bouncer) - underTest.hide("") + underTest.onImeHidden() assertThat(currentScene?.key).isEqualTo(SceneKey.Lockscreen) } @@ -409,7 +412,7 @@ class BouncerInteractorTest : SysuiTestCase() { val notBouncerSceneKey = currentScene?.key assertThat(notBouncerSceneKey).isNotEqualTo(SceneKey.Bouncer) - underTest.hide("") + underTest.onImeHidden() assertThat(currentScene?.key).isEqualTo(notBouncerSceneKey) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 2f7dde02fdce..b5177e1587ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -37,17 +37,20 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val authenticationInteractor = - utils.authenticationInteractor( - utils.authenticationRepository(), - ) + private val authenticationInteractor = utils.authenticationInteractor() private val sceneInteractor = utils.sceneInteractor() + private val deviceEntryInteractor = + utils.deviceEntryInteractor( + authenticationInteractor = authenticationInteractor, + sceneInteractor = sceneInteractor, + ) private val underTest = PinBouncerViewModel( applicationContext = context, viewModelScope = testScope.backgroundScope, interactor = utils.bouncerInteractor( + deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ), diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index da2534d6fb14..b75355a82406 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -44,12 +44,15 @@ class BouncerViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val authenticationInteractor = - utils.authenticationInteractor( - repository = utils.authenticationRepository, + private val authenticationInteractor = utils.authenticationInteractor() + private val deviceEntryInteractor = + utils.deviceEntryInteractor( + authenticationInteractor = authenticationInteractor, + sceneInteractor = utils.sceneInteractor(), ) private val bouncerInteractor = utils.bouncerInteractor( + deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, sceneInteractor = utils.sceneInteractor(), ) @@ -223,6 +226,8 @@ class BouncerViewModelTest : SysuiTestCase() { DataLayerAuthenticationMethodModel.Pattern } ) - setLockscreenEnabled(model !is DomainLayerAuthenticationMethodModel.None) + utils.deviceEntryRepository.setInsecureLockscreenEnabled( + model !is DomainLayerAuthenticationMethodModel.None + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index c1b33542267b..0926399a8617 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -44,13 +44,16 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val authenticationInteractor = - utils.authenticationInteractor( - repository = utils.authenticationRepository(), - ) + private val authenticationInteractor = utils.authenticationInteractor() private val sceneInteractor = utils.sceneInteractor() + private val deviceEntryInteractor = + utils.deviceEntryInteractor( + authenticationInteractor = authenticationInteractor, + sceneInteractor = utils.sceneInteractor(), + ) private val bouncerInteractor = utils.bouncerInteractor( + deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ) @@ -139,7 +142,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) @@ -207,7 +210,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private fun TestScope.lockDeviceAndOpenPasswordBouncer() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index bf109d9b2b61..2e7c9aaa67e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -47,13 +47,16 @@ class PatternBouncerViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val authenticationInteractor = - utils.authenticationInteractor( - repository = utils.authenticationRepository(), - ) + private val authenticationInteractor = utils.authenticationInteractor() private val sceneInteractor = utils.sceneInteractor() + private val deviceEntryInteractor = + utils.deviceEntryInteractor( + authenticationInteractor = authenticationInteractor, + sceneInteractor = utils.sceneInteractor(), + ) private val bouncerInteractor = utils.bouncerInteractor( + deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ) @@ -378,7 +381,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { private fun TestScope.lockDeviceAndOpenPatternBouncer() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") assertThat(collectLastValue(sceneInteractor.desiredScene).invoke()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 2576204c247f..255bbe3ae231 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -47,12 +47,15 @@ class PinBouncerViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = - utils.authenticationInteractor( - repository = utils.authenticationRepository(), + private val authenticationInteractor = utils.authenticationInteractor() + private val deviceEntryInteractor = + utils.deviceEntryInteractor( + authenticationInteractor = authenticationInteractor, + sceneInteractor = utils.sceneInteractor(), ) private val bouncerInteractor = utils.bouncerInteractor( + deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ) @@ -81,7 +84,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") @@ -103,7 +106,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") @@ -358,7 +361,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { private fun TestScope.lockDeviceAndOpenPinBouncer() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt index 9260f63c1195..617e3101f403 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt @@ -60,7 +60,7 @@ class PackageUpdateMonitorTest : SysuiTestCase() { underTest.startMonitoring() // There are two receivers registered - verify(context, times(2)) + verify(context, times(1)) .registerReceiverAsUser(any(), eq(USER), any(), eq(null), eq(bgHandler)) verify(packageManager).registerPackageMonitorCallback(any(), eq(USER.getIdentifier())) // context will be used to get PackageManager, the test should clear invocations diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt index 54142590b453..677108cab291 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt @@ -18,10 +18,13 @@ package com.android.systemui.controls.ui import android.app.ActivityOptions import android.app.PendingIntent +import android.content.Context import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.activity.EmptyTestActivity import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.policy.KeyguardStateController @@ -30,11 +33,13 @@ import com.android.systemui.util.mockito.capture import com.android.wm.shell.taskview.TaskView import com.google.common.truth.Truth.assertThat import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.any +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -43,31 +48,31 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper class DetailDialogTest : SysuiTestCase() { - @Mock - private lateinit var taskView: TaskView - @Mock - private lateinit var broadcastSender: BroadcastSender - @Mock - private lateinit var controlViewHolder: ControlViewHolder - @Mock - private lateinit var pendingIntent: PendingIntent - @Mock - private lateinit var keyguardStateController: KeyguardStateController - @Mock - private lateinit var activityStarter: ActivityStarter + @Rule + @JvmField + val activityRule: ActivityScenarioRule<EmptyTestActivity> = + ActivityScenarioRule(EmptyTestActivity::class.java) + + @Mock private lateinit var taskView: TaskView + @Mock private lateinit var broadcastSender: BroadcastSender + @Mock private lateinit var controlViewHolder: ControlViewHolder + @Mock private lateinit var pendingIntent: PendingIntent + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var activityStarter: ActivityStarter + + private lateinit var underTest: DetailDialog @Before fun setUp() { MockitoAnnotations.initMocks(this) + + underTest = createDialog(pendingIntent) } @Test fun testPendingIntentIsUnModified() { - // GIVEN the dialog is created with a PendingIntent - val dialog = createDialog(pendingIntent) - // WHEN the TaskView is initialized - dialog.stateCallback.onInitialized() + underTest.stateCallback.onInitialized() // THEN the PendingIntent used to call startActivity is unmodified by systemui verify(taskView).startActivity(eq(pendingIntent), any(), any(), any()) @@ -75,11 +80,8 @@ class DetailDialogTest : SysuiTestCase() { @Test fun testActivityOptionsAllowBal() { - // GIVEN the dialog is created with a PendingIntent - val dialog = createDialog(pendingIntent) - // WHEN the TaskView is initialized - dialog.stateCallback.onInitialized() + underTest.stateCallback.onInitialized() val optionsCaptor = argumentCaptor<ActivityOptions>() @@ -90,17 +92,41 @@ class DetailDialogTest : SysuiTestCase() { .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) assertThat(optionsCaptor.value.isPendingIntentBackgroundActivityLaunchAllowedByPermission) .isTrue() + assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue() + } + + @Test + fun testDismissRemovesTheTask() { + activityRule.scenario.onActivity { + underTest = createDialog(pendingIntent, it) + underTest.show() + + underTest.dismiss() + + verify(taskView).removeTask() + verify(taskView, never()).release() + } + } + + @Test + fun testTaskRemovalReleasesTaskView() { + underTest.stateCallback.onTaskRemovalStarted(0) + + verify(taskView).release() } - private fun createDialog(pendingIntent: PendingIntent): DetailDialog { + private fun createDialog( + pendingIntent: PendingIntent, + context: Context = mContext, + ): DetailDialog { return DetailDialog( - mContext, + context, broadcastSender, taskView, pendingIntent, controlViewHolder, keyguardStateController, - activityStarter + activityStarter, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt new file mode 100644 index 000000000000..8e8cbe4a7cf2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt @@ -0,0 +1,127 @@ +package com.android.systemui.deviceentry.data.repository + +import android.content.pm.UserInfo +import androidx.test.filters.SmallTest +import com.android.internal.widget.LockPatternUtils +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class DeviceEntryRepositoryTest : SysuiTestCase() { + + @Mock private lateinit var lockPatternUtils: LockPatternUtils + @Mock private lateinit var keyguardBypassController: KeyguardBypassController + @Mock private lateinit var keyguardStateController: KeyguardStateController + + private val testUtils = SceneTestUtils(this) + private val testScope = testUtils.testScope + private val userRepository = FakeUserRepository() + + private lateinit var underTest: DeviceEntryRepository + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + userRepository.setUserInfos(USER_INFOS) + runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) } + + underTest = + DeviceEntryRepositoryImpl( + applicationScope = testScope.backgroundScope, + backgroundDispatcher = testUtils.testDispatcher, + userRepository = userRepository, + lockPatternUtils = lockPatternUtils, + keyguardBypassController = keyguardBypassController, + keyguardStateController = keyguardStateController, + ) + } + + @Test + fun isUnlocked() = + testScope.runTest { + whenever(keyguardStateController.isUnlocked).thenReturn(false) + val isUnlocked by collectLastValue(underTest.isUnlocked) + + runCurrent() + assertThat(isUnlocked).isFalse() + + val captor = argumentCaptor<KeyguardStateController.Callback>() + Mockito.verify(keyguardStateController, Mockito.atLeastOnce()) + .addCallback(captor.capture()) + + whenever(keyguardStateController.isUnlocked).thenReturn(true) + captor.value.onUnlockedChanged() + runCurrent() + assertThat(isUnlocked).isTrue() + + whenever(keyguardStateController.isUnlocked).thenReturn(false) + captor.value.onKeyguardShowingChanged() + runCurrent() + assertThat(isUnlocked).isFalse() + } + + @Test + fun isInsecureLockscreenEnabled() = + testScope.runTest { + whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[0].id)).thenReturn(false) + whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[1].id)).thenReturn(true) + + userRepository.setSelectedUserInfo(USER_INFOS[0]) + assertThat(underTest.isInsecureLockscreenEnabled()).isTrue() + + userRepository.setSelectedUserInfo(USER_INFOS[1]) + assertThat(underTest.isInsecureLockscreenEnabled()).isFalse() + } + + @Test + fun isBypassEnabled_disabledInController() = + testScope.runTest { + whenever(keyguardBypassController.isBypassEnabled).thenAnswer { false } + whenever(keyguardBypassController.bypassEnabled).thenAnswer { false } + assertThat(underTest.isBypassEnabled()).isFalse() + } + + @Test + fun isBypassEnabled_enabledInController() = + testScope.runTest { + whenever(keyguardBypassController.isBypassEnabled).thenAnswer { true } + whenever(keyguardBypassController.bypassEnabled).thenAnswer { true } + assertThat(underTest.isBypassEnabled()).isTrue() + } + + companion object { + private val USER_INFOS = + listOf( + UserInfo( + /* id= */ 100, + /* name= */ "First user", + /* flags= */ 0, + ), + UserInfo( + /* id= */ 101, + /* name= */ "Second user", + /* flags= */ 0, + ), + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt new file mode 100644 index 000000000000..55582e123969 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -0,0 +1,234 @@ +package com.android.systemui.deviceentry.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository +import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class DeviceEntryInteractorTest : SysuiTestCase() { + + private val utils = SceneTestUtils(this) + private val testScope = utils.testScope + private val repository: FakeDeviceEntryRepository = utils.deviceEntryRepository + private val sceneInteractor = utils.sceneInteractor() + private val authenticationInteractor = utils.authenticationInteractor() + private val underTest = + utils.deviceEntryInteractor( + repository = repository, + authenticationInteractor = authenticationInteractor, + sceneInteractor = sceneInteractor, + ) + + @Test + fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.deviceEntryRepository.apply { + setInsecureLockscreenEnabled(false) + + // Toggle isUnlocked, twice. + // + // This is done because the underTest.isUnlocked flow doesn't receive values from + // just changing the state above; the actual isUnlocked state needs to change to + // cause the logic under test to "pick up" the current state again. + // + // It is done twice to make sure that we don't actually change the isUnlocked state + // from what it originally was. + setUnlocked(!isUnlocked.value) + runCurrent() + setUnlocked(!isUnlocked.value) + runCurrent() + } + + val isUnlocked by collectLastValue(underTest.isUnlocked) + assertThat(isUnlocked).isTrue() + } + + @Test + fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + + val isUnlocked by collectLastValue(underTest.isUnlocked) + assertThat(isUnlocked).isTrue() + } + + @Test + fun isDeviceEntered_onLockscreenWithSwipe_isFalse() = + testScope.runTest { + val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + switchToScene(SceneKey.Lockscreen) + + assertThat(isDeviceEntered).isFalse() + } + + @Test + fun isDeviceEntered_onShadeBeforeDismissingLockscreenWithSwipe_isFalse() = + testScope.runTest { + val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + switchToScene(SceneKey.Lockscreen) + runCurrent() + switchToScene(SceneKey.Shade) + + assertThat(isDeviceEntered).isFalse() + } + + @Test + fun isDeviceEntered_afterDismissingLockscreenWithSwipe_isTrue() = + testScope.runTest { + val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + switchToScene(SceneKey.Lockscreen) + runCurrent() + switchToScene(SceneKey.Gone) + + assertThat(isDeviceEntered).isTrue() + } + + @Test + fun isDeviceEntered_onShadeAfterDismissingLockscreenWithSwipe_isTrue() = + testScope.runTest { + val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + switchToScene(SceneKey.Lockscreen) + runCurrent() + switchToScene(SceneKey.Gone) + runCurrent() + switchToScene(SceneKey.Shade) + + assertThat(isDeviceEntered).isTrue() + } + + @Test + fun isDeviceEntered_onBouncer_isFalse() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pattern + ) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + switchToScene(SceneKey.Lockscreen) + runCurrent() + switchToScene(SceneKey.Bouncer) + + val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) + assertThat(isDeviceEntered).isFalse() + } + + @Test + fun canSwipeToEnter_onLockscreenWithSwipe_isTrue() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + switchToScene(SceneKey.Lockscreen) + + val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) + assertThat(canSwipeToEnter).isTrue() + } + + @Test + fun canSwipeToEnter_onLockscreenWithPin_isFalse() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + switchToScene(SceneKey.Lockscreen) + + val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) + assertThat(canSwipeToEnter).isFalse() + } + + @Test + fun canSwipeToEnter_afterLockscreenDismissedInSwipeMode_isFalse() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + switchToScene(SceneKey.Lockscreen) + runCurrent() + switchToScene(SceneKey.Gone) + + val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) + assertThat(canSwipeToEnter).isFalse() + } + + @Test + fun isAuthenticationRequired_lockedAndSecured_true() = + testScope.runTest { + utils.deviceEntryRepository.setUnlocked(false) + runCurrent() + utils.authenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Password + ) + + assertThat(underTest.isAuthenticationRequired()).isTrue() + } + + @Test + fun isAuthenticationRequired_lockedAndNotSecured_false() = + testScope.runTest { + utils.deviceEntryRepository.setUnlocked(false) + runCurrent() + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + + assertThat(underTest.isAuthenticationRequired()).isFalse() + } + + @Test + fun isAuthenticationRequired_unlockedAndSecured_false() = + testScope.runTest { + utils.deviceEntryRepository.setUnlocked(true) + runCurrent() + utils.authenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Password + ) + + assertThat(underTest.isAuthenticationRequired()).isFalse() + } + + @Test + fun isAuthenticationRequired_unlockedAndNotSecured_false() = + testScope.runTest { + utils.deviceEntryRepository.setUnlocked(true) + runCurrent() + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + + assertThat(underTest.isAuthenticationRequired()).isFalse() + } + + @Test + fun isBypassEnabled_enabledInRepository_true() = + testScope.runTest { + utils.deviceEntryRepository.setBypassEnabled(true) + assertThat(underTest.isBypassEnabled()).isTrue() + } + + @Test + fun isBypassEnabled_disabledInRepository_false() = + testScope.runTest { + utils.deviceEntryRepository.setBypassEnabled(false) + assertThat(underTest.isBypassEnabled()).isFalse() + } + + private fun switchToScene(sceneKey: SceneKey) { + sceneInteractor.changeScene(SceneModel(sceneKey), "reason") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt index add601c4b1b3..8d12e491ad11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderTrackerTest.kt @@ -158,8 +158,8 @@ class SeekableSliderTrackerTest : SysuiTestCase() { var progress = 0.5f sliderEventProducer.sendEvent(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress)) - // GIVEN a progress event due to a touch on the slider track at threshold - progress += config.jumpThreshold + // GIVEN a progress event due to a touch on the slider track beyond threshold + progress += (config.jumpThreshold + 0.01f) sliderEventProducer.sendEvent( SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, progress) ) @@ -191,7 +191,6 @@ class SeekableSliderTrackerTest : SysuiTestCase() { // THEN the tracker moves to the jump-track location selected state assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.JUMP_BOOKEND_SELECTED) assertThat(mSeekableSliderTracker.isWaiting).isFalse() - verify(sliderStateListener).onUpperBookend() verifyNoMoreInteractions(sliderStateListener) } @@ -214,7 +213,6 @@ class SeekableSliderTrackerTest : SysuiTestCase() { // THEN the tracker moves to the JUMP_TRACK_LOCATION_SELECTED state assertThat(mSeekableSliderTracker.currentState).isEqualTo(SliderState.JUMP_BOOKEND_SELECTED) assertThat(mSeekableSliderTracker.isWaiting).isFalse() - verify(sliderStateListener).onLowerBookend() verifyNoMoreInteractions(sliderStateListener) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index 9a2936e227f8..7a13a0a96ee6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -102,7 +102,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { notificationShadeWindowController, powerManager, wallpaperManager ) keyguardUnlockAnimationController.setLauncherUnlockController( - launcherUnlockAnimationController) + "", launcherUnlockAnimationController) whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java)) whenever(powerManager.isInteractive).thenReturn(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 9d96ac736b5c..291dda20fdc3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -104,6 +104,7 @@ import com.android.systemui.shade.NotificationShadeWindowControllerImpl; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeWindowLogger; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -185,6 +186,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock SysuiColorExtractor mColorExtractor; private @Mock AuthController mAuthController; private @Mock ShadeExpansionStateManager mShadeExpansionStateManager; + private @Mock ShadeInteractor mShadeInteractor; private @Mock ShadeWindowLogger mShadeWindowLogger; private @Captor ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateControllerCallback; @@ -249,6 +251,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager, + () -> mShadeInteractor, mShadeWindowLogger); mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index 360fa5652e20..944b059450c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -32,7 +32,6 @@ import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUT import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN -import com.android.systemui.res.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController @@ -48,6 +47,10 @@ import com.android.systemui.keyguard.data.repository.BiometricType.REAR_FINGERPR import com.android.systemui.keyguard.data.repository.BiometricType.SIDE_FINGERPRINT import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT import com.android.systemui.keyguard.shared.model.DevicePosture +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.res.R +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.eq @@ -87,6 +90,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var biometricManager: BiometricManager + @Mock private lateinit var tableLogger: TableLogBuffer @Captor private lateinit var strongAuthTracker: ArgumentCaptor<LockPatternUtils.StrongAuthTracker> @Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback> @@ -97,6 +101,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { private lateinit var devicePostureRepository: FakeDevicePostureRepository private lateinit var facePropertyRepository: FakeFacePropertyRepository private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository private lateinit var testDispatcher: TestDispatcher private lateinit var testScope: TestScope @@ -112,6 +117,8 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { devicePostureRepository = FakeDevicePostureRepository() facePropertyRepository = FakeFacePropertyRepository() fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + mobileConnectionsRepository = + FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger) } private suspend fun createBiometricSettingsRepository() { @@ -132,6 +139,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { dumpManager = dumpManager, facePropertyRepository = facePropertyRepository, fingerprintPropertyRepository = fingerprintPropertyRepository, + mobileConnectionsRepository = mobileConnectionsRepository, ) testScope.runCurrent() fingerprintPropertyRepository.setProperties( @@ -421,6 +429,50 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { } @Test + fun anySimSecure_disablesFaceAuth() = + testScope.runTest { + faceAuthIsEnrolled() + createBiometricSettingsRepository() + + faceAuthIsEnabledByBiometricManager() + doNotDisableKeyguardAuthFeatures() + mobileConnectionsRepository.isAnySimSecure.value = false + runCurrent() + + val isFaceAuthEnabledAndEnrolled by + collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) + + assertThat(isFaceAuthEnabledAndEnrolled).isTrue() + + mobileConnectionsRepository.isAnySimSecure.value = true + runCurrent() + + assertThat(isFaceAuthEnabledAndEnrolled).isFalse() + } + + @Test + fun anySimSecure_disablesFaceAuthToNotCurrentlyRun() = + testScope.runTest { + faceAuthIsEnrolled() + + createBiometricSettingsRepository() + val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed) + + deviceIsInPostureThatSupportsFaceAuth() + doNotDisableKeyguardAuthFeatures() + faceAuthIsStrongBiometric() + faceAuthIsEnabledByBiometricManager() + mobileConnectionsRepository.isAnySimSecure.value = false + + onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID) + onNonStrongAuthChanged(false, PRIMARY_USER_ID) + assertThat(isFaceAuthCurrentlyAllowed).isTrue() + + mobileConnectionsRepository.isAnySimSecure.value = true + assertThat(isFaceAuthCurrentlyAllowed).isFalse() + } + + @Test fun biometricManagerControlsFaceAuthenticationEnabledStatus() = testScope.runTest { faceAuthIsEnrolled() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 2691860bc25b..f93051c41b61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -44,7 +44,6 @@ import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.DozeParameters -import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -81,7 +80,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var dozeTransitionListener: DozeTransitionListener @Mock private lateinit var authController: AuthController - @Mock private lateinit var keyguardBypassController: KeyguardBypassController @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController @Mock private lateinit var dozeParameters: DozeParameters @@ -103,7 +101,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { screenLifecycle, biometricUnlockController, keyguardStateController, - keyguardBypassController, keyguardUpdateMonitor, dozeTransitionListener, dozeParameters, @@ -213,23 +210,9 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test - fun isBypassEnabled_disabledInController() { - whenever(keyguardBypassController.isBypassEnabled).thenReturn(false) - whenever(keyguardBypassController.bypassEnabled).thenReturn(false) - assertThat(underTest.isBypassEnabled()).isFalse() - } - - @Test - fun isBypassEnabled_enabledInController() { - whenever(keyguardBypassController.isBypassEnabled).thenReturn(true) - whenever(keyguardBypassController.bypassEnabled).thenReturn(true) - assertThat(underTest.isBypassEnabled()).isTrue() - } - - @Test fun isAodAvailable() = runTest { val flow = underTest.isAodAvailable - var isAodAvailable = collectLastValue(flow) + val isAodAvailable = collectLastValue(flow) runCurrent() val callback = @@ -273,29 +256,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test - fun isKeyguardUnlocked() = - testScope.runTest { - whenever(keyguardStateController.isUnlocked).thenReturn(false) - val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked) - - runCurrent() - assertThat(isKeyguardUnlocked).isFalse() - - val captor = argumentCaptor<KeyguardStateController.Callback>() - verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture()) - - whenever(keyguardStateController.isUnlocked).thenReturn(true) - captor.value.onUnlockedChanged() - runCurrent() - assertThat(isKeyguardUnlocked).isTrue() - - whenever(keyguardStateController.isUnlocked).thenReturn(false) - captor.value.onKeyguardShowingChanged() - runCurrent() - assertThat(isKeyguardUnlocked).isFalse() - } - - @Test fun isDozing() = testScope.runTest { underTest.setIsDozing(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 90e217f3418c..82c7fa4c4d12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -15,8 +15,6 @@ * */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package com.android.systemui.keyguard.domain.interactor import android.app.StatusBarManager @@ -27,13 +25,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.data.repository.FakeCommandQueue -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.shade.data.repository.FakeShadeRepository @@ -42,62 +38,53 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.onCompletion -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 -import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RoboPilotTest @RunWith(AndroidJUnit4::class) class KeyguardInteractorTest : SysuiTestCase() { - private lateinit var commandQueue: FakeCommandQueue - private lateinit var featureFlags: FakeFeatureFlags - private lateinit var testScope: TestScope - - private lateinit var underTest: KeyguardInteractor - private lateinit var repository: FakeKeyguardRepository - private lateinit var bouncerRepository: FakeKeyguardBouncerRepository - private lateinit var configurationRepository: FakeConfigurationRepository - private lateinit var shadeRepository: FakeShadeRepository - private lateinit var sceneInteractor: SceneInteractor - private lateinit var transitionState: MutableStateFlow<ObservableTransitionState> + + private val testUtils = SceneTestUtils(this) + private val testScope = testUtils.testScope + private val repository = testUtils.keyguardRepository + private val sceneInteractor = testUtils.sceneInteractor() + private val commandQueue = FakeCommandQueue() + private val featureFlags = FakeFeatureFlagsClassic().apply { set(FACE_AUTH_REFACTOR, true) } + private val bouncerRepository = FakeKeyguardBouncerRepository() + private val configurationRepository = FakeConfigurationRepository() + private val shadeRepository = FakeShadeRepository() + private val transitionState: MutableStateFlow<ObservableTransitionState> = + MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone)) + + private val underTest = + KeyguardInteractor( + repository = repository, + commandQueue = commandQueue, + featureFlags = featureFlags, + sceneContainerFlags = testUtils.sceneContainerFlags, + deviceEntryRepository = testUtils.deviceEntryRepository, + bouncerRepository = bouncerRepository, + configurationRepository = configurationRepository, + shadeRepository = shadeRepository, + sceneInteractorProvider = { sceneInteractor }, + ) @Before fun setUp() { - MockitoAnnotations.initMocks(this) - featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) } - commandQueue = FakeCommandQueue() - val sceneTestUtils = SceneTestUtils(this) - testScope = sceneTestUtils.testScope - repository = sceneTestUtils.keyguardRepository - bouncerRepository = FakeKeyguardBouncerRepository() - configurationRepository = FakeConfigurationRepository() - shadeRepository = FakeShadeRepository() - sceneInteractor = sceneTestUtils.sceneInteractor() - transitionState = MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone)) sceneInteractor.setTransitionState(transitionState) - underTest = - KeyguardInteractor( - repository = repository, - commandQueue = commandQueue, - featureFlags = featureFlags, - sceneContainerFlags = sceneTestUtils.sceneContainerFlags, - bouncerRepository = bouncerRepository, - configurationRepository = configurationRepository, - shadeRepository = shadeRepository, - sceneInteractorProvider = { sceneInteractor }, - ) } @Test fun onCameraLaunchDetected() = testScope.runTest { val flow = underTest.onCameraLaunchDetected - var cameraLaunchSource = collectLastValue(flow) + val cameraLaunchSource = collectLastValue(flow) runCurrent() commandQueue.doForEachCallback { @@ -175,7 +162,7 @@ class KeyguardInteractorTest : SysuiTestCase() { @Test fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest { - var isVisible = collectLastValue(underTest.isKeyguardVisible) + val isVisible = collectLastValue(underTest.isKeyguardVisible) repository.setKeyguardShowing(true) repository.setKeyguardOccluded(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index f2636c543844..591653ee214f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -1226,7 +1226,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN the keyguard is showing locked keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - keyguardRepository.setKeyguardUnlocked(false) runCurrent() shadeRepository.setShadeModel( ShadeModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 1681cfdce822..6e94691d42a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -37,10 +37,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = - utils.authenticationInteractor( - repository = utils.authenticationRepository(), - ) private val underTest = createLockscreenSceneViewModel() @@ -49,8 +45,8 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.authenticationRepository.setLockscreenEnabled(true) - utils.authenticationRepository.setUnlocked(true) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setUnlocked(true) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) @@ -61,7 +57,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer) @@ -88,7 +84,11 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel { return LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, - authenticationInteractor = authenticationInteractor, + deviceEntryInteractor = + utils.deviceEntryInteractor( + authenticationInteractor = utils.authenticationInteractor(), + sceneInteractor = utils.sceneInteractor(), + ), communalInteractor = utils.communalInteractor(), longPress = KeyguardLongPressViewModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index 25faeef85e50..de57b603c7fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -81,6 +81,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Mock private lateinit var mediaDataManager: MediaDataManager @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController + @Mock lateinit var logger: MediaViewLogger @Captor private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)> @Captor @@ -121,7 +122,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() { notifPanelEvents, settings, fakeHandler, - ResourcesSplitShadeStateController() + ResourcesSplitShadeStateController(), + logger, ) verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) verify(statusBarStateController).addCallback(statusBarCallback.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt index 93f316e9a619..9e5d3bdb179c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDisableFlagsLoggerTest.kt @@ -29,15 +29,16 @@ import org.junit.Test import org.mockito.Mockito.mock @SmallTest -class QSFragmentDisableFlagsLoggerTest : SysuiTestCase() { - - private val buffer = LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)) - .create("buffer", 10) - private val disableFlagsLogger = DisableFlagsLogger( - listOf(DisableFlagsLogger.DisableFlag(0b001, 'A', 'a')), - listOf(DisableFlagsLogger.DisableFlag(0b001, 'B', 'b')) - ) - private val logger = QSFragmentDisableFlagsLogger(buffer, disableFlagsLogger) +class QSDisableFlagsLoggerTest : SysuiTestCase() { + + private val buffer = + LogBufferFactory(DumpManager(), mock(LogcatEchoTracker::class.java)).create("buffer", 10) + private val disableFlagsLogger = + DisableFlagsLogger( + listOf(DisableFlagsLogger.DisableFlag(0b001, 'A', 'a')), + listOf(DisableFlagsLogger.DisableFlag(0b001, 'B', 'b')) + ) + private val logger = QSDisableFlagsLogger(buffer, disableFlagsLogger) @Test fun logDisableFlagChange_bufferHasStates() { @@ -48,9 +49,8 @@ class QSFragmentDisableFlagsLoggerTest : SysuiTestCase() { val stringWriter = StringWriter() buffer.dump(PrintWriter(stringWriter), tailLength = 0) val actualString = stringWriter.toString() - val expectedLogString = disableFlagsLogger.getDisableFlagsString( - new = state, newAfterLocalModification = state - ) + val expectedLogString = + disableFlagsLogger.getDisableFlagsString(new = state, newAfterLocalModification = state) assertThat(actualString).contains(expectedLogString) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java index c4c233ca5eef..d57765c3ecf2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java @@ -1,15 +1,17 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package com.android.systemui.qs; @@ -34,7 +36,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.Nullable; -import android.app.Fragment; import android.content.Context; import android.graphics.Rect; import android.os.Bundle; @@ -49,12 +50,12 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.systemui.res.R; -import com.android.systemui.SysuiBaseFragmentTest; +import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.media.controls.ui.MediaHost; import com.android.systemui.qs.customize.QSCustomizerController; -import com.android.systemui.qs.dagger.QSFragmentComponent; +import com.android.systemui.qs.dagger.QSComponent; import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; @@ -66,8 +67,8 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.util.animation.UniqueObjectHostView; import org.junit.Before; @@ -79,10 +80,9 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) @SmallTest -public class QSFragmentTest extends SysuiBaseFragmentTest { +public class QSImplTest extends SysuiTestCase { - @Mock private QSFragmentComponent.Factory mQsComponentFactory; - @Mock private QSFragmentComponent mQsFragmentComponent; + @Mock private QSComponent mQsComponent; @Mock private QSPanelController mQSPanelController; @Mock private MediaHost mQSMediaHost; @Mock private MediaHost mQQSMediaHost; @@ -107,69 +107,54 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory; @Mock private FooterActionsViewBinder mFooterActionsViewBinder; @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; - @Mock private FeatureFlags mFeatureFlags; - private View mQsFragmentView; + @Mock private FeatureFlagsClassic mFeatureFlags; + private View mQsView; + + private QSImpl mUnderTest; - public QSFragmentTest() { - super(QSFragment.class); - } @Before public void setup() { - injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); - } - - @Test - public void testListening() { - QSFragment qs = (QSFragment) mFragment; - mFragments.dispatchResume(); - processAllMessages(); - - qs.setListening(true); - processAllMessages(); + mUnderTest = instantiate(); - qs.setListening(false); - processAllMessages(); + mUnderTest.onComponentCreated(mQsComponent, null); } + @Test public void testSaveState() { - mFragments.dispatchResume(); - processAllMessages(); + mUnderTest.setListening(true); + mUnderTest.setExpanded(true); + mUnderTest.setQsVisible(true); + + Bundle bundle = new Bundle(); + mUnderTest.onSaveInstanceState(bundle); - QSFragment qs = (QSFragment) mFragment; - qs.setListening(true); - qs.setExpanded(true); - qs.setQsVisible(true); - processAllMessages(); - recreateFragment(); - processAllMessages(); + // Get a new instance + QSImpl other = instantiate(); + other.onComponentCreated(mQsComponent, bundle); - // Get the reference to the new fragment. - qs = (QSFragment) mFragment; - assertTrue(qs.isListening()); - assertTrue(qs.isExpanded()); - assertTrue(qs.isQsVisible()); + assertTrue(other.isListening()); + assertTrue(other.isExpanded()); + assertTrue(other.isQsVisible()); } @Test public void transitionToFullShade_smallScreen_alphaAlways1() { - QSFragment fragment = resumeAndGetFragment(); setIsSmallScreen(); setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE); boolean isTransitioningToFullShade = true; float transitionProgress = 0.5f; float squishinessFraction = 0.5f; - fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, + mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, squishinessFraction); - assertThat(mQsFragmentView.getAlpha()).isEqualTo(1f); + assertThat(mQsView.getAlpha()).isEqualTo(1f); } @Test public void transitionToFullShade_largeScreen_alphaLargeScreenShadeInterpolator() { - QSFragment fragment = resumeAndGetFragment(); setIsLargeScreen(); setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE); boolean isTransitioningToFullShade = true; @@ -177,43 +162,40 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { float squishinessFraction = 0.5f; when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f); - fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, + mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, squishinessFraction); - assertThat(mQsFragmentView.getAlpha()) - .isEqualTo(123f); + assertThat(mQsView.getAlpha()).isEqualTo(123f); } @Test public void transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() { - QSFragment fragment = resumeAndGetFragment(); setStatusBarCurrentAndUpcomingState(KEYGUARD); when(mQSPanelController.isBouncerInTransit()).thenReturn(false); boolean isTransitioningToFullShade = true; float transitionProgress = 0.5f; float squishinessFraction = 0.5f; - fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, + mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, squishinessFraction); - assertThat(mQsFragmentView.getAlpha()).isEqualTo(transitionProgress); + assertThat(mQsView.getAlpha()).isEqualTo(transitionProgress); } @Test public void transitionToFullShade_onKeyguard_bouncerActive_setsAlphaUsingBouncerInterpolator() { - QSFragment fragment = resumeAndGetFragment(); setStatusBarCurrentAndUpcomingState(KEYGUARD); when(mQSPanelController.isBouncerInTransit()).thenReturn(true); boolean isTransitioningToFullShade = true; float transitionProgress = 0.5f; float squishinessFraction = 0.5f; - fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, + mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, squishinessFraction); - assertThat(mQsFragmentView.getAlpha()) + assertThat(mQsView.getAlpha()) .isEqualTo( BouncerPanelExpansionCalculator.aboutToShowBouncerProgress( transitionProgress)); @@ -221,40 +203,37 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Test public void transitionToFullShade_inFullWidth_alwaysSetsAlphaTo1() { - QSFragment fragment = resumeAndGetFragment(); - fragment.setIsNotificationPanelFullWidth(true); + mUnderTest.setIsNotificationPanelFullWidth(true); boolean isTransitioningToFullShade = true; float transitionProgress = 0.1f; float squishinessFraction = 0.5f; - fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, + mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, squishinessFraction); - assertThat(mQsFragmentView.getAlpha()).isEqualTo(1); + assertThat(mQsView.getAlpha()).isEqualTo(1); transitionProgress = 0.5f; - fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, + mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, squishinessFraction); - assertThat(mQsFragmentView.getAlpha()).isEqualTo(1); - assertThat(mQsFragmentView.getAlpha()).isEqualTo(1); + assertThat(mQsView.getAlpha()).isEqualTo(1); + assertThat(mQsView.getAlpha()).isEqualTo(1); transitionProgress = 0.7f; - fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, + mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, squishinessFraction); - assertThat(mQsFragmentView.getAlpha()).isEqualTo(1); + assertThat(mQsView.getAlpha()).isEqualTo(1); } @Test public void transitionToFullShade_setsSquishinessOnController() { - QSFragment fragment = resumeAndGetFragment(); boolean isTransitioningToFullShade = true; float transitionProgress = 0.123f; float squishinessFraction = 0.456f; - fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, + mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, squishinessFraction); - verify(mQsFragmentComponent.getQSSquishinessController()) - .setSquishiness(squishinessFraction); + verify(mQsComponent.getQSSquishinessController()).setSquishiness(squishinessFraction); } @Test @@ -265,10 +244,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { float proposedTranslation = 456f; float squishinessFraction = 0.987f; - QSFragment fragment = resumeAndGetFragment(); enableSplitShade(); - fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, + mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, squishinessFraction); verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged( @@ -283,10 +261,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { float proposedTranslation = 456f; float squishinessFraction = 0.987f; - QSFragment fragment = resumeAndGetFragment(); disableSplitShade(); - fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, + mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, squishinessFraction); verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged( @@ -295,7 +272,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Test public void setQsExpansion_inSplitShade_whenTransitioningToKeyguard_setsAlphaBasedOnShadeTransitionProgress() { - QSFragment fragment = resumeAndGetFragment(); enableSplitShade(); when(mStatusBarStateController.getState()).thenReturn(SHADE); when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); @@ -303,24 +279,23 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { float transitionProgress = 0; float squishinessFraction = 0f; - fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, + mUnderTest.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress, squishinessFraction); // trigger alpha refresh with non-zero expansion and fraction values - fragment.setQsExpansion(/* expansion= */ 1, /* panelExpansionFraction= */1, + mUnderTest.setQsExpansion(/* expansion= */ 1, /* panelExpansionFraction= */1, /* proposedTranslation= */ 0, /* squishinessFraction= */ 1); // alpha should follow lockscreen to shade progress, not panel expansion fraction - assertThat(mQsFragmentView.getAlpha()).isEqualTo(transitionProgress); + assertThat(mQsView.getAlpha()).isEqualTo(transitionProgress); } @Test public void getQsMinExpansionHeight_notInSplitShade_returnsHeaderHeight() { - QSFragment fragment = resumeAndGetFragment(); disableSplitShade(); when(mHeader.getHeight()).thenReturn(1234); - int height = fragment.getQsMinExpansionHeight(); + int height = mUnderTest.getQsMinExpansionHeight(); assertThat(height).isEqualTo(mHeader.getHeight()); } @@ -329,13 +304,12 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { public void getQsMinExpansionHeight_inSplitShade_returnsAbsoluteBottomOfQSContainer() { int top = 1234; int height = 9876; - QSFragment fragment = resumeAndGetFragment(); enableSplitShade(); - setLocationOnScreen(mQsFragmentView, top); - when(mQsFragmentView.getHeight()).thenReturn(height); + setLocationOnScreen(mQsView, top); + when(mQsView.getHeight()).thenReturn(height); int expectedHeight = top + height; - assertThat(fragment.getQsMinExpansionHeight()).isEqualTo(expectedHeight); + assertThat(mUnderTest.getQsMinExpansionHeight()).isEqualTo(expectedHeight); } @Test @@ -343,47 +317,43 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { int top = 1234; int height = 9876; float translationY = -600f; - QSFragment fragment = resumeAndGetFragment(); enableSplitShade(); - setLocationOnScreen(mQsFragmentView, (int) (top + translationY)); - when(mQsFragmentView.getHeight()).thenReturn(height); - when(mQsFragmentView.getTranslationY()).thenReturn(translationY); + setLocationOnScreen(mQsView, (int) (top + translationY)); + when(mQsView.getHeight()).thenReturn(height); + when(mQsView.getTranslationY()).thenReturn(translationY); int expectedHeight = top + height; - assertThat(fragment.getQsMinExpansionHeight()).isEqualTo(expectedHeight); + assertThat(mUnderTest.getQsMinExpansionHeight()).isEqualTo(expectedHeight); } @Test public void hideImmediately_notInSplitShade_movesViewUpByHeaderHeight() { - QSFragment fragment = resumeAndGetFragment(); disableSplitShade(); when(mHeader.getHeight()).thenReturn(555); - fragment.hideImmediately(); + mUnderTest.hideImmediately(); - assertThat(mQsFragmentView.getY()).isEqualTo(-mHeader.getHeight()); + assertThat(mQsView.getY()).isEqualTo(-mHeader.getHeight()); } @Test public void hideImmediately_inSplitShade_movesViewUpByQSAbsoluteBottom() { - QSFragment fragment = resumeAndGetFragment(); enableSplitShade(); int top = 1234; int height = 9876; - setLocationOnScreen(mQsFragmentView, top); - when(mQsFragmentView.getHeight()).thenReturn(height); + setLocationOnScreen(mQsView, top); + when(mQsView.getHeight()).thenReturn(height); - fragment.hideImmediately(); + mUnderTest.hideImmediately(); int qsAbsoluteBottom = top + height; - assertThat(mQsFragmentView.getY()).isEqualTo(-qsAbsoluteBottom); + assertThat(mQsView.getY()).isEqualTo(-qsAbsoluteBottom); } @Test public void setCollapseExpandAction_passedToControllers() { Runnable action = () -> {}; - QSFragment fragment = resumeAndGetFragment(); - fragment.setCollapseExpandAction(action); + mUnderTest.setCollapseExpandAction(action); verify(mQSPanelController).setCollapseExpandAction(action); verify(mQuickQSPanelController).setCollapseExpandAction(action); @@ -391,24 +361,21 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Test public void setOverScrollAmount_setsTranslationOnView() { - QSFragment fragment = resumeAndGetFragment(); + mUnderTest.setOverScrollAmount(123); - fragment.setOverScrollAmount(123); - - assertThat(mQsFragmentView.getTranslationY()).isEqualTo(123); + assertThat(mQsView.getTranslationY()).isEqualTo(123); } @Test public void setOverScrollAmount_beforeViewCreated_translationIsNotSet() { - QSFragment fragment = getFragment(); - - fragment.setOverScrollAmount(123); + QSImpl other = instantiate(); + other.setOverScrollAmount(123); - assertThat(mQsFragmentView.getTranslationY()).isEqualTo(0); + assertThat(mQsView.getTranslationY()).isEqualTo(0); } private Lifecycle.State getListeningAndVisibilityLifecycleState() { - return getFragment() + return mUnderTest .getListeningAndVisibilityLifecycleOwner() .getLifecycle() .getCurrentState(); @@ -416,11 +383,10 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Test public void setListeningFalse_notVisible() { - QSFragment fragment = resumeAndGetFragment(); - fragment.setQsVisible(false); + mUnderTest.setQsVisible(false); clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController); - fragment.setListening(false); + mUnderTest.setListening(false); verify(mQSContainerImplController).setListening(false); assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED); verify(mQSPanelController).setListening(eq(false), anyBoolean()); @@ -428,11 +394,10 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Test public void setListeningTrue_notVisible() { - QSFragment fragment = resumeAndGetFragment(); - fragment.setQsVisible(false); + mUnderTest.setQsVisible(false); clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController); - fragment.setListening(true); + mUnderTest.setListening(true); verify(mQSContainerImplController).setListening(false); assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.STARTED); verify(mQSPanelController).setListening(eq(false), anyBoolean()); @@ -440,11 +405,10 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Test public void setListeningFalse_visible() { - QSFragment fragment = resumeAndGetFragment(); - fragment.setQsVisible(true); + mUnderTest.setQsVisible(true); clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController); - fragment.setListening(false); + mUnderTest.setListening(false); verify(mQSContainerImplController).setListening(false); assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED); verify(mQSPanelController).setListening(eq(false), anyBoolean()); @@ -452,11 +416,10 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Test public void setListeningTrue_visible() { - QSFragment fragment = resumeAndGetFragment(); - fragment.setQsVisible(true); + mUnderTest.setQsVisible(true); clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController); - fragment.setListening(true); + mUnderTest.setListening(true); verify(mQSContainerImplController).setListening(true); assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.RESUMED); verify(mQSPanelController).setListening(eq(true), anyBoolean()); @@ -464,31 +427,28 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Test public void passCorrectExpansionState_inSplitShade() { - QSFragment fragment = resumeAndGetFragment(); enableSplitShade(); clearInvocations(mQSPanelController); - fragment.setExpanded(true); + mUnderTest.setExpanded(true); verify(mQSPanelController).setExpanded(true); - fragment.setExpanded(false); + mUnderTest.setExpanded(false); verify(mQSPanelController).setExpanded(false); } @Test public void startsListeningAfterStateChangeToExpanded_inSplitShade() { - QSFragment fragment = resumeAndGetFragment(); enableSplitShade(); - fragment.setQsVisible(true); + mUnderTest.setQsVisible(true); clearInvocations(mQSPanelController); - fragment.setExpanded(true); + mUnderTest.setExpanded(true); verify(mQSPanelController).setListening(true, true); } @Test public void testUpdateQSBounds_setMediaClipCorrectly() { - QSFragment fragment = resumeAndGetFragment(); disableSplitShade(); Rect mediaHostClip = new Rect(); @@ -497,7 +457,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { when(mQSPanelScrollView.getMeasuredHeight()).thenReturn(200); when(mQSMediaHost.getCurrentClipping()).thenReturn(mediaHostClip); - fragment.updateQsBounds(); + mUnderTest.updateQsBounds(); assertEquals(25, mediaHostClip.top); assertEquals(175, mediaHostClip.bottom); @@ -505,17 +465,15 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Test public void testQsUpdatesQsAnimatorWithUpcomingState() { - QSFragment fragment = resumeAndGetFragment(); setStatusBarCurrentAndUpcomingState(SHADE); - fragment.onUpcomingStateChanged(KEYGUARD); + mUnderTest.onUpcomingStateChanged(KEYGUARD); verify(mQSAnimator).setOnKeyguard(true); } - @Override - protected Fragment instantiate(Context context, String className, Bundle arguments) { + private QSImpl instantiate() { MockitoAnnotations.initMocks(this); - CommandQueue commandQueue = new CommandQueue(context, new FakeDisplayTracker(context)); + CommandQueue commandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext)); setupQsComponent(); setUpViews(); @@ -523,9 +481,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { setUpMedia(); setUpOther(); - return new QSFragment( + return new QSImpl( new RemoteInputQuickSettingsDisabler( - context, + mContext, commandQueue, new ResourcesSplitShadeStateController(), mock(ConfigurationController.class)), @@ -534,8 +492,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { mQSMediaHost, mQQSMediaHost, mBypassController, - mQsComponentFactory, - mock(QSFragmentDisableFlagsLogger.class), + mock(QSDisableFlagsLogger.class), mock(DumpManager.class), mock(QSLogger.class), mock(FooterActionsController.class), @@ -561,12 +518,13 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { } private void setUpViews() { - mQsFragmentView = spy(new View(mContext)); - when(mQsFragmentView.findViewById(R.id.expanded_qs_scroll_view)) + mQsView = spy(new View(mContext)); + when(mQsComponent.getRootView()).thenReturn(mQsView); + when(mQsView.findViewById(R.id.expanded_qs_scroll_view)) .thenReturn(mQSPanelScrollView); - when(mQsFragmentView.findViewById(R.id.header)).thenReturn(mHeader); - when(mQsFragmentView.findViewById(android.R.id.edit)).thenReturn(new View(mContext)); - when(mQsFragmentView.findViewById(R.id.qs_footer_actions)).thenAnswer( + when(mQsView.findViewById(R.id.header)).thenReturn(mHeader); + when(mQsView.findViewById(android.R.id.edit)).thenReturn(new View(mContext)); + when(mQsView.findViewById(R.id.qs_footer_actions)).thenAnswer( invocation -> new FooterActionsViewBinder().create(mContext)); } @@ -597,37 +555,26 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { return realInflater.inflate(layoutRes, root, attachToRoot); } - return mQsFragmentView; + return mQsView; } private void setupQsComponent() { - when(mQsComponentFactory.create(any(QSFragment.class))).thenReturn(mQsFragmentComponent); - when(mQsFragmentComponent.getQSPanelController()).thenReturn(mQSPanelController); - when(mQsFragmentComponent.getQuickQSPanelController()).thenReturn(mQuickQSPanelController); - when(mQsFragmentComponent.getQSCustomizerController()).thenReturn(mQsCustomizerController); - when(mQsFragmentComponent.getQSContainerImplController()) + when(mQsComponent.getQSPanelController()).thenReturn(mQSPanelController); + when(mQsComponent.getQuickQSPanelController()).thenReturn(mQuickQSPanelController); + when(mQsComponent.getQSCustomizerController()).thenReturn(mQsCustomizerController); + when(mQsComponent.getQSContainerImplController()) .thenReturn(mQSContainerImplController); - when(mQsFragmentComponent.getQSFooter()).thenReturn(mFooter); - when(mQsFragmentComponent.getQSFooterActionController()) + when(mQsComponent.getQSFooter()).thenReturn(mFooter); + when(mQsComponent.getQSFooterActionController()) .thenReturn(mQSFooterActionController); - when(mQsFragmentComponent.getQSAnimator()).thenReturn(mQSAnimator); - when(mQsFragmentComponent.getQSSquishinessController()).thenReturn(mSquishinessController); - } - - private QSFragment getFragment() { - return ((QSFragment) mFragment); - } - - private QSFragment resumeAndGetFragment() { - mFragments.dispatchResume(); - processAllMessages(); - return getFragment(); + when(mQsComponent.getQSAnimator()).thenReturn(mQSAnimator); + when(mQsComponent.getQSSquishinessController()).thenReturn(mSquishinessController); } private void setStatusBarCurrentAndUpcomingState(int statusBarState) { when(mStatusBarStateController.getState()).thenReturn(statusBarState); when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(statusBarState); - getFragment().onStateChanged(statusBarState); + mUnderTest.onStateChanged(statusBarState); } private void enableSplitShade() { @@ -639,7 +586,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { } private void setSplitShadeEnabled(boolean enabled) { - getFragment().setInSplitShade(enabled); + mUnderTest.setInSplitShade(enabled); } private void setLocationOnScreen(View view, int top) { @@ -652,10 +599,10 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { } private void setIsLargeScreen() { - getFragment().setIsNotificationPanelFullWidth(false); + mUnderTest.setIsNotificationPanelFullWidth(false); } private void setIsSmallScreen() { - getFragment().setIsNotificationPanelFullWidth(true); + mUnderTest.setIsNotificationPanelFullWidth(true); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index b595e8de3bad..79411f427f1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -66,6 +66,7 @@ import com.android.systemui.qs.external.TileServiceKey; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.qs.tiles.di.NewQSTileFactory; import com.android.systemui.settings.UserFileManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeController; @@ -77,6 +78,8 @@ import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; +import dagger.Lazy; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -102,8 +105,6 @@ public class QSTileHostTest extends SysuiTestCase { private static final String SETTING = QSHost.TILES_SETTING; @Mock - private QSFactory mDefaultFactory; - @Mock private PluginManager mPluginManager; @Mock private TunerService mTunerService; @@ -117,7 +118,6 @@ public class QSTileHostTest extends SysuiTestCase { private CustomTile mCustomTile; @Mock private UserTracker mUserTracker; - private SecureSettings mSecureSettings; @Mock private CustomTileStatePersister mCustomTileStatePersister; @Mock @@ -127,6 +127,10 @@ public class QSTileHostTest extends SysuiTestCase { @Mock private UserFileManager mUserFileManager; + private SecureSettings mSecureSettings; + + private QSFactory mDefaultFactory; + private SparseArray<SharedPreferences> mSharedPreferencesByUser; private FakeFeatureFlags mFeatureFlags; @@ -144,6 +148,8 @@ public class QSTileHostTest extends SysuiTestCase { mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false); mFeatureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, false); + // TODO(b/299909337): Add test checking the new factory is used when the flag is on + mFeatureFlags.set(Flags.QS_PIPELINE_NEW_TILES, false); mQSPipelineFlagsRepository = new QSPipelineFlagsRepository(mFeatureFlags); mMainExecutor = new FakeExecutor(new FakeSystemClock()); @@ -164,7 +170,8 @@ public class QSTileHostTest extends SysuiTestCase { mSecureSettings = new FakeSettings(); saveSetting(""); - mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor, + setUpTileFactory(); + mQSTileHost = new TestQSTileHost(mContext, () -> null, mDefaultFactory, mMainExecutor, mPluginManager, mTunerService, () -> mAutoTiles, mShadeController, mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister, mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository); @@ -178,7 +185,6 @@ public class QSTileHostTest extends SysuiTestCase { mMainExecutor.runAllReady(); } }, mUserTracker.getUserId()); - setUpTileFactory(); } private void saveSetting(String value) { @@ -191,32 +197,29 @@ public class QSTileHostTest extends SysuiTestCase { } private void setUpTileFactory() { - // Only create this kind of tiles - when(mDefaultFactory.createTile(anyString())).thenAnswer( - invocation -> { - String spec = invocation.getArgument(0); - if ("spec1".equals(spec)) { - return new TestTile1(mQSTileHost); - } else if ("spec2".equals(spec)) { - return new TestTile2(mQSTileHost); - } else if ("spec3".equals(spec)) { - return new TestTile3(mQSTileHost); - } else if ("na".equals(spec)) { - return new NotAvailableTile(mQSTileHost); - } else if (CUSTOM_TILE_SPEC.equals(spec)) { - QSTile tile = mCustomTile; - QSTile.State s = mock(QSTile.State.class); - s.spec = spec; - when(mCustomTile.getState()).thenReturn(s); - return tile; - } else if ("internet".equals(spec) - || "wifi".equals(spec) - || "cell".equals(spec)) { - return new TestTile1(mQSTileHost); - } else { - return null; - } - }); + mDefaultFactory = new FakeQSFactory(spec -> { + if ("spec1".equals(spec)) { + return new TestTile1(mQSTileHost); + } else if ("spec2".equals(spec)) { + return new TestTile2(mQSTileHost); + } else if ("spec3".equals(spec)) { + return new TestTile3(mQSTileHost); + } else if ("na".equals(spec)) { + return new NotAvailableTile(mQSTileHost); + } else if (CUSTOM_TILE_SPEC.equals(spec)) { + QSTile tile = mCustomTile; + QSTile.State s = mock(QSTile.State.class); + s.spec = spec; + when(mCustomTile.getState()).thenReturn(s); + return tile; + } else if ("internet".equals(spec) + || "wifi".equals(spec) + || "cell".equals(spec)) { + return new TestTile1(mQSTileHost); + } else { + return null; + } + }); when(mCustomTile.isAvailable()).thenReturn(true); } @@ -703,7 +706,7 @@ public class QSTileHostTest extends SysuiTestCase { } private class TestQSTileHost extends QSTileHost { - TestQSTileHost(Context context, + TestQSTileHost(Context context, Lazy<NewQSTileFactory> newQSTileFactoryProvider, QSFactory defaultFactory, Executor mainExecutor, PluginManager pluginManager, TunerService tunerService, Provider<AutoTileManager> autoTiles, @@ -712,7 +715,7 @@ public class QSTileHostTest extends SysuiTestCase { CustomTileStatePersister customTileStatePersister, TileLifecycleManager.Factory tileLifecycleManagerFactory, UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) { - super(context, defaultFactory, mainExecutor, pluginManager, + super(context, newQSTileFactoryProvider, defaultFactory, mainExecutor, pluginManager, tunerService, autoTiles, shadeController, qsLogger, userTracker, secureSettings, customTileStatePersister, tileLifecycleManagerFactory, userFileManager, featureFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index bde30382ba05..d3cd26bcd3cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -124,6 +124,7 @@ public class TileQueryHelperTest extends SysuiTestCase { if (FACTORY_TILES.contains(spec)) { FakeQSTile tile = new FakeQSTile(mBgExecutor, mMainExecutor); tile.setState(mState); + tile.setTileSpec(spec); return tile; } else { return null; @@ -284,7 +285,10 @@ public class TileQueryHelperTest extends SysuiTestCase { Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.QS_TILES, null); QSTile t = mock(QSTile.class); - when(mQSHost.createTile("hotspot")).thenReturn(t); + when(mQSHost.createTile("hotspot")).thenAnswer(invocation -> { + t.setTileSpec("hotspot"); + return t; + }); mContext.getOrCreateTestableResources().addOverride(R.string.quick_settings_tiles_stock, "hotspot"); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index 126dd633108e..43cf1b5ecc40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -16,12 +16,15 @@ package com.android.systemui.qs.external +import android.app.IUriGrantsManager import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ServiceInfo +import android.graphics.Bitmap +import android.graphics.Canvas import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.os.Handler @@ -49,6 +52,7 @@ import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -67,6 +71,8 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import java.util.Arrays + @SmallTest @RunWith(AndroidTestingRunner::class) @@ -74,10 +80,8 @@ import org.mockito.MockitoAnnotations class CustomTileTest : SysuiTestCase() { companion object { - const val packageName = "test_package" const val className = "test_class" - val componentName = ComponentName(packageName, className) - val TILE_SPEC = CustomTile.toSpec(componentName) + val UID = 12345 } @Mock private lateinit var tileHost: QSHost @@ -94,11 +98,36 @@ class CustomTileTest : SysuiTestCase() { @Mock private lateinit var serviceInfo: ServiceInfo @Mock private lateinit var customTileStatePersister: CustomTileStatePersister @Mock private lateinit var uiEventLogger: QsEventLogger + @Mock private lateinit var ugm: IUriGrantsManager private var displayTracker = FakeDisplayTracker(mContext) private lateinit var customTile: CustomTile private lateinit var testableLooper: TestableLooper - private lateinit var customTileBuilder: CustomTile.Builder + private val packageName = context.packageName + private val componentName = ComponentName(packageName, className) + private val TILE_SPEC = CustomTile.toSpec(componentName) + + private val customTileFactory = object : CustomTile.Factory { + override fun create(action: String, userContext: Context): CustomTile { + return CustomTile( + { tileHost }, + uiEventLogger, + testableLooper.looper, + Handler(testableLooper.looper), + FalsingManagerFake(), + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + action, + userContext, + customTileStatePersister, + tileServices, + displayTracker, + ugm, + ) + } + } @Before fun setUp() { @@ -116,24 +145,13 @@ class CustomTileTest : SysuiTestCase() { `when`(packageManager.getServiceInfo(any(ComponentName::class.java), anyInt())) .thenReturn(serviceInfo) + `when`(packageManager.getResourcesForApplication(any<ApplicationInfo>())) + .thenReturn(context.resources) + serviceInfo.applicationInfo = applicationInfo - customTileBuilder = CustomTile.Builder( - { tileHost }, - uiEventLogger, - testableLooper.looper, - Handler(testableLooper.looper), - FalsingManagerFake(), - metricsLogger, - statusBarStateController, - activityStarter, - qsLogger, - customTileStatePersister, - tileServices, - displayTracker - ) - customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) customTile.initialize() testableLooper.processAllMessages() } @@ -146,7 +164,7 @@ class CustomTileTest : SysuiTestCase() { `when`(userContext.packageManager).thenReturn(packageManager) `when`(userContext.userId).thenReturn(10) - val tile = CustomTile.create(customTileBuilder, TILE_SPEC, userContext) + val tile = CustomTile.create(customTileFactory, TILE_SPEC, userContext) tile.initialize() testableLooper.processAllMessages() @@ -156,7 +174,7 @@ class CustomTileTest : SysuiTestCase() { @Test fun testToggleableTileHasBooleanState() { `when`(tileServiceManager.isToggleableTile).thenReturn(true) - customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) customTile.initialize() testableLooper.processAllMessages() @@ -173,7 +191,7 @@ class CustomTileTest : SysuiTestCase() { @Test fun testValueUpdatedInBooleanTile() { `when`(tileServiceManager.isToggleableTile).thenReturn(true) - customTile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + customTile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) customTile.initialize() testableLooper.processAllMessages() @@ -219,7 +237,7 @@ class CustomTileTest : SysuiTestCase() { val t = Tile().apply { state = Tile.STATE_INACTIVE } - customTile.updateTileState(t) + customTile.updateTileState(t, UID) testableLooper.processAllMessages() verify(customTileStatePersister, never()).persistState(any(), any()) @@ -243,7 +261,7 @@ class CustomTileTest : SysuiTestCase() { `when`(tileServiceManager.isActiveTile).thenReturn(true) `when`(customTileStatePersister .readState(TileServiceKey(componentName, customTile.user))).thenReturn(t) - val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) tile.initialize() testableLooper.processAllMessages() @@ -281,11 +299,11 @@ class CustomTileTest : SysuiTestCase() { } `when`(tileServiceManager.isActiveTile).thenReturn(true) - val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) tile.initialize() testableLooper.processAllMessages() - tile.updateTileState(t) + tile.updateTileState(t, UID) testableLooper.processAllMessages() @@ -297,13 +315,13 @@ class CustomTileTest : SysuiTestCase() { fun testAvailableBeforeInitialization() { `when`(packageManager.getApplicationInfo(anyString(), anyInt())) .thenThrow(PackageManager.NameNotFoundException()) - val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) assertTrue(tile.isAvailable) } @Test fun testNotAvailableAfterInitializationWithoutIcon() { - val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) reset(tileHost) tile.initialize() testableLooper.processAllMessages() @@ -315,7 +333,7 @@ class CustomTileTest : SysuiTestCase() { fun testInvalidPendingIntentDoesNotStartActivity() { val pi = mock(PendingIntent::class.java) `when`(pi.isActivity).thenReturn(false) - val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) assertThrows(IllegalArgumentException::class.java) { tile.qsTile.activityLaunchForClick = pi @@ -333,7 +351,7 @@ class CustomTileTest : SysuiTestCase() { fun testValidPendingIntentWithNoClickDoesNotStartActivity() { val pi = mock(PendingIntent::class.java) `when`(pi.isActivity).thenReturn(true) - val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) tile.qsTile.activityLaunchForClick = pi testableLooper.processAllMessages() @@ -347,7 +365,7 @@ class CustomTileTest : SysuiTestCase() { fun testValidPendingIntentStartsActivity() { val pi = mock(PendingIntent::class.java) `when`(pi.isActivity).thenReturn(true) - val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) tile.qsTile.activityLaunchForClick = pi tile.handleClick(mock(LaunchableFrameLayout::class.java)) @@ -363,7 +381,7 @@ class CustomTileTest : SysuiTestCase() { fun testActiveTileListensOnceAfterCreated() { `when`(tileServiceManager.isActiveTile).thenReturn(true) - val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) tile.initialize() tile.postStale() testableLooper.processAllMessages() @@ -376,7 +394,7 @@ class CustomTileTest : SysuiTestCase() { fun testActiveTileDoesntListenAfterFirstTime() { `when`(tileServiceManager.isActiveTile).thenReturn(true) - val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext) + val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) tile.initialize() // Make sure we have an icon in the tile because we don't have a default icon // This should not be overridden by the retrieved tile that has null icon. @@ -424,19 +442,128 @@ class CustomTileTest : SysuiTestCase() { // Set the tile to listening and apply the tile (unmodified) customTile.handleSetListening(true) testableLooper.processAllMessages() - customTile.updateTileState(tile) + customTile.updateTileState(tile, UID) customTile.refreshState() testableLooper.processAllMessages() assertThat(customTile.state.label).isEqualTo(label2) } - private fun copyTileUsingParcel(t: Tile): Tile { - val parcel = Parcel.obtain() - parcel.setDataPosition(0) - t.writeToParcel(parcel, 0) - parcel.setDataPosition(0) + @Test + fun uriIconLoadSuccess_correctIcon() { + val size = 100 + val icon = mock(Icon::class.java) + val drawable = context.getDrawable(R.drawable.cloud)!! + whenever(icon.loadDrawable(any())).thenReturn(drawable) + whenever(icon.loadDrawableCheckingUriGrant( + any(), + eq(ugm), + anyInt(), + anyString()) + ).thenReturn(drawable) + + serviceInfo.icon = R.drawable.android + + customTile.handleSetListening(true) + testableLooper.processAllMessages() + customTile.handleSetListening(false) + testableLooper.processAllMessages() + + val tile = copyTileUsingParcel(customTile.qsTile) + tile.icon = icon + + customTile.updateTileState(tile, UID) + + customTile.refreshState() + testableLooper.processAllMessages() + + verify(icon).loadDrawableCheckingUriGrant(context, ugm, UID, packageName) + + assertThat( + areDrawablesEqual( + customTile.state.iconSupplier.get().getDrawable(context), + drawable, + size + ) + ).isTrue() + } + + @Test + fun uriIconLoadFailsWithoutGrant_defaultIcon() { + val size = 100 + val drawable = context.getDrawable(R.drawable.cloud)!! + val icon = mock(Icon::class.java) + whenever(icon.loadDrawable(any())).thenReturn(drawable) + whenever(icon.loadDrawableCheckingUriGrant( + any(), + eq(ugm), + anyInt(), + anyString()) + ).thenReturn(null) + + // Give it an icon to prevent issues + serviceInfo.icon = R.drawable.android + + customTile.handleSetListening(true) + testableLooper.processAllMessages() + customTile.handleSetListening(false) + testableLooper.processAllMessages() + + val tile = copyTileUsingParcel(customTile.qsTile) + tile.icon = icon + + customTile.updateTileState(tile, UID) + + customTile.refreshState() + testableLooper.processAllMessages() + + verify(icon).loadDrawableCheckingUriGrant(context, ugm, UID, packageName) + + assertThat( + areDrawablesEqual( + customTile.state.iconSupplier.get().getDrawable(context), + context.getDrawable(R.drawable.android)!!, + size + ) + ).isTrue() + } +} + +private fun areDrawablesEqual(drawable1: Drawable, drawable2: Drawable, size: Int = 24): Boolean { + val bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) + val bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) + + val canvas1 = Canvas(bm1) + val canvas2 = Canvas(bm2) + + drawable1.setBounds(0, 0, size, size) + drawable2.setBounds(0, 0, size, size) + + drawable1.draw(canvas1) + drawable2.draw(canvas2) - return Tile.CREATOR.createFromParcel(parcel) + return equalBitmaps(bm1, bm2).also { + bm1.recycle() + bm2.recycle() } +} + +private fun equalBitmaps(a: Bitmap, b: Bitmap): Boolean { + if (a.width != b.width || a.height != b.height) return false + val w = a.width + val h = a.height + val aPix = IntArray(w * h) + val bPix = IntArray(w * h) + a.getPixels(aPix, 0, w, 0, 0, w, h) + b.getPixels(bPix, 0, w, 0, 0, w, h) + return Arrays.equals(aPix, bPix) +} + +private fun copyTileUsingParcel(t: Tile): Tile { + val parcel = Parcel.obtain() + parcel.setDataPosition(0) + t.writeToParcel(parcel, 0) + parcel.setDataPosition(0) + + return Tile.CREATOR.createFromParcel(parcel) }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt index 365e8a5187d4..78c2acf7209e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt @@ -16,6 +16,11 @@ package com.android.systemui.qs.external +import android.app.IUriGrantsManager +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -26,11 +31,21 @@ import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.qs.QSTileView +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.Arrays @SmallTest @RunWith(AndroidTestingRunner::class) @@ -40,12 +55,19 @@ class TileRequestDialogTest : SysuiTestCase() { companion object { private const val APP_NAME = "App name" private const val LABEL = "Label" + private const val PACKAGE = "package" + private const val UID = 12345 + private val DEFAULT_ICON = R.drawable.android } private lateinit var dialog: TileRequestDialog + @Mock + private lateinit var ugm: IUriGrantsManager + @Before fun setUp() { + MockitoAnnotations.initMocks(this) // Create in looper so we can make sure that the tile is fully updated TestableLooper.get(this).runWithLooper { dialog = TileRequestDialog(mContext) @@ -62,9 +84,9 @@ class TileRequestDialogTest : SysuiTestCase() { @Test fun setTileData_hasCorrectViews() { val icon = Icon.createWithResource(mContext, R.drawable.cloud) - val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon) + val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE) - dialog.setTileData(tileData) + dialog.setTileData(tileData, ugm) dialog.show() val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID) @@ -77,9 +99,9 @@ class TileRequestDialogTest : SysuiTestCase() { @Test fun setTileData_hasCorrectAppName() { val icon = Icon.createWithResource(mContext, R.drawable.cloud) - val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon) + val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE) - dialog.setTileData(tileData) + dialog.setTileData(tileData, ugm) dialog.show() val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID) @@ -90,9 +112,9 @@ class TileRequestDialogTest : SysuiTestCase() { @Test fun setTileData_hasCorrectLabel() { val icon = Icon.createWithResource(mContext, R.drawable.cloud) - val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon) + val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE) - dialog.setTileData(tileData) + dialog.setTileData(tileData, ugm) dialog.show() TestableLooper.get(this).processAllMessages() @@ -105,9 +127,9 @@ class TileRequestDialogTest : SysuiTestCase() { @Test fun setTileData_hasIcon() { val icon = Icon.createWithResource(mContext, R.drawable.cloud) - val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon) + val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE) - dialog.setTileData(tileData) + dialog.setTileData(tileData, ugm) dialog.show() TestableLooper.get(this).processAllMessages() @@ -119,9 +141,9 @@ class TileRequestDialogTest : SysuiTestCase() { @Test fun setTileData_nullIcon_hasIcon() { - val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, null) + val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, null, PACKAGE) - dialog.setTileData(tileData) + dialog.setTileData(tileData, ugm) dialog.show() TestableLooper.get(this).processAllMessages() @@ -134,9 +156,9 @@ class TileRequestDialogTest : SysuiTestCase() { @Test fun setTileData_hasNoStateDescription() { val icon = Icon.createWithResource(mContext, R.drawable.cloud) - val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon) + val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE) - dialog.setTileData(tileData) + dialog.setTileData(tileData, ugm) dialog.show() TestableLooper.get(this).processAllMessages() @@ -150,9 +172,9 @@ class TileRequestDialogTest : SysuiTestCase() { @Test fun setTileData_tileNotClickable() { val icon = Icon.createWithResource(mContext, R.drawable.cloud) - val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon) + val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE) - dialog.setTileData(tileData) + dialog.setTileData(tileData, ugm) dialog.show() TestableLooper.get(this).processAllMessages() @@ -167,9 +189,9 @@ class TileRequestDialogTest : SysuiTestCase() { @Test fun setTileData_tileHasCorrectContentDescription() { val icon = Icon.createWithResource(mContext, R.drawable.cloud) - val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon) + val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE) - dialog.setTileData(tileData) + dialog.setTileData(tileData, ugm) dialog.show() TestableLooper.get(this).processAllMessages() @@ -179,4 +201,111 @@ class TileRequestDialogTest : SysuiTestCase() { assertThat(tile.contentDescription).isEqualTo(LABEL) } + + @Test + fun uriIconLoadSuccess_correctIcon() { + val tintColor = Color.BLACK + val icon = Mockito.mock(Icon::class.java) + val drawable = context.getDrawable(R.drawable.cloud)!!.apply { + setTint(tintColor) + } + whenever(icon.loadDrawable(any())).thenReturn(drawable) + whenever(icon.loadDrawableCheckingUriGrant( + any(), + eq(ugm), + anyInt(), + anyString()) + ).thenReturn(drawable) + + val size = 100 + + val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE) + + dialog.setTileData(tileData, ugm) + dialog.show() + + TestableLooper.get(this).processAllMessages() + + verify(icon).loadDrawableCheckingUriGrant(any(), eq(ugm), eq(UID), eq(PACKAGE)) + + val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID) + val tile = content.getChildAt(1) as QSTileView + + val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply { + setTint(tintColor) + } + + assertThat(areDrawablesEqual(iconDrawable, drawable, size)).isTrue() + } + + @Test + fun uriIconLoadFail_defaultIcon() { + val tintColor = Color.BLACK + val icon = Mockito.mock(Icon::class.java) + val drawable = context.getDrawable(R.drawable.cloud)!!.apply { + setTint(tintColor) + } + whenever(icon.loadDrawable(any())).thenReturn(drawable) + whenever(icon.loadDrawableCheckingUriGrant( + any(), + eq(ugm), + anyInt(), + anyString()) + ).thenReturn(null) + + val size = 100 + + val tileData = TileRequestDialog.TileData(UID, APP_NAME, LABEL, icon, PACKAGE) + + dialog.setTileData(tileData, ugm) + dialog.show() + + TestableLooper.get(this).processAllMessages() + + verify(icon).loadDrawableCheckingUriGrant(any(), eq(ugm), eq(UID), eq(PACKAGE)) + + val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID) + val tile = content.getChildAt(1) as QSTileView + + val iconDrawable = (tile.icon.iconView as ImageView).drawable.apply { + setTint(tintColor) + } + + val defaultIcon = context.getDrawable(DEFAULT_ICON)!!.apply { + setTint(tintColor) + } + + assertThat(areDrawablesEqual(iconDrawable, defaultIcon, size)).isTrue() + } } + +private fun areDrawablesEqual(drawable1: Drawable, drawable2: Drawable, size: Int = 24): Boolean { + val bm1 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) + val bm2 = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) + + val canvas1 = Canvas(bm1) + val canvas2 = Canvas(bm2) + + drawable1.setBounds(0, 0, size, size) + drawable2.setBounds(0, 0, size, size) + + drawable1.draw(canvas1) + drawable2.draw(canvas2) + + return equalBitmaps(bm1, bm2).also { + bm1.recycle() + bm2.recycle() + } +} + +private fun equalBitmaps(a: Bitmap, b: Bitmap): Boolean { + if (a.width != b.width || a.height != b.height) return false + val w = a.width + val h = a.height + val aPix = IntArray(w * h) + val bPix = IntArray(w * h) + a.getPixels(aPix, 0, w, 0, 0, w, h) + b.getPixels(bPix, 0, w, 0, 0, w, h) + return Arrays.equals(aPix, bPix) +} + diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt index ccfb5cf8959a..3afa6adbc972 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.external +import android.app.IUriGrantsManager import android.app.StatusBarManager import android.content.ComponentName import android.content.DialogInterface @@ -57,6 +58,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() { private val TEST_COMPONENT = ComponentName("test_pkg", "test_cls") private const val TEST_APP_NAME = "App" private const val TEST_LABEL = "Label" + private const val TEST_UID = 12345 } @Mock @@ -71,6 +73,8 @@ class TileServiceRequestControllerTest : SysuiTestCase() { private lateinit var logger: TileRequestDialogEventLogger @Mock private lateinit var icon: Icon + @Mock + private lateinit var ugm: IUriGrantsManager private val instanceIdSequence = InstanceIdSequenceFake(1_000) private lateinit var controller: TileServiceRequestController @@ -88,7 +92,8 @@ class TileServiceRequestControllerTest : SysuiTestCase() { qsHost, commandQueue, commandRegistry, - logger + logger, + ugm, ) { tileRequestDialog } @@ -98,10 +103,24 @@ class TileServiceRequestControllerTest : SysuiTestCase() { @Test fun requestTileAdd_dataIsPassedToDialog() { - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback()) + controller.requestTileAdd( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + Callback(), + ) verify(tileRequestDialog).setTileData( - TileRequestDialog.TileData(TEST_APP_NAME, TEST_LABEL, icon) + TileRequestDialog.TileData( + TEST_UID, + TEST_APP_NAME, + TEST_LABEL, + icon, + TEST_COMPONENT.packageName, + ), + ugm, ) } @@ -110,7 +129,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() { `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2) val callback = Callback() - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + controller.requestTileAdd( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + callback, + ) assertThat(callback.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED) verify(qsHost, never()).addTile(any(ComponentName::class.java), anyBoolean()) @@ -120,7 +146,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() { fun tileAlreadyAdded_logged() { `when`(qsHost.indexOf(CustomTile.toSpec(TEST_COMPONENT))).thenReturn(2) - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} + controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} verify(logger).logTileAlreadyAdded(eq<String>(TEST_COMPONENT.packageName), any()) verify(logger, never()).logDialogShown(anyString(), any()) @@ -129,19 +155,33 @@ class TileServiceRequestControllerTest : SysuiTestCase() { @Test fun showAllUsers_set() { - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback()) + controller.requestTileAdd( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + Callback(), + ) verify(tileRequestDialog).setShowForAllUsers(true) } @Test fun cancelOnTouchOutside_set() { - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback()) + controller.requestTileAdd( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + Callback(), + ) verify(tileRequestDialog).setCanceledOnTouchOutside(true) } @Test fun dialogShown_logged() { - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} + controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} verify(logger).logDialogShown(eq<String>(TEST_COMPONENT.packageName), any()) } @@ -152,7 +192,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() { ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) val callback = Callback() - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + controller.requestTileAdd( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + callback, + ) verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) cancelListenerCaptor.value.onCancel(tileRequestDialog) @@ -165,7 +212,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() { val cancelListenerCaptor = ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} + controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId) verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) @@ -185,7 +232,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() { ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) val callback = Callback() - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + controller.requestTileAdd( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + callback, + ) verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor)) clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_POSITIVE) @@ -199,7 +253,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() { val clickListenerCaptor = ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} + controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId) verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor)) @@ -219,7 +273,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() { ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) val callback = Callback() - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + controller.requestTileAdd( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + callback, + ) verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor)) clickListenerCaptor.value.onClick(tileRequestDialog, DialogInterface.BUTTON_NEGATIVE) @@ -233,7 +294,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() { val clickListenerCaptor = ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} + controller.requestTileAdd(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon) {} val instanceId = InstanceId.fakeInstanceId(instanceIdSequence.lastInstanceId) verify(tileRequestDialog).setNegativeButton(anyInt(), capture(clickListenerCaptor)) @@ -257,10 +318,24 @@ class TileServiceRequestControllerTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) verify(commandQueue, atLeastOnce()).addCallback(capture(captor)) - captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, Callback()) + captor.value.requestAddTile( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + Callback(), + ) verify(tileRequestDialog).setTileData( - TileRequestDialog.TileData(TEST_APP_NAME, TEST_LABEL, icon) + TileRequestDialog.TileData( + TEST_UID, + TEST_APP_NAME, + TEST_LABEL, + icon, + TEST_COMPONENT.packageName, + ), + ugm, ) } @@ -271,7 +346,7 @@ class TileServiceRequestControllerTest : SysuiTestCase() { verify(commandQueue, atLeastOnce()).addCallback(capture(captor)) val c = Callback() - captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c) + captor.value.requestAddTile(TEST_UID, TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, c) assertThat(c.lastAccepted).isEqualTo(TileServiceRequestController.TILE_ALREADY_ADDED) } @@ -288,7 +363,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() { throw RemoteException() } } - captor.value.requestAddTile(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + captor.value.requestAddTile( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + callback, + ) verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) cancelListenerCaptor.value.onCancel(tileRequestDialog) @@ -300,7 +382,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() { ArgumentCaptor.forClass(DialogInterface.OnDismissListener::class.java) val callback = Callback() - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + controller.requestTileAdd( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + callback, + ) verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) dismissListenerCaptor.value.onDismiss(tileRequestDialog) @@ -317,7 +406,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() { ArgumentCaptor.forClass(DialogInterface.OnClickListener::class.java) val callback = Callback() - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + controller.requestTileAdd( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + callback, + ) verify(tileRequestDialog).setPositiveButton(anyInt(), capture(clickListenerCaptor)) verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) @@ -338,7 +434,14 @@ class TileServiceRequestControllerTest : SysuiTestCase() { ArgumentCaptor.forClass(DialogInterface.OnCancelListener::class.java) val callback = Callback() - controller.requestTileAdd(TEST_COMPONENT, TEST_APP_NAME, TEST_LABEL, icon, callback) + controller.requestTileAdd( + TEST_UID, + TEST_COMPONENT, + TEST_APP_NAME, + TEST_LABEL, + icon, + callback, + ) verify(tileRequestDialog).setOnCancelListener(capture(cancelListenerCaptor)) verify(tileRequestDialog).setOnDismissListener(capture(dismissListenerCaptor)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt index 9386d711374a..9a55f722c13c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt @@ -23,15 +23,19 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher 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 +import org.mockito.Mock +import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -39,6 +43,20 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class AutoAddSettingsRepositoryTest : SysuiTestCase() { private val secureSettings = FakeSettings() + private val userAutoAddRepositoryFactory = + object : UserAutoAddRepository.Factory { + override fun create(userId: Int): UserAutoAddRepository { + return UserAutoAddRepository( + userId, + secureSettings, + logger, + testScope.backgroundScope, + testDispatcher, + ) + } + } + + @Mock private lateinit var logger: QSPipelineLogger private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -47,110 +65,37 @@ class AutoAddSettingsRepositoryTest : SysuiTestCase() { @Before fun setUp() { - underTest = - AutoAddSettingRepository( - secureSettings, - testDispatcher, - ) - } - - @Test - fun nonExistentSetting_emptySet() = - testScope.runTest { - val specs by collectLastValue(underTest.autoAddedTiles(0)) - - assertThat(specs).isEmpty() - } - - @Test - fun settingsChange_correctValues() = - testScope.runTest { - val userId = 0 - val specs by collectLastValue(underTest.autoAddedTiles(userId)) - - val value = "a,custom(b/c)" - storeForUser(value, userId) - - assertThat(specs).isEqualTo(value.toSet()) + MockitoAnnotations.initMocks(this) - val newValue = "a" - storeForUser(newValue, userId) - - assertThat(specs).isEqualTo(newValue.toSet()) - } + underTest = AutoAddSettingRepository(userAutoAddRepositoryFactory) + } @Test fun tilesForCorrectUsers() = testScope.runTest { - val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0)) - val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1)) - val user0Tiles = "a" val user1Tiles = "custom(b/c)" storeForUser(user0Tiles, 0) storeForUser(user1Tiles, 1) + val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0)) + val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1)) + runCurrent() - assertThat(tilesFromUser0).isEqualTo(user0Tiles.toSet()) - assertThat(tilesFromUser1).isEqualTo(user1Tiles.toSet()) - } - - @Test - fun noInvalidTileSpecs() = - testScope.runTest { - val userId = 0 - val tiles by collectLastValue(underTest.autoAddedTiles(userId)) - - val specs = "d,custom(bad)" - storeForUser(specs, userId) - - assertThat(tiles).isEqualTo("d".toSet()) - } - - @Test - fun markAdded() = - testScope.runTest { - val userId = 0 - val specs = mutableSetOf(TileSpec.create("a")) - underTest.markTileAdded(userId, TileSpec.create("a")) - - assertThat(loadForUser(userId).toSet()).containsExactlyElementsIn(specs) - - specs.add(TileSpec.create("b")) - underTest.markTileAdded(userId, TileSpec.create("b")) - - assertThat(loadForUser(userId).toSet()).containsExactlyElementsIn(specs) + assertThat(tilesFromUser0).isEqualTo(user0Tiles.toTilesSet()) + assertThat(tilesFromUser1).isEqualTo(user1Tiles.toTilesSet()) } @Test fun markAdded_multipleUsers() = testScope.runTest { - underTest.markTileAdded(userId = 1, TileSpec.create("a")) - - assertThat(loadForUser(0).toSet()).isEmpty() - assertThat(loadForUser(1).toSet()) - .containsExactlyElementsIn(setOf(TileSpec.create("a"))) - } - - @Test - fun markAdded_Invalid_noop() = - testScope.runTest { - val userId = 0 - underTest.markTileAdded(userId, TileSpec.Invalid) - - assertThat(loadForUser(userId).toSet()).isEmpty() - } - - @Test - fun unmarkAdded() = - testScope.runTest { - val userId = 0 - val specs = "a,custom(b/c)" - storeForUser(specs, userId) + val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0)) + val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1)) + runCurrent() - underTest.unmarkTileAdded(userId, TileSpec.create("a")) + underTest.markTileAdded(userId = 1, TileSpec.create("a")) - assertThat(loadForUser(userId).toSet()) - .containsExactlyElementsIn(setOf(TileSpec.create("custom(b/c)"))) + assertThat(tilesFromUser0).isEmpty() + assertThat(tilesFromUser1).containsExactlyElementsIn(setOf(TileSpec.create("a"))) } @Test @@ -159,33 +104,23 @@ class AutoAddSettingsRepositoryTest : SysuiTestCase() { val specs = "a,b" storeForUser(specs, 0) storeForUser(specs, 1) + val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0)) + val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1)) + runCurrent() underTest.unmarkTileAdded(1, TileSpec.create("a")) - assertThat(loadForUser(0).toSet()).isEqualTo(specs.toSet()) - assertThat(loadForUser(1).toSet()).isEqualTo(setOf(TileSpec.create("b"))) + assertThat(tilesFromUser0).isEqualTo(specs.toTilesSet()) + assertThat(tilesFromUser1).isEqualTo(setOf(TileSpec.create("b"))) } private fun storeForUser(specs: String, userId: Int) { secureSettings.putStringForUser(SETTING, specs, userId) } - private fun loadForUser(userId: Int): String { - return secureSettings.getStringForUser(SETTING, userId) ?: "" - } - companion object { private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES - private const val DELIMITER = "," - fun Set<TileSpec>.toSeparatedString() = joinToString(DELIMITER, transform = TileSpec::spec) - - fun String.toSet(): Set<TileSpec> { - return if (isNullOrBlank()) { - emptySet() - } else { - split(DELIMITER).map(TileSpec::create).toSet() - } - } + private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt new file mode 100644 index 000000000000..dc09a33adc1c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredBroadcastRepositoryTest.kt @@ -0,0 +1,220 @@ +package com.android.systemui.qs.pipeline.data.repository + +import android.content.Intent +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.FakeBroadcastDispatcher +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +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 +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +@RoboPilotTest +class QSSettingsRestoredBroadcastRepositoryTest : SysuiTestCase() { + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) + + @Mock private lateinit var pipelineLogger: QSPipelineLogger + + private lateinit var underTest: QSSettingsRestoredBroadcastRepository + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + underTest = + QSSettingsRestoredBroadcastRepository( + fakeBroadcastDispatcher, + pipelineLogger, + testScope.backgroundScope, + dispatcher, + ) + } + + @Test + fun restoreDataAfterBothIntents_tilesRestoredFirst() = + testScope.runTest { + runCurrent() + val restoreData by collectLastValue(underTest.restoreData) + val user = 0 + + val tilesIntent = + createRestoreIntent( + RestoreType.TILES, + CURRENT_TILES, + RESTORED_TILES, + ) + + val autoAddIntent = + createRestoreIntent( + RestoreType.AUTOADD, + CURRENT_AUTO_ADDED_TILES, + RESTORED_AUTO_ADDED_TILES, + ) + + sendIntentForUser(tilesIntent, user) + + // No restore data yet as we are missing one of the broadcasts + assertThat(restoreData).isNull() + + // After the second event, we see the corresponding restore + sendIntentForUser(autoAddIntent, user) + + with(restoreData!!) { + assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList()) + assertThat(restoredAutoAddedTiles).isEqualTo(RESTORED_AUTO_ADDED_TILES.toTilesSet()) + assertThat(userId).isEqualTo(user) + } + } + + @Test + fun restoreDataAfterBothIntents_autoAddRestoredFirst() = + testScope.runTest { + runCurrent() + val restoreData by collectLastValue(underTest.restoreData) + val user = 0 + + val tilesIntent = + createRestoreIntent( + RestoreType.TILES, + CURRENT_TILES, + RESTORED_TILES, + ) + + val autoAddIntent = + createRestoreIntent( + RestoreType.AUTOADD, + CURRENT_AUTO_ADDED_TILES, + RESTORED_AUTO_ADDED_TILES, + ) + + sendIntentForUser(autoAddIntent, user) + + // No restore data yet as we are missing one of the broadcasts + assertThat(restoreData).isNull() + + // After the second event, we see the corresponding restore + sendIntentForUser(tilesIntent, user) + + with(restoreData!!) { + assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList()) + assertThat(restoredAutoAddedTiles).isEqualTo(RESTORED_AUTO_ADDED_TILES.toTilesSet()) + assertThat(userId).isEqualTo(user) + } + } + + @Test + fun interleavedBroadcastsFromDifferentUsers_onlysendDataForCorrectUser() = + testScope.runTest { + runCurrent() + val restoreData by collectLastValue(underTest.restoreData) + + val user0 = 0 + val user10 = 10 + + val currentTiles10 = "z,y,x" + val restoredTiles10 = "x" + val currentAutoAdded10 = "f" + val restoredAutoAdded10 = "f,g" + + val tilesIntent0 = + createRestoreIntent( + RestoreType.TILES, + CURRENT_TILES, + RESTORED_TILES, + ) + val autoAddIntent0 = + createRestoreIntent( + RestoreType.AUTOADD, + CURRENT_AUTO_ADDED_TILES, + RESTORED_AUTO_ADDED_TILES, + ) + val tilesIntent10 = + createRestoreIntent( + RestoreType.TILES, + currentTiles10, + restoredTiles10, + ) + val autoAddIntent10 = + createRestoreIntent( + RestoreType.AUTOADD, + currentAutoAdded10, + restoredAutoAdded10, + ) + + sendIntentForUser(tilesIntent0, user0) + sendIntentForUser(autoAddIntent10, user10) + assertThat(restoreData).isNull() + + sendIntentForUser(tilesIntent10, user10) + + with(restoreData!!) { + assertThat(restoredTiles).isEqualTo(restoredTiles10.toTilesList()) + assertThat(restoredAutoAddedTiles).isEqualTo(restoredAutoAdded10.toTilesSet()) + assertThat(userId).isEqualTo(user10) + } + + sendIntentForUser(autoAddIntent0, user0) + + with(restoreData!!) { + assertThat(restoredTiles).isEqualTo(RESTORED_TILES.toTilesList()) + assertThat(restoredAutoAddedTiles).isEqualTo(RESTORED_AUTO_ADDED_TILES.toTilesSet()) + assertThat(userId).isEqualTo(user0) + } + } + + private fun sendIntentForUser(intent: Intent, userId: Int) { + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + intent, + FakeBroadcastDispatcher.fakePendingResultForUser(userId) + ) + } + + companion object { + private const val CURRENT_TILES = "a,b,c,d" + private const val RESTORED_TILES = "b,a,c" + private const val CURRENT_AUTO_ADDED_TILES = "d" + private const val RESTORED_AUTO_ADDED_TILES = "e" + + private fun createRestoreIntent( + type: RestoreType, + previousValue: String, + restoredValue: String, + ): Intent { + val setting = + when (type) { + RestoreType.TILES -> Settings.Secure.QS_TILES + RestoreType.AUTOADD -> Settings.Secure.QS_AUTO_ADDED_TILES + } + return Intent(Intent.ACTION_SETTING_RESTORED) + .putExtra(Intent.EXTRA_SETTING_NAME, setting) + .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue) + .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue) + } + + private fun String.toTilesList() = TilesSettingConverter.toTilesList(this) + + private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this) + + private enum class RestoreType { + TILES, + AUTOADD, + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt index 1c28e4c022a6..08adebb87b1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt @@ -23,14 +23,12 @@ import com.android.systemui.res.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.qs.QSHost import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import com.android.systemui.retail.data.repository.FakeRetailModeRepository import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -49,9 +47,28 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { private lateinit var secureSettings: FakeSettings private lateinit var retailModeRepository: FakeRetailModeRepository + private val defaultTilesRepository = + object : DefaultTilesRepository { + override val defaultTiles: List<TileSpec> + get() = DEFAULT_TILES.toTileSpecs() + } @Mock private lateinit var logger: QSPipelineLogger + private val userTileSpecRepositoryFactory = + object : UserTileSpecRepository.Factory { + override fun create(userId: Int): UserTileSpecRepository { + return UserTileSpecRepository( + userId, + defaultTilesRepository, + secureSettings, + logger, + testScope.backgroundScope, + testDispatcher, + ) + } + } + private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -66,293 +83,85 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { retailModeRepository.setRetailMode(false) with(context.orCreateTestableResources) { - addOverride(R.string.quick_settings_tiles_default, DEFAULT_TILES) addOverride(R.string.quick_settings_tiles_retail_mode, RETAIL_TILES) } underTest = TileSpecSettingsRepository( - secureSettings, context.resources, logger, retailModeRepository, - testDispatcher, + userTileSpecRepositoryFactory ) } @Test - fun emptySetting_usesDefaultValue() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - assertThat(tiles).isEqualTo(getDefaultTileSpecs()) - } - - @Test - fun changeInSettings_changesValue() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - storeTilesForUser("a", 0) - assertThat(tiles).isEqualTo(listOf(TileSpec.create("a"))) - - storeTilesForUser("a,custom(b/c)", 0) - assertThat(tiles) - .isEqualTo(listOf(TileSpec.create("a"), TileSpec.create("custom(b/c)"))) - } - - @Test fun tilesForCorrectUsers() = testScope.runTest { - val tilesFromUser0 by collectLastValue(underTest.tilesSpecs(0)) - val tilesFromUser1 by collectLastValue(underTest.tilesSpecs(1)) - val user0Tiles = "a" val user1Tiles = "custom(b/c)" storeTilesForUser(user0Tiles, 0) storeTilesForUser(user1Tiles, 1) + val tilesFromUser0 by collectLastValue(underTest.tilesSpecs(0)) + val tilesFromUser1 by collectLastValue(underTest.tilesSpecs(1)) + assertThat(tilesFromUser0).isEqualTo(user0Tiles.toTileSpecs()) assertThat(tilesFromUser1).isEqualTo(user1Tiles.toTileSpecs()) } @Test - fun invalidTilesAreNotPresent() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - val specs = "d,custom(bad)" - storeTilesForUser(specs, 0) - - assertThat(tiles).isEqualTo(specs.toTileSpecs().filter { it != TileSpec.Invalid }) - } - - @Test - fun noValidTiles_defaultSet() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - storeTilesForUser("custom(bad),custom()", 0) - - assertThat(tiles).isEqualTo(getDefaultTileSpecs()) - } - - @Test - fun addTileAtEnd() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - storeTilesForUser("a", 0) - - underTest.addTile(userId = 0, TileSpec.create("b")) - - val expected = "a,b" - assertThat(loadTilesForUser(0)).isEqualTo(expected) - assertThat(tiles).isEqualTo(expected.toTileSpecs()) - } - - @Test - fun addTileAtPosition() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - storeTilesForUser("a,custom(b/c)", 0) - - underTest.addTile(userId = 0, TileSpec.create("d"), position = 1) - - val expected = "a,d,custom(b/c)" - assertThat(loadTilesForUser(0)).isEqualTo(expected) - assertThat(tiles).isEqualTo(expected.toTileSpecs()) - } - - @Test - fun addInvalidTile_noop() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - val specs = "a,custom(b/c)" - storeTilesForUser(specs, 0) - - underTest.addTile(userId = 0, TileSpec.Invalid) - - assertThat(loadTilesForUser(0)).isEqualTo(specs) - assertThat(tiles).isEqualTo(specs.toTileSpecs()) - } - - @Test - fun addTileAtPosition_tooLarge_addedAtEnd() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - val specs = "a,custom(b/c)" - storeTilesForUser(specs, 0) - - underTest.addTile(userId = 0, TileSpec.create("d"), position = 100) - - val expected = "a,custom(b/c),d" - assertThat(loadTilesForUser(0)).isEqualTo(expected) - assertThat(tiles).isEqualTo(expected.toTileSpecs()) - } - - @Test fun addTileForOtherUser_addedInThatUser() = testScope.runTest { - val tilesUser0 by collectLastValue(underTest.tilesSpecs(0)) - val tilesUser1 by collectLastValue(underTest.tilesSpecs(1)) - storeTilesForUser("a", 0) storeTilesForUser("b", 1) + val tilesUser0 by collectLastValue(underTest.tilesSpecs(0)) + val tilesUser1 by collectLastValue(underTest.tilesSpecs(1)) + runCurrent() underTest.addTile(userId = 1, TileSpec.create("c")) - assertThat(loadTilesForUser(0)).isEqualTo("a") assertThat(tilesUser0).isEqualTo("a".toTileSpecs()) - assertThat(loadTilesForUser(1)).isEqualTo("b,c") + assertThat(loadTilesForUser(0)).isEqualTo("a") assertThat(tilesUser1).isEqualTo("b,c".toTileSpecs()) - } - - @Test - fun removeTiles() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - storeTilesForUser("a,b", 0) - - underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"))) - - assertThat(loadTilesForUser(0)).isEqualTo("b") - assertThat(tiles).isEqualTo("b".toTileSpecs()) - } - - @Test - fun removeTilesNotThere_noop() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - val specs = "a,b" - storeTilesForUser(specs, 0) - - underTest.removeTiles(userId = 0, listOf(TileSpec.create("c"))) - - assertThat(loadTilesForUser(0)).isEqualTo(specs) - assertThat(tiles).isEqualTo(specs.toTileSpecs()) - } - - @Test - fun removeInvalidTile_noop() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - val specs = "a,b" - storeTilesForUser(specs, 0) - - underTest.removeTiles(userId = 0, listOf(TileSpec.Invalid)) - - assertThat(loadTilesForUser(0)).isEqualTo(specs) - assertThat(tiles).isEqualTo(specs.toTileSpecs()) + assertThat(loadTilesForUser(1)).isEqualTo("b,c") } @Test fun removeTileFromSecondaryUser_removedOnlyInCorrectUser() = testScope.runTest { - val user0Tiles by collectLastValue(underTest.tilesSpecs(0)) - val user1Tiles by collectLastValue(underTest.tilesSpecs(1)) - val specs = "a,b" storeTilesForUser(specs, 0) storeTilesForUser(specs, 1) + val user0Tiles by collectLastValue(underTest.tilesSpecs(0)) + val user1Tiles by collectLastValue(underTest.tilesSpecs(1)) + runCurrent() underTest.removeTiles(userId = 1, listOf(TileSpec.create("a"))) - assertThat(loadTilesForUser(0)).isEqualTo(specs) assertThat(user0Tiles).isEqualTo(specs.toTileSpecs()) - assertThat(loadTilesForUser(1)).isEqualTo("b") - assertThat(user1Tiles).isEqualTo("b".toTileSpecs()) - } - - @Test - fun removeMultipleTiles() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - storeTilesForUser("a,b,c,d", 0) - - underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"), TileSpec.create("c"))) - - assertThat(loadTilesForUser(0)).isEqualTo("b,d") - assertThat(tiles).isEqualTo("b,d".toTileSpecs()) - } - - @Test - fun changeTiles() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - val specs = "a,custom(b/c)" - - underTest.setTiles(userId = 0, specs.toTileSpecs()) - - assertThat(loadTilesForUser(0)).isEqualTo(specs) - assertThat(tiles).isEqualTo(specs.toTileSpecs()) - } - - @Test - fun changeTiles_ignoresInvalid() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - val specs = "a,custom(b/c)" - - underTest.setTiles(userId = 0, listOf(TileSpec.Invalid) + specs.toTileSpecs()) - assertThat(loadTilesForUser(0)).isEqualTo(specs) - assertThat(tiles).isEqualTo(specs.toTileSpecs()) - } - - @Test - fun changeTiles_empty_noChanges() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - underTest.setTiles(userId = 0, emptyList()) - - assertThat(loadTilesForUser(0)).isNull() - assertThat(tiles).isEqualTo(getDefaultTileSpecs()) + assertThat(user1Tiles).isEqualTo("b".toTileSpecs()) + assertThat(loadTilesForUser(1)).isEqualTo("b") } @Test fun changeTiles_forCorrectUser() = testScope.runTest { - val user0Tiles by collectLastValue(underTest.tilesSpecs(0)) - val user1Tiles by collectLastValue(underTest.tilesSpecs(1)) - val specs = "a" storeTilesForUser(specs, 0) storeTilesForUser(specs, 1) + val user0Tiles by collectLastValue(underTest.tilesSpecs(0)) + val user1Tiles by collectLastValue(underTest.tilesSpecs(1)) + runCurrent() underTest.setTiles(userId = 1, "b".toTileSpecs()) - assertThat(loadTilesForUser(0)).isEqualTo("a") assertThat(user0Tiles).isEqualTo(specs.toTileSpecs()) + assertThat(loadTilesForUser(0)).isEqualTo("a") - assertThat(loadTilesForUser(1)).isEqualTo("b") assertThat(user1Tiles).isEqualTo("b".toTileSpecs()) - } - - @Test - fun multipleConcurrentRemovals_bothRemoved() = - testScope.runTest { - val tiles by collectLastValue(underTest.tilesSpecs(0)) - - val specs = "a,b,c" - storeTilesForUser(specs, 0) - - coroutineScope { - underTest.removeTiles(userId = 0, listOf(TileSpec.create("c"))) - underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"))) - } - - assertThat(loadTilesForUser(0)).isEqualTo("b") - assertThat(tiles).isEqualTo("b".toTileSpecs()) + assertThat(loadTilesForUser(1)).isEqualTo("b") } @Test @@ -361,6 +170,7 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { retailModeRepository.setRetailMode(true) val tiles by collectLastValue(underTest.tilesSpecs(0)) + runCurrent() assertThat(tiles).isEqualTo(RETAIL_TILES.toTileSpecs()) } @@ -369,25 +179,13 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { fun retailMode_cannotModifyTiles() = testScope.runTest { retailModeRepository.setRetailMode(true) - - underTest.setTiles(0, DEFAULT_TILES.toTileSpecs()) - - assertThat(loadTilesForUser(0)).isNull() - } - - @Test - fun emptyTilesReplacedByDefaultInSettings() = - testScope.runTest { val tiles by collectLastValue(underTest.tilesSpecs(0)) runCurrent() - assertThat(loadTilesForUser(0)) - .isEqualTo(getDefaultTileSpecs().map { it.spec }.joinToString(",")) - } + underTest.setTiles(0, listOf(TileSpec.create("a"))) - private fun getDefaultTileSpecs(): List<TileSpec> { - return QSHost.getDefaultSpecs(context.resources).map(TileSpec::create) - } + assertThat(loadTilesForUser(0)).isEqualTo(DEFAULT_TILES) + } private fun TestScope.storeTilesForUser(specs: String, forUser: Int) { secureSettings.putStringForUser(SETTING, specs, forUser) @@ -403,8 +201,6 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { private const val RETAIL_TILES = "d" private const val SETTING = Settings.Secure.QS_TILES - private fun String.toTileSpecs(): List<TileSpec> { - return split(",").map(TileSpec::create) - } + private fun String.toTileSpecs() = TilesSettingConverter.toTilesList(this) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt new file mode 100644 index 000000000000..20876237b476 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt @@ -0,0 +1,100 @@ +package com.android.systemui.qs.pipeline.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RoboPilotTest +@SmallTest +@RunWith(AndroidJUnit4::class) +class TilesSettingConverterTest : SysuiTestCase() { + + @Test + fun toTilesList_correctContentAndOrdering() { + val specString = + listOf( + "c", + "b", + "custom(x/y)", + "d", + ) + .joinToString(DELIMITER) + + val expected = + listOf( + TileSpec.create("c"), + TileSpec.create("b"), + TileSpec.create("custom(x/y)"), + TileSpec.create("d"), + ) + + assertThat(TilesSettingConverter.toTilesList(specString)).isEqualTo(expected) + } + + @Test + fun toTilesList_removesInvalid() { + val specString = + listOf( + "a", + "", + "b", + ) + .joinToString(DELIMITER) + assertThat(TileSpec.create("")).isEqualTo(TileSpec.Invalid) + val expected = + listOf( + TileSpec.create("a"), + TileSpec.create("b"), + ) + assertThat(TilesSettingConverter.toTilesList(specString)).isEqualTo(expected) + } + + @Test + fun toTilesSet_correctContent() { + val specString = + listOf( + "c", + "b", + "custom(x/y)", + "d", + ) + .joinToString(DELIMITER) + + val expected = + setOf( + TileSpec.create("c"), + TileSpec.create("b"), + TileSpec.create("custom(x/y)"), + TileSpec.create("d"), + ) + + assertThat(TilesSettingConverter.toTilesSet(specString)).isEqualTo(expected) + } + + @Test + fun toTilesSet_removesInvalid() { + val specString = + listOf( + "a", + "", + "b", + ) + .joinToString(DELIMITER) + assertThat(TileSpec.create("")).isEqualTo(TileSpec.Invalid) + val expected = + setOf( + TileSpec.create("a"), + TileSpec.create("b"), + ) + assertThat(TilesSettingConverter.toTilesSet(specString)).isEqualTo(expected) + } + + companion object { + private const val DELIMITER = "," + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt new file mode 100644 index 000000000000..81fd72b11227 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt @@ -0,0 +1,160 @@ +package com.android.systemui.qs.pipeline.data.repository + +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +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 +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@RoboPilotTest +@SmallTest +@RunWith(AndroidJUnit4::class) +class UserAutoAddRepositoryTest : SysuiTestCase() { + private val secureSettings = FakeSettings() + + @Mock private lateinit var logger: QSPipelineLogger + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private lateinit var underTest: UserAutoAddRepository + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + underTest = + UserAutoAddRepository( + USER, + secureSettings, + logger, + testScope.backgroundScope, + testDispatcher, + ) + } + + @Test + fun nonExistentSetting_emptySet() = + testScope.runTest { + val specs by collectLastValue(underTest.autoAdded()) + + assertThat(specs).isEmpty() + } + + @Test + fun settingsChange_noChanges() = + testScope.runTest { + val value = "a,custom(b/c)" + store(value) + val specs by collectLastValue(underTest.autoAdded()) + runCurrent() + + assertThat(specs).isEqualTo(value.toTilesSet()) + + val newValue = "a" + store(newValue) + + assertThat(specs).isEqualTo(value.toTilesSet()) + } + + @Test + fun noInvalidTileSpecs() = + testScope.runTest { + val specs = "d,custom(bad)" + store(specs) + val tiles by collectLastValue(underTest.autoAdded()) + runCurrent() + + assertThat(tiles).isEqualTo("d".toTilesSet()) + } + + @Test + fun markAdded() = + testScope.runTest { + val specs = mutableSetOf(TileSpec.create("a")) + val autoAdded by collectLastValue(underTest.autoAdded()) + runCurrent() + + underTest.markTileAdded(TileSpec.create("a")) + + assertThat(autoAdded).containsExactlyElementsIn(specs) + + specs.add(TileSpec.create("b")) + underTest.markTileAdded(TileSpec.create("b")) + + assertThat(autoAdded).containsExactlyElementsIn(specs) + } + + @Test + fun markAdded_Invalid_noop() = + testScope.runTest { + val autoAdded by collectLastValue(underTest.autoAdded()) + runCurrent() + + underTest.markTileAdded(TileSpec.Invalid) + + Truth.assertThat(autoAdded).isEmpty() + } + + @Test + fun unmarkAdded() = + testScope.runTest { + val specs = "a,custom(b/c)" + store(specs) + val autoAdded by collectLastValue(underTest.autoAdded()) + runCurrent() + + underTest.unmarkTileAdded(TileSpec.create("a")) + + assertThat(autoAdded).containsExactlyElementsIn(setOf(TileSpec.create("custom(b/c)"))) + } + + @Test + fun restore_addsRestoredTiles() = + testScope.runTest { + val specs = "a,b" + val restored = "b,c" + store(specs) + val autoAdded by collectLastValue(underTest.autoAdded()) + runCurrent() + + val restoreData = + RestoreData( + emptyList(), + restored.toTilesSet(), + USER, + ) + underTest.reconcileRestore(restoreData) + + assertThat(autoAdded).containsExactlyElementsIn("a,b,c".toTilesSet()) + } + + private fun store(specs: String) { + secureSettings.putStringForUser(SETTING, specs, USER) + } + + companion object { + private const val USER = 10 + private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES + + private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt new file mode 100644 index 000000000000..389580c1326b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt @@ -0,0 +1,351 @@ +package com.android.systemui.qs.pipeline.data.repository + +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.test.StandardTestDispatcher +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 +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@RoboPilotTest +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class UserTileSpecRepositoryTest : SysuiTestCase() { + private val secureSettings = FakeSettings() + private val defaultTilesRepository = + object : DefaultTilesRepository { + override val defaultTiles: List<TileSpec> + get() = DEFAULT_TILES.toTileSpecs() + } + + @Mock private lateinit var logger: QSPipelineLogger + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private lateinit var underTest: UserTileSpecRepository + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + underTest = + UserTileSpecRepository( + USER, + defaultTilesRepository, + secureSettings, + logger, + testScope.backgroundScope, + testDispatcher, + ) + } + + @Test + fun emptySetting_usesDefaultValue() = + testScope.runTest { + val tiles by collectLastValue(underTest.tiles()) + assertThat(tiles).isEqualTo(getDefaultTileSpecs()) + } + + @Test + fun changeInSettings_valueDoesntChange() = + testScope.runTest { + storeTiles("a") + val tiles by collectLastValue(underTest.tiles()) + + assertThat(tiles).isEqualTo(listOf(TileSpec.create("a"))) + + storeTiles("a,custom(b/c)") + assertThat(tiles).isEqualTo(listOf(TileSpec.create("a"))) + } + + @Test + fun changeInSettings_settingIsRestored() = + testScope.runTest { + storeTiles("a") + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + storeTiles("a,custom(b/c)") + assertThat(loadTiles()).isEqualTo("a") + } + + @Test + fun invalidTilesAreNotPresent() = + testScope.runTest { + val specs = "d,custom(bad)" + storeTiles(specs) + + val tiles by collectLastValue(underTest.tiles()) + + assertThat(tiles).isEqualTo(specs.toTileSpecs().filter { it != TileSpec.Invalid }) + } + + @Test + fun noValidTiles_defaultSet() = + testScope.runTest { + storeTiles("custom(bad),custom()") + + val tiles by collectLastValue(underTest.tiles()) + + assertThat(tiles).isEqualTo(getDefaultTileSpecs()) + } + + /* + * Following tests are for the possible actions that can be performed to the list of tiles. + * In general, the tests follow this scheme: + * + * 1. Set starting tiles in Settings + * 2. Start collection of flows + * 3. Call `runCurrent` so all collectors are started (side effects) + * 4. Perform operation + * 5. Check that the flow contains the right value + * 6. Check that settings contains the right value. + */ + + @Test + fun addTileAtEnd() = + testScope.runTest { + storeTiles("a") + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + underTest.addTile(TileSpec.create("b")) + + val expected = "a,b" + assertThat(tiles).isEqualTo(expected.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(expected) + } + + @Test + fun addTileAtPosition() = + testScope.runTest { + storeTiles("a,custom(b/c)") + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + underTest.addTile(TileSpec.create("d"), position = 1) + + val expected = "a,d,custom(b/c)" + assertThat(tiles).isEqualTo(expected.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(expected) + } + + @Test + fun addInvalidTile_noop() = + testScope.runTest { + val specs = "a,custom(b/c)" + storeTiles(specs) + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + underTest.addTile(TileSpec.Invalid) + + assertThat(tiles).isEqualTo(specs.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(specs) + } + + @Test + fun addTileAtPosition_tooLarge_addedAtEnd() = + testScope.runTest { + val specs = "a,custom(b/c)" + storeTiles(specs) + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + underTest.addTile(TileSpec.create("d"), position = 100) + + val expected = "a,custom(b/c),d" + assertThat(tiles).isEqualTo(expected.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(expected) + } + + @Test + fun removeTiles() = + testScope.runTest { + storeTiles("a,b") + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + underTest.removeTiles(listOf(TileSpec.create("a"))) + + assertThat(tiles).isEqualTo("b".toTileSpecs()) + assertThat(loadTiles()).isEqualTo("b") + } + + @Test + fun removeTilesNotThere_noop() = + testScope.runTest { + val specs = "a,b" + storeTiles(specs) + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + underTest.removeTiles(listOf(TileSpec.create("c"))) + + assertThat(tiles).isEqualTo(specs.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(specs) + } + + @Test + fun removeInvalidTile_noop() = + testScope.runTest { + val specs = "a,b" + storeTiles(specs) + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + underTest.removeTiles(listOf(TileSpec.Invalid)) + + assertThat(tiles).isEqualTo(specs.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(specs) + } + + @Test + fun removeMultipleTiles() = + testScope.runTest { + storeTiles("a,b,c,d") + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + underTest.removeTiles(listOf(TileSpec.create("a"), TileSpec.create("c"))) + + assertThat(tiles).isEqualTo("b,d".toTileSpecs()) + assertThat(loadTiles()).isEqualTo("b,d") + } + + @Test + fun changeTiles() = + testScope.runTest { + val specs = "a,custom(b/c)" + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + underTest.setTiles(specs.toTileSpecs()) + + assertThat(tiles).isEqualTo(specs.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(specs) + } + + @Test + fun changeTiles_ignoresInvalid() = + testScope.runTest { + val specs = "a,custom(b/c)" + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + underTest.setTiles(listOf(TileSpec.Invalid) + specs.toTileSpecs()) + + assertThat(tiles).isEqualTo(specs.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(specs) + } + + @Test + fun changeTiles_empty_noChanges() = + testScope.runTest { + val specs = "a,b,c,d" + storeTiles(specs) + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + underTest.setTiles(emptyList()) + + assertThat(tiles).isEqualTo(specs.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(specs) + } + + @Test + fun multipleConcurrentRemovals_bothRemoved() = + testScope.runTest { + val specs = "a,b,c" + storeTiles(specs) + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + coroutineScope { + underTest.removeTiles(listOf(TileSpec.create("c"))) + underTest.removeTiles(listOf(TileSpec.create("a"))) + } + + assertThat(tiles).isEqualTo("b".toTileSpecs()) + assertThat(loadTiles()).isEqualTo("b") + } + + @Test + fun emptyTilesReplacedByDefaultInSettings() = + testScope.runTest { + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + assertThat(loadTiles()) + .isEqualTo(getDefaultTileSpecs().map { it.spec }.joinToString(",")) + } + + @Test + fun restoreDataIsProperlyReconciled() = + testScope.runTest { + // Tile b was just auto-added, so we should re-add it in position 1 + // Tile e was auto-added before, but the user had removed it (not in the restored set). + // It should not be re-added + val specsBeforeRestore = "a,b,c,d,e" + val restoredSpecs = "a,c,d,f" + val autoAddedBeforeRestore = "b,d" + val restoredAutoAdded = "d,e" + + storeTiles(specsBeforeRestore) + val tiles by collectLastValue(underTest.tiles()) + runCurrent() + + val restoreData = + RestoreData( + restoredSpecs.toTileSpecs(), + restoredAutoAdded.toTilesSet(), + USER, + ) + underTest.reconcileRestore(restoreData, autoAddedBeforeRestore.toTilesSet()) + runCurrent() + + val expected = "a,b,c,d,f" + assertThat(tiles).isEqualTo(expected.toTileSpecs()) + assertThat(loadTiles()).isEqualTo(expected) + } + + private fun getDefaultTileSpecs(): List<TileSpec> { + return defaultTilesRepository.defaultTiles + } + + private fun TestScope.storeTiles(specs: String) { + secureSettings.putStringForUser(SETTING, specs, USER) + runCurrent() + } + + private fun loadTiles(): String? { + return secureSettings.getStringForUser(SETTING, USER) + } + + companion object { + private const val USER = 10 + private const val DEFAULT_TILES = "a,b,c" + private const val SETTING = Settings.Secure.QS_TILES + + private fun String.toTileSpecs() = TilesSettingConverter.toTilesList(this) + private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt index dc1b9c4d0d14..a7505240caeb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt @@ -46,6 +46,7 @@ import com.android.systemui.qs.pipeline.domain.model.TileModel import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.qs.tiles.di.NewQSTileFactory import com.android.systemui.qs.toProto import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.repository.FakeUserRepository @@ -91,6 +92,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { @Mock private lateinit var logger: QSPipelineLogger + @Mock private lateinit var newQSTileFactory: NewQSTileFactory + private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -105,6 +108,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true) featureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, true) + // TODO(b/299909337): Add test checking the new factory is used when the flag is on + featureFlags.set(Flags.QS_PIPELINE_NEW_TILES, true) userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1)) @@ -117,6 +122,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { userRepository = userRepository, customTileStatePersister = customTileStatePersister, tileFactory = tileFactory, + newQSTileFactory = { newQSTileFactory }, customTileAddedRepository = customTileAddedRepository, tileLifecycleManagerFactory = tileLifecycleManagerFactory, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt new file mode 100644 index 000000000000..2e6b50b637dd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt @@ -0,0 +1,94 @@ +package com.android.systemui.qs.pipeline.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.qs.pipeline.data.model.RestoreData +import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository +import com.android.systemui.qs.pipeline.data.repository.FakeQSSettingsRestoredRepository +import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository +import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +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 +import org.mockito.MockitoAnnotations + +@RoboPilotTest +@RunWith(AndroidJUnit4::class) +@SmallTest +class RestoreReconciliationInteractorTest : SysuiTestCase() { + + private val tileSpecRepository = FakeTileSpecRepository() + private val autoAddRepository = FakeAutoAddRepository() + + private val qsSettingsRestoredRepository = FakeQSSettingsRestoredRepository() + + private lateinit var underTest: RestoreReconciliationInteractor + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + underTest = + RestoreReconciliationInteractor( + tileSpecRepository, + autoAddRepository, + qsSettingsRestoredRepository, + testScope.backgroundScope, + testDispatcher + ) + underTest.start() + } + + @Test + fun reconciliationInCorrectOrder_hascurrentAutoAdded() = + testScope.runTest { + val user = 10 + val tiles by collectLastValue(tileSpecRepository.tilesSpecs(user)) + val autoAdd by collectLastValue(autoAddRepository.autoAddedTiles(user)) + + // Tile b was just auto-added, so we should re-add it in position 1 + // Tile e was auto-added before, but the user had removed it (not in the restored set). + // It should not be re-added + val specsBeforeRestore = "a,b,c,d,e" + val restoredSpecs = "a,c,d,f" + val autoAddedBeforeRestore = "b,d" + val restoredAutoAdded = "d,e" + + val restoreData = + RestoreData( + restoredSpecs.toTilesList(), + restoredAutoAdded.toTilesSet(), + user, + ) + + autoAddedBeforeRestore.toTilesSet().forEach { + autoAddRepository.markTileAdded(user, it) + } + tileSpecRepository.setTiles(user, specsBeforeRestore.toTilesList()) + + qsSettingsRestoredRepository.onDataRestored(restoreData) + runCurrent() + + val expectedTiles = "a,b,c,d,f" + assertThat(tiles).isEqualTo(expectedTiles.toTilesList()) + + val expectedAutoAdd = "b,d,e" + assertThat(autoAdd).isEqualTo(expectedAutoAdd.toTilesSet()) + } + + companion object { + private fun String.toTilesList() = TilesSettingConverter.toTilesList(this) + private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt index e222542e5c53..067218a9c983 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt @@ -51,16 +51,17 @@ import com.android.systemui.qs.tiles.ScreenRecordTile import com.android.systemui.qs.tiles.UiModeNightTile import com.android.systemui.qs.tiles.WorkModeTile import com.android.systemui.util.leak.GarbageMonitor +import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat -import javax.inject.Provider import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Answers +import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito.inOrder -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import javax.inject.Provider +import org.mockito.Mockito.`when` as whenever private val specMap = mapOf( "internet" to InternetTile::class.java, @@ -98,7 +99,7 @@ private val specMap = mapOf( class QSFactoryImplTest : SysuiTestCase() { @Mock private lateinit var qsHost: QSHost - @Mock(answer = Answers.RETURNS_SELF) private lateinit var customTileBuilder: CustomTile.Builder + @Mock private lateinit var customTileFactory: CustomTile.Factory @Mock private lateinit var customTile: CustomTile @Mock private lateinit var internetTile: InternetTile @@ -139,7 +140,7 @@ class QSFactoryImplTest : SysuiTestCase() { whenever(qsHost.context).thenReturn(mContext) whenever(qsHost.userContext).thenReturn(mContext) - whenever(customTileBuilder.build()).thenReturn(customTile) + whenever(customTileFactory.create(anyString(), any())).thenReturn(customTile) val tileMap = mutableMapOf<String, Provider<QSTileImpl<*>>>( "internet" to Provider { internetTile }, @@ -174,7 +175,7 @@ class QSFactoryImplTest : SysuiTestCase() { factory = QSFactoryImpl( { qsHost }, - { customTileBuilder }, + { customTileFactory }, tileMap, ) // When adding/removing tiles, fix also [specMap] and [tileMap] diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt index bce4c06d320f..3bf59ca62024 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -172,6 +172,9 @@ class DeviceControlsTileTest : SysuiTestCase() { @Test fun testNotAvailableControls() { featureEnabled = false + + // Destroy previous tile + tile.destroy() tile = createTile() assertThat(tile.isAvailable).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java index a0c107340dcd..954d30edf143 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java @@ -226,6 +226,10 @@ public class DreamTileTest extends SysuiTestCase { assertTrue(supportedTileOnlySystemUser.isAvailable()); when(mUserTracker.getUserInfo()).thenReturn(nonMainUserInfo); assertFalse(supportedTileOnlySystemUser.isAvailable()); + + destroyTile(unsupportedTile); + destroyTile(supportedTileAllUsers); + destroyTile(supportedTileOnlySystemUser); } @Test @@ -250,6 +254,8 @@ public class DreamTileTest extends SysuiTestCase { mTestableLooper.processAllMessages(); assertEquals(QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked), dockedTile.getState().icon); + + destroyTile(dockedTile); } private void setScreensaverEnabled(boolean enabled) { @@ -257,6 +263,11 @@ public class DreamTileTest extends SysuiTestCase { DEFAULT_USER); } + private void destroyTile(QSTileImpl<?> tile) { + tile.destroy(); + mTestableLooper.processAllMessages(); + } + private DreamTile constructTileForTest(boolean dreamSupported, boolean dreamOnlyEnabledForSystemUser) { return new DreamTile( diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java index df6993d11447..440270b6ebfa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java @@ -216,7 +216,7 @@ public class RotationLockTileTest extends SysuiTestCase { public void testSecondaryString_rotationResolverDisabled_isEmpty() { mTestableResources.addOverride(com.android.internal.R.bool.config_allowRotationResolver, false); - mLockTile = new RotationLockTile( + RotationLockTile otherTile = new RotationLockTile( mHost, mUiEventLogger, mTestableLooper.getLooper(), @@ -232,10 +232,12 @@ public class RotationLockTileTest extends SysuiTestCase { new FakeSettings() ); - mLockTile.refreshState(); + otherTile.refreshState(); mTestableLooper.processAllMessages(); - assertEquals("", mLockTile.getState().secondaryLabel.toString()); + assertEquals("", otherTile.getState().secondaryLabel.toString()); + + destroyTile(otherTile); } @Test @@ -258,6 +260,12 @@ public class RotationLockTileTest extends SysuiTestCase { assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_auto_rotate_icon_on)); } + + private void destroyTile(QSTileImpl<?> tile) { + tile.destroy(); + mTestableLooper.processAllMessages(); + } + private void enableAutoRotation() { when(mRotationPolicyWrapper.isRotationLocked()).thenReturn(false); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt index 47b4244e0910..06b7a9fde873 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt @@ -1,12 +1,27 @@ -package com.android.systemui.qs.tiles.base +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.base.actions import android.content.Intent -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserActionHandler -import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -17,7 +32,8 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RoboPilotTest +@RunWith(AndroidJUnit4::class) class QSTileIntentUserActionHandlerTest : SysuiTestCase() { @Mock private lateinit var activityStarted: ActivityStarter @@ -34,7 +50,7 @@ class QSTileIntentUserActionHandlerTest : SysuiTestCase() { fun testPassesIntentToStarter() { val intent = Intent("test.ACTION") - underTest.handle(QSTileUserAction.Click(context, null), intent) + underTest.handle(null, intent) verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt new file mode 100644 index 000000000000..4f25d12aea49 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.base.interactor + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.RestrictedLockUtils +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RoboPilotTest +@RunWith(AndroidJUnit4::class) +class DisabledByPolicyInteractorTest : SysuiTestCase() { + + @Mock private lateinit var restrictedLockProxy: RestrictedLockProxy + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var context: Context + + @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent> + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + lateinit var underTest: DisabledByPolicyInteractor + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = + DisabledByPolicyInteractorImpl( + context, + activityStarter, + restrictedLockProxy, + testDispatcher, + ) + } + + @Test + fun testEnabledWhenNoAdmin() = + testScope.runTest { + whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(null) + + assertThat(underTest.isDisabled(TEST_USER, TEST_RESTRICTION)) + .isSameInstanceAs(DisabledByPolicyInteractor.PolicyResult.TileEnabled) + } + + @Test + fun testDisabledWhenAdminWithNoRestrictions() = + testScope.runTest { + val admin = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER)) + whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(admin) + whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString())) + .thenReturn(false) + + val result = + underTest.isDisabled(TEST_USER, TEST_RESTRICTION) + as DisabledByPolicyInteractor.PolicyResult.TileDisabled + assertThat(result.admin).isEqualTo(admin) + } + + @Test + fun testEnabledWhenAdminWithRestrictions() = + testScope.runTest { + whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(ADMIN) + whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString())) + .thenReturn(true) + + assertThat(underTest.isDisabled(TEST_USER, TEST_RESTRICTION)) + .isSameInstanceAs(DisabledByPolicyInteractor.PolicyResult.TileEnabled) + } + + @Test + fun testHandleDisabledByPolicy() { + val result = + underTest.handlePolicyResult( + DisabledByPolicyInteractor.PolicyResult.TileDisabled(ADMIN) + ) + + val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, ADMIN) + assertThat(result).isTrue() + verify(activityStarter).postStartActivityDismissingKeyguard(intentCaptor.capture(), any()) + assertThat(intentCaptor.value.filterEquals(expectedIntent)).isTrue() + } + + @Test + fun testHandleEnabled() { + val result = + underTest.handlePolicyResult(DisabledByPolicyInteractor.PolicyResult.TileEnabled) + + assertThat(result).isFalse() + verify(activityStarter, never()) + .postStartActivityDismissingKeyguard(intentCaptor.capture(), any()) + } + + private companion object { + const val TEST_USER = 1 + const val TEST_RESTRICTION = "test_restriction" + + val TEST_COMPONENT_NAME = ComponentName("test.pkg", "test.cls") + + val ADMIN = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt index 643866e3cade..9024c6c5576b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt @@ -1,12 +1,16 @@ package com.android.systemui.qs.tiles.viewmodel -import android.graphics.drawable.Icon -import android.testing.AndroidTestingRunner +import android.graphics.drawable.ShapeDrawable import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest +import com.android.internal.logging.InstanceId import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest @@ -26,12 +30,13 @@ import org.junit.runner.RunWith // TODO(b/299909368): Add more tests @MediumTest @RoboPilotTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>() private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>() + private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor() private val testCoroutineDispatcher = StandardTestDispatcher() private val testScope = TestScope(testCoroutineDispatcher) @@ -65,26 +70,27 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { scope: TestScope, config: QSTileConfig = TEST_QS_TILE_CONFIG, ): QSTileViewModel = - object : - BaseQSTileViewModel<Any>( - config, - fakeQSTileUserActionInteractor, - fakeQSTileDataInteractor, - object : QSTileDataToStateMapper<Any> { - override fun map(config: QSTileConfig, data: Any): QSTileState { - return QSTileState(config.tileIcon, config.tileLabel) - } - }, - testCoroutineDispatcher, - tileScope = scope.backgroundScope, - ) {} + BaseQSTileViewModel( + config, + fakeQSTileUserActionInteractor, + fakeQSTileDataInteractor, + object : QSTileDataToStateMapper<Any> { + override fun map(config: QSTileConfig, data: Any): QSTileState = + QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {} + }, + fakeDisabledByPolicyInteractor, + testCoroutineDispatcher, + scope.backgroundScope, + ) private companion object { + val TEST_QS_TILE_CONFIG = QSTileConfig( TileSpec.create("default"), - Icon.createWithContentUri(""), - "", + Icon.Loaded(ShapeDrawable(), null), + 0, + InstanceId.fakeInstanceId(0), ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 8ae89304974d..f1c99d71b4e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -48,11 +48,6 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = - utils.authenticationInteractor( - repository = utils.authenticationRepository(), - ) - private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private var mobileIconsViewModel: MobileIconsViewModel = @@ -85,10 +80,17 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { broadcastDispatcher = fakeBroadcastDispatcher, ) + val authenticationInteractor = utils.authenticationInteractor() + underTest = QuickSettingsSceneViewModel( bouncerInteractor = utils.bouncerInteractor( + deviceEntryInteractor = + utils.deviceEntryInteractor( + authenticationInteractor = authenticationInteractor, + sceneInteractor = sceneInteractor, + ), authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ), @@ -101,7 +103,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(true) + utils.deviceEntryRepository.setUnlocked(true) runCurrent() underTest.onContentClicked() @@ -114,7 +116,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) runCurrent() underTest.onContentClicked() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 85bd92bf3b12..5259013afc95 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -83,7 +83,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val sceneContainerConfig = utils.fakeSceneContainerConfig() private val sceneRepository = utils.fakeSceneContainerRepository( @@ -93,14 +92,12 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { utils.sceneInteractor( repository = sceneRepository, ) - - private val authenticationRepository = utils.authenticationRepository() - private val authenticationInteractor = - utils.authenticationInteractor( - repository = authenticationRepository, + private val authenticationInteractor = utils.authenticationInteractor() + private val deviceEntryInteractor = + utils.deviceEntryInteractor( + authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ) - private val communalInteractor = utils.communalInteractor() private val transitionState = @@ -116,6 +113,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val bouncerInteractor = utils.bouncerInteractor( + deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ) @@ -128,7 +126,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val lockscreenSceneViewModel = LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, - authenticationInteractor = authenticationInteractor, + deviceEntryInteractor = deviceEntryInteractor, communalInteractor = communalInteractor, longPress = KeyguardLongPressViewModel( @@ -178,12 +176,12 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { shadeSceneViewModel = ShadeSceneViewModel( applicationScope = testScope.backgroundScope, - authenticationInteractor = authenticationInteractor, + deviceEntryInteractor = deviceEntryInteractor, bouncerInteractor = bouncerInteractor, shadeHeaderViewModel = shadeHeaderViewModel, ) - authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) val displayTracker = FakeDisplayTracker(context) val sysUiState = SysUiState(displayTracker) @@ -191,6 +189,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { SceneContainerStartable( applicationScope = testScope.backgroundScope, sceneInteractor = sceneInteractor, + deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, keyguardInteractor = keyguardInteractor, flags = utils.sceneContainerFlags, @@ -417,13 +416,13 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit // is not an observable that can trigger a new evaluation. - authenticationRepository.setLockscreenEnabled( + utils.deviceEntryRepository.setInsecureLockscreenEnabled( authMethod !is DomainLayerAuthenticationMethodModel.None ) - authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer()) + utils.authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer()) if (!authMethod.isSecure) { // When the auth method is not secure, the device is never considered locked. - authenticationRepository.setUnlocked(true) + utils.deviceEntryRepository.setUnlocked(true) } runCurrent() } @@ -528,14 +527,14 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(authMethod.isSecure) .isTrue() - authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) runCurrent() } /** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */ private fun TestScope.unlockDevice() { assertWithMessage("Cannot unlock a device that's already unlocked!") - .that(authenticationInteractor.isUnlocked.value) + .that(deviceEntryInteractor.isUnlocked.value) .isFalse() emulateUserDrivenTransition(SceneKey.Bouncer) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 16fdf8e40ed1..00a20ccc1c0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -59,17 +59,13 @@ class SceneContainerStartableTest : SysuiTestCase() { private val testScope = utils.testScope private val sceneInteractor = utils.sceneInteractor() private val sceneContainerFlags = utils.sceneContainerFlags - private val authenticationRepository = utils.authenticationRepository() - private val authenticationInteractor = - utils.authenticationInteractor( - repository = authenticationRepository, + private val authenticationInteractor = utils.authenticationInteractor() + private val deviceEntryInteractor = + utils.deviceEntryInteractor( + authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ) - private val keyguardRepository = utils.keyguardRepository - private val keyguardInteractor = - utils.keyguardInteractor( - repository = keyguardRepository, - ) + private val keyguardInteractor = utils.keyguardInteractor() private val sysUiState: SysUiState = mock() private val falsingCollector: FalsingCollector = mock() @@ -77,6 +73,7 @@ class SceneContainerStartableTest : SysuiTestCase() { SceneContainerStartable( applicationScope = testScope.backgroundScope, sceneInteractor = sceneInteractor, + deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, keyguardInteractor = keyguardInteractor, flags = sceneContainerFlags, @@ -141,7 +138,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) underTest.start() - authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -157,7 +154,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) underTest.start() - authenticationRepository.setUnlocked(true) + utils.deviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -173,7 +170,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - authenticationRepository.setUnlocked(true) + utils.deviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -189,7 +186,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - authenticationRepository.setUnlocked(true) + utils.deviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -205,7 +202,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) underTest.start() - keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP) + utils.keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -251,7 +248,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -267,7 +264,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -283,7 +280,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -300,9 +297,9 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - authenticationRepository.setUnlocked(true) + utils.deviceEntryRepository.setUnlocked(true) runCurrent() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -389,11 +386,11 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() verify(falsingCollector).setShowingAod(false) - keyguardRepository.setIsDozing(true) + utils.keyguardRepository.setIsDozing(true) runCurrent() verify(falsingCollector).setShowingAod(true) - keyguardRepository.setIsDozing(false) + utils.keyguardRepository.setIsDozing(false) runCurrent() verify(falsingCollector, times(2)).setShowingAod(false) } @@ -401,7 +398,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun collectFalsingSignals_screenOnAndOff_aodUnavailable() = testScope.runTest { - keyguardRepository.setAodAvailable(false) + utils.keyguardRepository.setAodAvailable(false) runCurrent() prepareState( initialSceneKey = SceneKey.Lockscreen, @@ -414,31 +411,31 @@ class SceneContainerStartableTest : SysuiTestCase() { verify(falsingCollector, never()).onScreenOnFromTouch() verify(falsingCollector, never()).onScreenOff() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) runCurrent() verify(falsingCollector, times(1)).onScreenTurningOn() verify(falsingCollector, never()).onScreenOnFromTouch() verify(falsingCollector, never()).onScreenOff() - keyguardRepository.setWakefulnessModel(ASLEEP) + utils.keyguardRepository.setWakefulnessModel(ASLEEP) runCurrent() verify(falsingCollector, times(1)).onScreenTurningOn() verify(falsingCollector, never()).onScreenOnFromTouch() verify(falsingCollector, times(1)).onScreenOff() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP) + utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP) runCurrent() verify(falsingCollector, times(1)).onScreenTurningOn() verify(falsingCollector, times(1)).onScreenOnFromTouch() verify(falsingCollector, times(1)).onScreenOff() - keyguardRepository.setWakefulnessModel(ASLEEP) + utils.keyguardRepository.setWakefulnessModel(ASLEEP) runCurrent() verify(falsingCollector, times(1)).onScreenTurningOn() verify(falsingCollector, times(1)).onScreenOnFromTouch() verify(falsingCollector, times(2)).onScreenOff() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) runCurrent() verify(falsingCollector, times(2)).onScreenTurningOn() verify(falsingCollector, times(1)).onScreenOnFromTouch() @@ -448,7 +445,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun collectFalsingSignals_screenOnAndOff_aodAvailable() = testScope.runTest { - keyguardRepository.setAodAvailable(true) + utils.keyguardRepository.setAodAvailable(true) runCurrent() prepareState( initialSceneKey = SceneKey.Lockscreen, @@ -461,31 +458,31 @@ class SceneContainerStartableTest : SysuiTestCase() { verify(falsingCollector, never()).onScreenOnFromTouch() verify(falsingCollector, never()).onScreenOff() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) runCurrent() verify(falsingCollector, never()).onScreenTurningOn() verify(falsingCollector, never()).onScreenOnFromTouch() verify(falsingCollector, never()).onScreenOff() - keyguardRepository.setWakefulnessModel(ASLEEP) + utils.keyguardRepository.setWakefulnessModel(ASLEEP) runCurrent() verify(falsingCollector, never()).onScreenTurningOn() verify(falsingCollector, never()).onScreenOnFromTouch() verify(falsingCollector, never()).onScreenOff() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP) + utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP) runCurrent() verify(falsingCollector, never()).onScreenTurningOn() verify(falsingCollector, never()).onScreenOnFromTouch() verify(falsingCollector, never()).onScreenOff() - keyguardRepository.setWakefulnessModel(ASLEEP) + utils.keyguardRepository.setWakefulnessModel(ASLEEP) runCurrent() verify(falsingCollector, never()).onScreenTurningOn() verify(falsingCollector, never()).onScreenOnFromTouch() verify(falsingCollector, never()).onScreenOff() - keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) + utils.keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON) runCurrent() verify(falsingCollector, never()).onScreenTurningOn() verify(falsingCollector, never()).onScreenOnFromTouch() @@ -521,8 +518,8 @@ class SceneContainerStartableTest : SysuiTestCase() { ): MutableStateFlow<ObservableTransitionState> { assumeTrue(Flags.SCENE_CONTAINER_ENABLED) sceneContainerFlags.enabled = true - authenticationRepository.setUnlocked(isDeviceUnlocked) - keyguardRepository.setBypassEnabled(isBypassEnabled) + utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked) + utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled) val transitionStateFlow = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(SceneKey.Lockscreen) @@ -534,8 +531,10 @@ class SceneContainerStartableTest : SysuiTestCase() { sceneInteractor.onSceneChanged(SceneModel(it), "reason") } authenticationMethod?.let { - authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer()) - authenticationRepository.setLockscreenEnabled( + utils.authenticationRepository.setAuthenticationMethod( + authenticationMethod.toDataLayer() + ) + utils.deviceEntryRepository.setInsecureLockscreenEnabled( authenticationMethod != AuthenticationMethodModel.None ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt new file mode 100644 index 000000000000..091531e435e4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt @@ -0,0 +1,86 @@ +/* + * 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.screenshot + +import android.media.MediaPlayer +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import java.lang.IllegalStateException +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@SmallTest +class ScreenshotSoundControllerTest : SysuiTestCase() { + + private val soundProvider = mock<ScreenshotSoundProvider>() + private val mediaPlayer = mock<MediaPlayer>() + private val bgDispatcher = UnconfinedTestDispatcher() + private val scope = TestScope(bgDispatcher) + @Before + fun setup() { + whenever(soundProvider.getScreenshotSound()).thenReturn(mediaPlayer) + } + + @Test + fun init_soundLoading() { + createController() + bgDispatcher.scheduler.runCurrent() + + verify(soundProvider).getScreenshotSound() + } + + @Test + fun init_soundLoadingException_playAndReleaseDoNotThrow() = runTest { + whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException()) + + val controller = createController() + + controller.playCameraSound().await() + controller.releaseScreenshotSound().await() + + verify(mediaPlayer, never()).start() + verify(mediaPlayer, never()).release() + } + + @Test + fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() = runTest { + val controller = createController() + + controller.playCameraSound().await() + + verify(mediaPlayer).start() + } + + @Test + fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() = runTest { + val controller = createController() + + controller.releaseScreenshotSound().await() + + verify(mediaPlayer).release() + } + + private fun createController() = + ScreenshotSoundControllerImpl(soundProvider, scope, bgDispatcher) +} 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 31c8a3d77d6b..ed731dd41cd3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -118,7 +118,7 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; -import com.android.systemui.qs.QSFragment; +import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeRepository; @@ -291,7 +291,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; @Mock protected ShadeTransitionController mShadeTransitionController; @Mock protected QS mQs; - @Mock protected QSFragment mQSFragment; + @Mock protected QSFragmentLegacy mQSFragment; @Mock protected ViewGroup mQsHeader; @Mock protected ViewParent mViewParent; @Mock protected ViewTreeObserver mViewTreeObserver; diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 2d00e8c24f33..2ed20908ba2f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; @@ -46,22 +47,34 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R; import com.android.systemui.scene.FakeWindowRootViewComponent; +import com.android.systemui.scene.SceneTestUtils; +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; +import com.android.systemui.shade.data.repository.FakeShadeRepository; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; +import com.android.systemui.user.domain.interactor.UserInteractor; import com.google.common.util.concurrent.MoreExecutors; @@ -77,8 +90,10 @@ import org.mockito.Spy; import java.util.List; import java.util.concurrent.Executor; +import kotlinx.coroutines.test.TestScope; + @RunWith(AndroidTestingRunner.class) -@RunWithLooper +@RunWithLooper(setAsMainLooper = true) @SmallTest public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @@ -102,6 +117,9 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener; private final Executor mBackgroundExecutor = MoreExecutors.directExecutor(); + private SceneTestUtils mUtils = new SceneTestUtils(this); + private TestScope mTestScope = mUtils.getTestScope(); + private ShadeInteractor mShadeInteractor; private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; private float mPreferredRefreshRate = -1; @@ -119,6 +137,23 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { when(mDozeParameters.getAlwaysOn()).thenReturn(true); when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); + mShadeInteractor = + new ShadeInteractor( + mTestScope.getBackgroundScope(), + new FakeDisableFlagsRepository(), + new FakeSceneContainerFlags(), + mUtils::sceneInteractor, + new FakeKeyguardRepository(), + new FakeUserSetupRepository(), + mock(DeviceProvisionedController.class), + mock(UserInteractor.class), + new SharedNotificationContainerInteractor( + new FakeConfigurationRepository(), + mContext, + new ResourcesSplitShadeStateController()), + new FakeShadeRepository() + ); + mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl( mContext, new FakeWindowRootViewComponent.Factory(mNotificationShadeWindowView), @@ -136,6 +171,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager, + () -> mShadeInteractor, mShadeWindowLogger) { @Override protected boolean isDebuggable() { @@ -272,9 +308,9 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Test public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() { - mNotificationShadeWindowController.onShadeExpansionFullyChanged(true); + mNotificationShadeWindowController.onShadeOrQsExpanded(true); clearInvocations(mWindowManager); - mNotificationShadeWindowController.onShadeExpansionFullyChanged(true); + mNotificationShadeWindowController.onShadeOrQsExpanded(true); verifyNoMoreInteractions(mWindowManager); mNotificationShadeWindowController.setNotificationShadeFocusable(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt index f7d2497d0bbf..0c3af03da59e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQuickSettingsContainerTest.kt @@ -22,9 +22,9 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.constraintlayout.widget.ConstraintLayout import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase -import com.android.systemui.qs.QSFragment +import com.android.systemui.qs.QSFragmentLegacy +import com.android.systemui.res.R import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -40,7 +40,7 @@ class NotificationsQuickSettingsContainerTest : SysuiTestCase() { @Mock private lateinit var qsFrame: View @Mock private lateinit var stackScroller: View @Mock private lateinit var keyguardStatusBar: View - @Mock private lateinit var qsFragment: QSFragment + @Mock private lateinit var qsFragment: QSFragmentLegacy private lateinit var qsView: ViewGroup private lateinit var qsContainer: View diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index fb0d4db4840a..8138b328a3c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -34,7 +34,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.dump.DumpManager; @@ -46,7 +45,8 @@ import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; -import com.android.systemui.qs.QSFragment; +import com.android.systemui.qs.QSFragmentLegacy; +import com.android.systemui.res.R; import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; import com.android.systemui.screenrecord.RecordingController; @@ -75,18 +75,17 @@ import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.user.domain.interactor.UserInteractor; import com.android.systemui.util.kotlin.JavaAdapter; -import dagger.Lazy; - import org.junit.After; import org.junit.Before; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import dagger.Lazy; import kotlinx.coroutines.test.TestScope; public class QuickSettingsControllerBaseTest extends SysuiTestCase { @@ -109,7 +108,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardBottomAreaView mQsFrame; @Mock protected KeyguardStatusBarView mKeyguardStatusBar; @Mock protected QS mQs; - @Mock protected QSFragment mQSFragment; + @Mock protected QSFragmentLegacy mQSFragment; @Mock protected Lazy<NotificationPanelViewController> mPanelViewControllerLazy; @Mock protected NotificationPanelViewController mNotificationPanelViewController; @Mock protected NotificationPanelView mPanelView; diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index d018cbbfbc24..2be1c09843c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -25,7 +25,6 @@ import android.os.UserManager import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue @@ -35,6 +34,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState @@ -54,6 +54,7 @@ import com.android.systemui.user.domain.interactor.GuestUserInteractor import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode import com.android.systemui.user.domain.interactor.RefreshUsersScheduler import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -153,6 +154,7 @@ class ShadeInteractorTest : SysuiTestCase() { refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = guestInteractor, uiEventLogger = uiEventLogger, + userRestrictionChecker = mock(), ) underTest = ShadeInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 5c75d9cff10a..602bd5fded3d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -47,9 +47,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = - utils.authenticationInteractor( - repository = utils.authenticationRepository(), + private val authenticationInteractor = utils.authenticationInteractor() + private val deviceEntryInteractor = + utils.deviceEntryInteractor( + authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ) @@ -88,9 +89,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { underTest = ShadeSceneViewModel( applicationScope = testScope.backgroundScope, - authenticationInteractor = authenticationInteractor, + deviceEntryInteractor = deviceEntryInteractor, bouncerInteractor = utils.bouncerInteractor( + deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ), @@ -103,7 +105,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -113,7 +115,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(true) + utils.deviceEntryRepository.setUnlocked(true) assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) } @@ -122,7 +124,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setLockscreenEnabled(true) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason") @@ -134,7 +136,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setLockscreenEnabled(true) + utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason") @@ -147,7 +149,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(true) + utils.deviceEntryRepository.setUnlocked(true) runCurrent() underTest.onContentClicked() @@ -160,7 +162,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setUnlocked(false) + utils.deviceEntryRepository.setUnlocked(false) runCurrent() underTest.onContentClicked() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index 48665fe0c9b0..e71473681211 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.shared.clocks +import android.content.ComponentName import android.content.ContentResolver import android.content.Context import android.graphics.drawable.Drawable @@ -28,12 +29,11 @@ import com.android.systemui.plugins.ClockId import com.android.systemui.plugins.ClockMetadata import com.android.systemui.plugins.ClockProviderPlugin import com.android.systemui.plugins.ClockSettings -import com.android.systemui.plugins.PluginListener import com.android.systemui.plugins.PluginLifecycleManager +import com.android.systemui.plugins.PluginListener import com.android.systemui.plugins.PluginManager import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock import junit.framework.Assert.assertEquals import junit.framework.Assert.fail import kotlinx.coroutines.CoroutineDispatcher @@ -46,6 +46,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.never +import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -66,7 +67,6 @@ class ClockRegistryTest : SysuiTestCase() { @Mock private lateinit var mockDefaultClock: ClockController @Mock private lateinit var mockThumbnail: Drawable @Mock private lateinit var mockContentResolver: ContentResolver - @Mock private lateinit var mockPluginLifecycle: PluginLifecycleManager<ClockProviderPlugin> private lateinit var fakeDefaultProvider: FakeClockPlugin private lateinit var pluginListener: PluginListener<ClockProviderPlugin> private lateinit var registry: ClockRegistry @@ -84,6 +84,41 @@ class ClockRegistryTest : SysuiTestCase() { } } + private class FakeLifecycle( + private val tag: String, + private val plugin: ClockProviderPlugin?, + ) : PluginLifecycleManager<ClockProviderPlugin> { + var onLoad: (() -> Unit)? = null + var onUnload: (() -> Unit)? = null + + private var mIsLoaded: Boolean = true + override fun isLoaded() = mIsLoaded + override fun getPlugin(): ClockProviderPlugin? = if (isLoaded) plugin else null + + var mComponentName = ComponentName("Package[$tag]", "Class[$tag]") + override fun toString() = "Manager[$tag]" + override fun getPackage(): String = mComponentName.getPackageName() + override fun getComponentName(): ComponentName = mComponentName + + private var isDebug: Boolean = false + override fun getIsDebug(): Boolean = isDebug + override fun setIsDebug(value: Boolean) { isDebug = value } + + override fun loadPlugin() { + if (!mIsLoaded) { + mIsLoaded = true + onLoad?.invoke() + } + } + + override fun unloadPlugin() { + if (mIsLoaded) { + mIsLoaded = false + onUnload?.invoke() + } + } + } + private class FakeClockPlugin : ClockProviderPlugin { private val metadata = mutableListOf<ClockMetadata>() private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>() @@ -150,13 +185,15 @@ class ClockRegistryTest : SysuiTestCase() { val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle1 = FakeLifecycle("1", plugin1) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3") .addClock("clock_4", "clock 4") + val lifecycle2 = FakeLifecycle("2", plugin2) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) val list = registry.getClocks() assertEquals( list.toSet(), @@ -178,18 +215,18 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun clockIdConflict_ErrorWithoutCrash_unloadDuplicate() { - val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail }) .addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail }) + val lifecycle1 = spy(FakeLifecycle("1", plugin1)) - val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin2 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle2 = spy(FakeLifecycle("2", plugin2)) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) val list = registry.getClocks() assertEquals( list.toSet(), @@ -204,8 +241,8 @@ class ClockRegistryTest : SysuiTestCase() { assertEquals(registry.createExampleClock("clock_2"), mockClock) assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail) assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail) - verify(mockPluginLifecycle1, never()).unloadPlugin() - verify(mockPluginLifecycle2, times(2)).unloadPlugin() + verify(lifecycle1, never()).unloadPlugin() + verify(lifecycle2, times(2)).unloadPlugin() } @Test @@ -213,14 +250,16 @@ class ClockRegistryTest : SysuiTestCase() { val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle1 = spy(FakeLifecycle("1", plugin1)) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3", { mockClock }) .addClock("clock_4", "clock 4") + val lifecycle2 = spy(FakeLifecycle("2", plugin2)) registry.applySettings(ClockSettings("clock_3", null)) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) val clock = registry.createCurrentClock() assertEquals(mockClock, clock) @@ -231,17 +270,19 @@ class ClockRegistryTest : SysuiTestCase() { val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle1 = spy(FakeLifecycle("1", plugin1)) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3", { mockClock }) .addClock("clock_4", "clock 4") + val lifecycle2 = spy(FakeLifecycle("2", plugin2)) registry.applySettings(ClockSettings("clock_3", null)) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) assertEquals(DEFAULT_CLOCK_ID, registry.activeClockId) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) assertEquals("clock_3", registry.activeClockId) } @@ -250,15 +291,17 @@ class ClockRegistryTest : SysuiTestCase() { val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle1 = spy(FakeLifecycle("1", plugin1)) val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3") .addClock("clock_4", "clock 4") + val lifecycle2 = spy(FakeLifecycle("2", plugin2)) registry.applySettings(ClockSettings("clock_3", null)) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle) - pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) + pluginListener.onPluginUnloaded(plugin2, lifecycle2) val clock = registry.createCurrentClock() assertEquals(clock, mockDefaultClock) @@ -266,15 +309,15 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun pluginRemoved_clockAndListChanged() { - val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin1 = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("clock_2", "clock 2") + val lifecycle1 = spy(FakeLifecycle("1", plugin1)) - val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin2 = FakeClockPlugin() .addClock("clock_3", "clock 3", { mockClock }) .addClock("clock_4", "clock 4") + val lifecycle2 = spy(FakeLifecycle("2", plugin2)) var changeCallCount = 0 var listChangeCallCount = 0 @@ -288,32 +331,32 @@ class ClockRegistryTest : SysuiTestCase() { assertEquals(1, changeCallCount) assertEquals(0, listChangeCallCount) - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) scheduler.runCurrent() assertEquals(1, changeCallCount) assertEquals(1, listChangeCallCount) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) scheduler.runCurrent() assertEquals(2, changeCallCount) assertEquals(2, listChangeCallCount) - pluginListener.onPluginUnloaded(plugin1, mockPluginLifecycle1) + pluginListener.onPluginUnloaded(plugin1, lifecycle1) scheduler.runCurrent() assertEquals(2, changeCallCount) assertEquals(2, listChangeCallCount) - pluginListener.onPluginUnloaded(plugin2, mockPluginLifecycle2) + pluginListener.onPluginUnloaded(plugin2, lifecycle2) scheduler.runCurrent() assertEquals(3, changeCallCount) assertEquals(2, listChangeCallCount) - pluginListener.onPluginDetached(mockPluginLifecycle1) + pluginListener.onPluginDetached(lifecycle1) scheduler.runCurrent() assertEquals(3, changeCallCount) assertEquals(3, listChangeCallCount) - pluginListener.onPluginDetached(mockPluginLifecycle2) + pluginListener.onPluginDetached(lifecycle2) scheduler.runCurrent() assertEquals(3, changeCallCount) assertEquals(4, listChangeCallCount) @@ -321,8 +364,9 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun unknownPluginAttached_clockAndListUnchanged_loadRequested() { - val mockPluginLifecycle = mock<PluginLifecycleManager<ClockProviderPlugin>>() - whenever(mockPluginLifecycle.getPackage()).thenReturn("some.other.package") + val lifecycle = FakeLifecycle("", null).apply { + mComponentName = ComponentName("some.other.package", "SomeClass") + } var changeCallCount = 0 var listChangeCallCount = 0 @@ -331,7 +375,7 @@ class ClockRegistryTest : SysuiTestCase() { override fun onAvailableClocksChanged() { listChangeCallCount++ } }) - assertEquals(true, pluginListener.onPluginAttached(mockPluginLifecycle)) + assertEquals(true, pluginListener.onPluginAttached(lifecycle)) scheduler.runCurrent() assertEquals(0, changeCallCount) assertEquals(0, listChangeCallCount) @@ -339,10 +383,12 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun knownPluginAttached_clockAndListChanged_notLoaded() { - val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - whenever(mockPluginLifecycle1.getPackage()).thenReturn("com.android.systemui.clocks.metro") - val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - whenever(mockPluginLifecycle2.getPackage()).thenReturn("com.android.systemui.clocks.bignum") + val lifecycle1 = FakeLifecycle("Metro", null).apply { + mComponentName = ComponentName("com.android.systemui.clocks.metro", "MetroClock") + } + val lifecycle2 = FakeLifecycle("BigNum", null).apply { + mComponentName = ComponentName("com.android.systemui.clocks.bignum", "BigNumClock") + } var changeCallCount = 0 var listChangeCallCount = 0 @@ -356,12 +402,12 @@ class ClockRegistryTest : SysuiTestCase() { assertEquals(1, changeCallCount) assertEquals(0, listChangeCallCount) - assertEquals(false, pluginListener.onPluginAttached(mockPluginLifecycle1)) + assertEquals(false, pluginListener.onPluginAttached(lifecycle1)) scheduler.runCurrent() assertEquals(1, changeCallCount) assertEquals(1, listChangeCallCount) - assertEquals(false, pluginListener.onPluginAttached(mockPluginLifecycle2)) + assertEquals(false, pluginListener.onPluginAttached(lifecycle2)) scheduler.runCurrent() assertEquals(1, changeCallCount) assertEquals(2, listChangeCallCount) @@ -369,18 +415,14 @@ class ClockRegistryTest : SysuiTestCase() { @Test fun pluginAddRemove_concurrentModification() { - val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - val mockPluginLifecycle3 = mock<PluginLifecycleManager<ClockProviderPlugin>>() - val mockPluginLifecycle4 = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin1 = FakeClockPlugin().addClock("clock_1", "clock 1") + val lifecycle1 = FakeLifecycle("1", plugin1) val plugin2 = FakeClockPlugin().addClock("clock_2", "clock 2") + val lifecycle2 = FakeLifecycle("2", plugin2) val plugin3 = FakeClockPlugin().addClock("clock_3", "clock 3") + val lifecycle3 = FakeLifecycle("3", plugin3) val plugin4 = FakeClockPlugin().addClock("clock_4", "clock 4") - whenever(mockPluginLifecycle1.isLoaded).thenReturn(true) - whenever(mockPluginLifecycle2.isLoaded).thenReturn(true) - whenever(mockPluginLifecycle3.isLoaded).thenReturn(true) - whenever(mockPluginLifecycle4.isLoaded).thenReturn(true) + val lifecycle4 = FakeLifecycle("4", plugin4) // Set the current clock to the final clock to load registry.applySettings(ClockSettings("clock_4", null)) @@ -390,15 +432,15 @@ class ClockRegistryTest : SysuiTestCase() { // unload other plugins. This causes ClockRegistry to modify the list of available clock // plugins while it is being iterated over. In production this happens as a result of a // thread race, instead of synchronously like it does here. - whenever(mockPluginLifecycle2.unloadPlugin()).then { - pluginListener.onPluginDetached(mockPluginLifecycle1) - pluginListener.onPluginLoaded(plugin4, mockContext, mockPluginLifecycle4) + lifecycle2.onUnload = { + pluginListener.onPluginDetached(lifecycle1) + pluginListener.onPluginLoaded(plugin4, mockContext, lifecycle4) } // Load initial plugins - pluginListener.onPluginLoaded(plugin1, mockContext, mockPluginLifecycle1) - pluginListener.onPluginLoaded(plugin2, mockContext, mockPluginLifecycle2) - pluginListener.onPluginLoaded(plugin3, mockContext, mockPluginLifecycle3) + pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1) + pluginListener.onPluginLoaded(plugin2, mockContext, lifecycle2) + pluginListener.onPluginLoaded(plugin3, mockContext, lifecycle3) // Repeatedly verify the loaded providers to get final state registry.verifyLoadedProviders() @@ -484,11 +526,11 @@ class ClockRegistryTest : SysuiTestCase() { private fun testTransitClockFlag(flag: Boolean) { featureFlags.set(TRANSIT_CLOCK, flag) registry.isTransitClockEnabled = featureFlags.isEnabled(TRANSIT_CLOCK) - val mockPluginLifecycle = mock<PluginLifecycleManager<ClockProviderPlugin>>() val plugin = FakeClockPlugin() .addClock("clock_1", "clock 1") .addClock("DIGITAL_CLOCK_METRO", "metro clock") - pluginListener.onPluginLoaded(plugin, mockContext, mockPluginLifecycle) + val lifecycle = FakeLifecycle("metro", plugin) + pluginListener.onPluginLoaded(plugin, mockContext, lifecycle) val list = registry.getClocks() if (flag) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 39accfb5ffcc..19863ecaf723 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -16,9 +16,18 @@ package com.android.systemui.statusbar; +import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.os.UserHandle.USER_ALL; +import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -32,6 +41,7 @@ import android.app.Notification; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.os.Handler; @@ -49,8 +59,6 @@ import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.UserTracker; @@ -66,6 +74,7 @@ import com.android.systemui.util.settings.FakeSettings; import com.google.android.collect.Lists; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -112,7 +121,6 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { private NotificationEntry mCurrentUserNotif; private NotificationEntry mSecondaryUserNotif; private NotificationEntry mWorkProfileNotif; - private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags(); @Before public void setUp() { @@ -127,8 +135,11 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0, UserManager.USER_TYPE_PROFILE_MANAGED); + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true); when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList( - mCurrentUser, mSecondaryUser, mWorkUser)); + mCurrentUser, mWorkUser)); + when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList( + mSecondaryUser)); mDependency.injectTestDependency(Dependency.MAIN_HANDLER, Handler.createAsync(Looper.myLooper())); @@ -153,14 +164,14 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testLockScreenShowNotificationsFalse() { - mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); + mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); } @Test public void testLockScreenShowNotificationsTrue() { - mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); + mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications()); } @@ -227,6 +238,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { // THEN work profile notification is redacted assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic()); } @Test @@ -238,6 +250,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { // THEN work profile notification isn't redacted assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic()); } @Test @@ -295,21 +308,12 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { @Test public void testUserSwitchedCallsOnUserSwitching() { - mFakeFeatureFlags.set(Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE, true); mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id, mContext); verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id); } @Test - public void testUserSwitchedCallsOnUserSwitched() { - mFakeFeatureFlags.set(Flags.LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE, false); - mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanged(mSecondaryUser.id, - mContext); - verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id); - } - - @Test public void testIsLockscreenPublicMode() { assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id)); mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id); @@ -348,6 +352,262 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { verify(listener, never()).onNotificationStateChanged(); } + @Test + public void testDevicePolicyDoesNotAllowNotifications() { + // User allows them + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + // DevicePolicy hides notifs on lockscreen + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); + when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Ignore("b/286230167") + @Test + public void testDevicePolicyDoesNotAllowNotifications_userAll() { + // User allows them + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + // DevicePolicy hides notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); + when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(USER_ALL)); + } + + @Test + @Ignore("b/286230167") + public void testDevicePolicyDoesNotAllowNotifications_secondary() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + // DevicePolicy hides notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); + when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + + // TODO (b/286230167): enable assertion + verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); + } + + @Test + public void testDevicePolicy_noPrivateNotifications() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); + when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + + // TODO (b/286230167): enable assertion. It's currently called 4 times. + //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); + } + + @Test + public void testDevicePolicy_noPrivateNotifications_userAll() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); + when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder() + .setNotification(new Notification()) + .setUser(UserHandle.ALL) + .build())); + } + + @Test + public void testDevicePolicyPrivateNotifications_secondary() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + // DevicePolicy hides sensitive content + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) + .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + + BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); + when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); + + // TODO (b/286230167): enable assertion. It's currently called 5 times. + //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); + } + + @Test + public void testHideNotifications_primary() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testHideNotifications_secondary() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Ignore("b/286230167") + @Test + public void testHideNotifications_workProfile() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id)); + } + + @Test + public void testHideNotifications_secondary_userSwitch() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); + + TestNotificationLockscreenUserManager lockscreenUserManager + = new TestNotificationLockscreenUserManager(mContext); + lockscreenUserManager.setUpWithPresenter(mPresenter); + + lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + + assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Test + public void testShowNotifications_secondary_userSwitch() { + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); + + TestNotificationLockscreenUserManager lockscreenUserManager + = new TestNotificationLockscreenUserManager(mContext); + lockscreenUserManager.setUpWithPresenter(mPresenter); + + lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + + assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); + } + + @Ignore("b/286230167") + @Test + public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + // DevicePolicy allows notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(0); + BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); + when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + // KeyguardManager does not + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); + + assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); + } + + @Test + public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + // DevicePolicy allows notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(0); + BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); + when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + // KeyguardManager does not + when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Test + public void testUserAllowsNotificationsInPublic_settingsChange() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + + // User disables + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + + @Ignore("b/286230167") + @Test + public void testUserAllowsNotificationsInPublic_devicePolicyChange() { + // User allows notifications + mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); + mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); + + assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + + // DevicePolicy disables notifications + when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) + .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); + when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); + mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); + mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, + new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + + assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + } + private class TestNotificationLockscreenUserManager extends NotificationLockscreenUserManagerImpl { public TestNotificationLockscreenUserManager(Context context) { @@ -368,8 +628,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { mKeyguardStateController, mSettings, mock(DumpManager.class), - mock(LockPatternUtils.class), - mFakeFeatureFlags); + mock(LockPatternUtils.class)); } public BroadcastReceiver getBaseBroadcastReceiverForTest() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java index 1592c3007ec8..a6180ec8ea0e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java @@ -248,7 +248,7 @@ public class StatusBarIconViewTest extends SysuiTestCase { } @Test - public void testUpdateIconScale_smallerFontAndConstrainedDrawableSizeLessThanDpIconSize() { + public void testUpdateIconScale_smallerFontAndRawDrawableSizeLessThanDpIconSize() { int dpIconSize = 60; int dpDrawingSize = 30; // smaller font scaling causes the spIconSize < dpIconSize @@ -262,12 +262,42 @@ public class StatusBarIconViewTest extends SysuiTestCase { setIconDrawableWithSize(/* width= */ 50, /* height= */ 50); mIconView.maybeUpdateIconScaleDimens(); - // WHEN both the constrained drawable width/height are less than dpIconSize, + // WHEN both the raw/constrained drawable width/height are less than dpIconSize, + // THEN the icon is scaled up from constrained drawable size to the raw drawable size + float scaleToBackRawDrawableSize = (float) 50 / 40; // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize; // THEN the scaled icon should be scaled down further to fit spIconSize float scaleToFitSpIconSize = (float) spIconSize / dpIconSize; - assertEquals(scaleToFitDrawingSize * scaleToFitSpIconSize, mIconView.getIconScale(), 0.01f); + assertEquals(scaleToBackRawDrawableSize * scaleToFitDrawingSize * scaleToFitSpIconSize, + mIconView.getIconScale(), 0.01f); + } + + @Test + public void testUpdateIconScale_smallerFontAndConstrainedDrawableSizeLessThanDpIconSize() { + int dpIconSize = 60; + int dpDrawingSize = 30; + // smaller font scaling causes the spIconSize < dpIconSize + int spIconSize = 40; + // the icon view layout size would be 40x150 + // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT) + setUpIconView(dpIconSize, dpDrawingSize, spIconSize); + mIconView.setNotification(mock(StatusBarNotification.class)); + // the raw drawable size is 70x70. When put the drawable into iconView whose + // layout size is 40x150, the drawable size would be constrained to 40x40 + setIconDrawableWithSize(/* width= */ 70, /* height= */ 70); + mIconView.maybeUpdateIconScaleDimens(); + + // WHEN the raw drawable width/height are larger than dpIconSize, + // but the constrained drawable width/height are less than dpIconSize, + // THEN the icon is scaled up from constrained drawable size to fit dpIconSize + float scaleToFitDpIconSize = (float) dpIconSize / 40; + // THEN the icon is scaled down from dpIconSize to fit the dpDrawingSize + float scaleToFitDrawingSize = (float) dpDrawingSize / dpIconSize; + // THEN the scaled icon should be scaled down further to fit spIconSize + float scaleToFitSpIconSize = (float) spIconSize / dpIconSize; + assertEquals(scaleToFitDpIconSize * scaleToFitDrawingSize * scaleToFitSpIconSize, + mIconView.getIconScale(), 0.01f); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt index 2c8900ce10dc..61ba4649f559 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt @@ -16,11 +16,18 @@ package com.android.systemui.statusbar.data.repository +import com.android.systemui.statusbar.data.model.StatusBarAppearance +import com.android.systemui.statusbar.data.model.StatusBarMode +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent import kotlinx.coroutines.flow.MutableStateFlow class FakeStatusBarModeRepository : StatusBarModeRepository { override val isTransientShown = MutableStateFlow(false) override val isInFullscreenMode = MutableStateFlow(false) + override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null) + override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT) + + override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {} override fun showTransient() { isTransientShown.value = true diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt index 27c1ba33fa08..d1a46fca21c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt @@ -16,20 +16,36 @@ package com.android.systemui.statusbar.data.repository +import android.graphics.Rect import android.view.WindowInsets import android.view.WindowInsetsController +import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS +import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS import android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS +import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS import androidx.test.filters.SmallTest import com.android.internal.statusbar.LetterboxDetails import com.android.internal.view.AppearanceRegion import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.data.model.StatusBarMode +import com.android.systemui.statusbar.phone.BoundsPair +import com.android.systemui.statusbar.phone.LetterboxAppearance +import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator +import com.android.systemui.statusbar.phone.StatusBarBoundsProvider +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent +import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.mockito.Mockito.verify @@ -37,14 +53,26 @@ import org.mockito.Mockito.verify class StatusBarModeRepositoryImplTest : SysuiTestCase() { private val testScope = TestScope() private val commandQueue = mock<CommandQueue>() + private val letterboxAppearanceCalculator = mock<LetterboxAppearanceCalculator>() + private val statusBarBoundsProvider = mock<StatusBarBoundsProvider>() + private val statusBarFragmentComponent = + mock<StatusBarFragmentComponent>().also { + whenever(it.boundsProvider).thenReturn(statusBarBoundsProvider) + } + private val ongoingCallRepository = OngoingCallRepository() private val underTest = StatusBarModeRepositoryImpl( testScope.backgroundScope, DISPLAY_ID, commandQueue, + letterboxAppearanceCalculator, + ongoingCallRepository, ) - .apply { this.start() } + .apply { + this.start() + this.onStatusBarViewInitialized(statusBarFragmentComponent) + } private val commandQueueCallback: CommandQueue.Callbacks get() { @@ -53,6 +81,15 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() { return callbackCaptor.value } + private val statusBarBoundsChangeListener: StatusBarBoundsProvider.BoundsChangeListener + get() { + val callbackCaptor = argumentCaptor<StatusBarBoundsProvider.BoundsChangeListener>() + verify(statusBarBoundsProvider).addChangeListener(capture(callbackCaptor)) + return callbackCaptor.value + } + + @Before fun setUp() {} + @Test fun isTransientShown_commandQueueShow_wrongDisplayId_notUpdated() { commandQueueCallback.showTransient( @@ -228,6 +265,240 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() { assertThat(latest).isTrue() } + @Test + fun statusBarAppearance_navBarColorManaged_matchesCallbackValue() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + onSystemBarAttributesChanged(navbarColorManagedByIme = true) + + assertThat(latest!!.navbarColorManagedByIme).isTrue() + + onSystemBarAttributesChanged(navbarColorManagedByIme = false) + + assertThat(latest!!.navbarColorManagedByIme).isFalse() + } + + @Test + fun statusBarAppearance_appearanceRegions_noLetterboxDetails_usesCallbackValues() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + whenever( + letterboxAppearanceCalculator.getLetterboxAppearance( + eq(APPEARANCE), + eq(APPEARANCE_REGIONS), + eq(LETTERBOX_DETAILS), + any(), + ) + ) + .thenReturn(CALCULATOR_LETTERBOX_APPEARANCE) + + // WHEN the letterbox details are empty + onSystemBarAttributesChanged( + appearance = APPEARANCE, + appearanceRegions = APPEARANCE_REGIONS.toTypedArray(), + letterboxDetails = emptyArray(), + ) + + // THEN the appearance regions passed to the callback are used, *not* + // REGIONS_FROM_LETTERBOX_CALCULATOR + assertThat(latest!!.appearanceRegions).isEqualTo(APPEARANCE_REGIONS) + assertThat(latest!!.appearanceRegions).isNotEqualTo(REGIONS_FROM_LETTERBOX_CALCULATOR) + } + + @Test + fun statusBarAppearance_appearanceRegions_letterboxDetails_usesLetterboxCalculator() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + whenever( + letterboxAppearanceCalculator.getLetterboxAppearance( + eq(APPEARANCE), + eq(APPEARANCE_REGIONS), + eq(LETTERBOX_DETAILS), + any(), + ) + ) + .thenReturn(CALCULATOR_LETTERBOX_APPEARANCE) + + onSystemBarAttributesChanged( + appearance = APPEARANCE, + appearanceRegions = APPEARANCE_REGIONS.toTypedArray(), + letterboxDetails = LETTERBOX_DETAILS.toTypedArray(), + ) + + assertThat(latest!!.appearanceRegions).isEqualTo(REGIONS_FROM_LETTERBOX_CALCULATOR) + } + + @Test + fun statusBarAppearance_boundsChanged_appearanceReFetched() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + // First, start with some appearances + val startingLetterboxAppearance = + LetterboxAppearance( + APPEARANCE_LIGHT_STATUS_BARS, + listOf(AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, Rect(0, 0, 1, 1))) + ) + whenever( + letterboxAppearanceCalculator.getLetterboxAppearance( + eq(APPEARANCE), + eq(APPEARANCE_REGIONS), + eq(LETTERBOX_DETAILS), + any(), + ) + ) + .thenReturn(startingLetterboxAppearance) + onSystemBarAttributesChanged( + appearance = APPEARANCE, + appearanceRegions = APPEARANCE_REGIONS.toTypedArray(), + letterboxDetails = LETTERBOX_DETAILS.toTypedArray(), + ) + assertThat(latest!!.mode).isEqualTo(StatusBarMode.TRANSPARENT) + assertThat(latest!!.appearanceRegions) + .isEqualTo(startingLetterboxAppearance.appearanceRegions) + + // WHEN there's a new appearance and we get new status bar bounds + val newLetterboxAppearance = + LetterboxAppearance( + APPEARANCE_LOW_PROFILE_BARS, + listOf(AppearanceRegion(APPEARANCE_LOW_PROFILE_BARS, Rect(10, 20, 30, 40))) + ) + whenever( + letterboxAppearanceCalculator.getLetterboxAppearance( + eq(APPEARANCE), + eq(APPEARANCE_REGIONS), + eq(LETTERBOX_DETAILS), + any(), + ) + ) + .thenReturn(newLetterboxAppearance) + statusBarBoundsChangeListener.onStatusBarBoundsChanged( + BoundsPair(Rect(0, 0, 50, 50), Rect(0, 0, 60, 60)) + ) + + // THEN the new appearances are used + assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT_TRANSPARENT) + assertThat(latest!!.appearanceRegions) + .isEqualTo(newLetterboxAppearance.appearanceRegions) + } + + @Test + fun statusBarMode_ongoingCallAndFullscreen_semiTransparent() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + ongoingCallRepository.setHasOngoingCall(true) + onSystemBarAttributesChanged( + requestedVisibleTypes = WindowInsets.Type.navigationBars(), + ) + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT) + } + + @Test + fun statusBarMode_ongoingCallButNotFullscreen_matchesAppearance() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + ongoingCallRepository.setHasOngoingCall(true) + onSystemBarAttributesChanged( + requestedVisibleTypes = WindowInsets.Type.statusBars(), + appearance = APPEARANCE_OPAQUE_STATUS_BARS, + ) + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE) + } + + @Test + fun statusBarMode_fullscreenButNotOngoingCall_matchesAppearance() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + ongoingCallRepository.setHasOngoingCall(false) + onSystemBarAttributesChanged( + requestedVisibleTypes = WindowInsets.Type.navigationBars(), + appearance = APPEARANCE_OPAQUE_STATUS_BARS, + ) + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE) + } + + @Test + fun statusBarMode_transientShown_semiTransparent() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + onSystemBarAttributesChanged( + appearance = APPEARANCE_OPAQUE_STATUS_BARS, + ) + + underTest.showTransient() + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT) + } + + @Test + fun statusBarMode_appearanceLowProfileAndOpaque_lightsOut() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + onSystemBarAttributesChanged( + appearance = APPEARANCE_LOW_PROFILE_BARS or APPEARANCE_OPAQUE_STATUS_BARS, + ) + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT) + } + + @Test + fun statusBarMode_appearanceLowProfile_lightsOutTransparent() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + onSystemBarAttributesChanged( + appearance = APPEARANCE_LOW_PROFILE_BARS, + ) + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.LIGHTS_OUT_TRANSPARENT) + } + + @Test + fun statusBarMode_appearanceOpaque_opaque() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + onSystemBarAttributesChanged( + appearance = APPEARANCE_OPAQUE_STATUS_BARS, + ) + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.OPAQUE) + } + + @Test + fun statusBarMode_appearanceSemiTransparent_semiTransparent() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + onSystemBarAttributesChanged( + appearance = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS, + ) + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.SEMI_TRANSPARENT) + } + + @Test + fun statusBarMode_appearanceNone_transparent() = + testScope.runTest { + val latest by collectLastValue(underTest.statusBarAppearance) + + onSystemBarAttributesChanged( + appearance = 0, + ) + + assertThat(latest!!.mode).isEqualTo(StatusBarMode.TRANSPARENT) + } + private fun onSystemBarAttributesChanged( displayId: Int = DISPLAY_ID, @WindowInsetsController.Appearance appearance: Int = APPEARANCE_OPAQUE_STATUS_BARS, @@ -253,5 +524,21 @@ class StatusBarModeRepositoryImplTest : SysuiTestCase() { private companion object { const val DISPLAY_ID = 5 + private const val APPEARANCE = APPEARANCE_LIGHT_STATUS_BARS + private val APPEARANCE_REGION = AppearanceRegion(APPEARANCE, Rect(0, 0, 150, 300)) + private val APPEARANCE_REGIONS = listOf(APPEARANCE_REGION) + private val LETTERBOX_DETAILS = + listOf( + LetterboxDetails( + /* letterboxInnerBounds= */ Rect(0, 0, 10, 10), + /* letterboxFullBounds= */ Rect(0, 0, 20, 20), + /* appAppearance= */ 0 + ) + ) + private val REGIONS_FROM_LETTERBOX_CALCULATOR = + listOf(AppearanceRegion(APPEARANCE, Rect(0, 0, 10, 20))) + private const val LETTERBOXED_APPEARANCE = APPEARANCE_LOW_PROFILE_BARS + private val CALCULATOR_LETTERBOX_APPEARANCE = + LetterboxAppearance(LETTERBOXED_APPEARANCE, REGIONS_FROM_LETTERBOX_CALCULATOR) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt index ac2aec6e0c86..0a10b2c85ebe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.mockito.Mockito.never @@ -126,6 +127,22 @@ class GroupExpansionManagerTest : SysuiTestCase() { } @Test + fun testExpandUnattachedEntry() { + featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + + // First, expand the entry when it is attached. + gem.setGroupExpanded(summary1, true) + assertThat(gem.isGroupExpanded(summary1)).isTrue() + + // Un-attach it, and un-expand it. + NotificationEntryBuilder.setNewParent(summary1, null) + gem.setGroupExpanded(summary1, false) + + // Expanding again should throw. + assertThrows(IllegalArgumentException::class.java) { gem.setGroupExpanded(summary1, true) } + } + + @Test fun testSyncWithPipeline() { featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) gem.attach(pipeline) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt index 37ec0e09f841..c1ffa641c6a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt @@ -58,6 +58,35 @@ class GroupMembershipManagerTest : SysuiTestCase() { val noParentEntry = NotificationEntryBuilder().setParent(null).build() assertThat(gmm.isChildInGroup(noParentEntry)).isFalse() } + @Test + fun testIsChildInGroup_summary_old() { + featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) + + val groupKey = "group" + val summary = + NotificationEntryBuilder() + .setGroup(mContext, groupKey) + .setGroupSummary(mContext, true) + .build() + GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() + + assertThat(gmm.isChildInGroup(summary)).isTrue() + } + + @Test + fun testIsChildInGroup_summary_new() { + featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + + val groupKey = "group" + val summary = + NotificationEntryBuilder() + .setGroup(mContext, groupKey) + .setGroupSummary(mContext, true) + .build() + GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() + + assertThat(gmm.isChildInGroup(summary)).isFalse() + } @Test fun testIsChildInGroup_child() { @@ -67,40 +96,78 @@ class GroupMembershipManagerTest : SysuiTestCase() { } @Test - fun testIsGroupSummary() { + fun testIsGroupSummary_topLevelEntry() { featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - val entry = NotificationEntryBuilder().setGroupSummary(mContext, true).build() - assertThat(gmm.isGroupSummary(entry)).isTrue() + val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() + assertThat(gmm.isGroupSummary(entry)).isFalse() } @Test - fun testGetGroupSummary() { + fun testIsGroupSummary_summary() { featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + val groupKey = "group" val summary = NotificationEntryBuilder() - .setGroup(mContext, "group") + .setGroup(mContext, groupKey) .setGroupSummary(mContext, true) .build() - val groupEntry = - GroupEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).setSummary(summary).build() - val entry = - NotificationEntryBuilder().setGroup(mContext, "group").setParent(groupEntry).build() + GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary) + assertThat(gmm.isGroupSummary(summary)).isTrue() } @Test - fun testGetGroupSummary_isSummary_old() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - val entry = NotificationEntryBuilder().setGroupSummary(mContext, true).build() + fun testIsGroupSummary_child() { + featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + + val groupKey = "group" + val summary = + NotificationEntryBuilder() + .setGroup(mContext, groupKey) + .setGroupSummary(mContext, true) + .build() + val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build() + GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build() + + assertThat(gmm.isGroupSummary(entry)).isFalse() + } + + @Test + fun testGetGroupSummary_topLevelEntry() { + featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() assertThat(gmm.getGroupSummary(entry)).isNull() } @Test - fun testGetGroupSummary_isSummary_new() { + fun testGetGroupSummary_summary() { + featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + + val groupKey = "group" + val summary = + NotificationEntryBuilder() + .setGroup(mContext, groupKey) + .setGroupSummary(mContext, true) + .build() + GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() + + assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary) + } + + @Test + fun testGetGroupSummary_child() { featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - val entry = NotificationEntryBuilder().setGroupSummary(mContext, true).build() - assertThat(gmm.getGroupSummary(entry)).isEqualTo(entry) + + val groupKey = "group" + val summary = + NotificationEntryBuilder() + .setGroup(mContext, groupKey) + .setGroupSummary(mContext, true) + .build() + val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build() + GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build() + + assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index f05436f66d82..50ce265b67d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -71,6 +71,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent; import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -117,6 +118,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { PendingIntent mPendingIntent; @Mock UserTracker mUserTracker; + @Mock + DeviceProvisionedController mDeviceProvisionedController; private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider; @@ -141,7 +144,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { mFlags, mKeyguardNotificationVisibilityProvider, mUiEventLoggerFake, - mUserTracker); + mUserTracker, + mDeviceProvisionedController); mNotifInterruptionStateProvider.mUseHeadsUp = true; } @@ -694,6 +698,25 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test + public void testShouldFullscreen_suppressedInterruptionsWhenNotProvisioned() { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(true); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + when(mStatusBarStateController.isDreaming()).thenReturn(false); + when(mPowerManager.isScreenOn()).thenReturn(true); + when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(false); + mNotifInterruptionStateProvider.addSuppressor(mSuppressInterruptions); + + assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry)) + .isEqualTo(FullScreenIntentDecision.FSI_NOT_PROVISIONED); + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isTrue(); + verify(mLogger, never()).logNoFullscreen(any(), any()); + verify(mLogger, never()).logNoFullscreenWarning(any(), any()); + verify(mLogger).logFullscreen(entry, "FSI_NOT_PROVISIONED"); + } + + @Test public void testShouldNotFullScreen_willHun() throws RemoteException { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); when(mPowerManager.isInteractive()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt index 64d025628754..7dcbd8084594 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt @@ -73,14 +73,14 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { controller = ChannelEditorDialogController(mContext, mockNoMan, dialogBuilder) channel1 = NotificationChannel(TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_DEFAULT) - channel2 = NotificationChannel(TEST_CHANNEL2, TEST_CHANNEL_NAME2, IMPORTANCE_DEFAULT) + channel2 = NotificationChannel(TEST_CHANNEL2, TEST_CHANNEL_NAME2, IMPORTANCE_NONE) channelDefault = NotificationChannel( NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME, IMPORTANCE_DEFAULT) group = NotificationChannelGroup(TEST_GROUP_ID, TEST_GROUP_NAME) - `when`(mockNoMan.getNotificationChannelGroupsForPackage( - eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())) + `when`(mockNoMan.getRecentBlockedNotificationChannelGroupsForPackage( + eq(TEST_PACKAGE_NAME), eq(TEST_UID))) .thenReturn(ParceledListSlice(listOf(group))) `when`(mockNoMan.areNotificationsEnabledForPackage(eq(TEST_PACKAGE_NAME), eq(TEST_UID))) @@ -89,11 +89,13 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { @Test fun testPrepareDialogForApp_noExtraChannels() { + channel1.group = group.id + channel2.group = group.id group.channels = listOf(channel1, channel2) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, - setOf(channel1, channel2), appIcon, clickListener) + channel1, appIcon, clickListener) - assertEquals(2, controller.paddedChannels.size) + assertEquals(2, controller.channelList.size) } @Test @@ -101,39 +103,41 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { group.addChannel(channelDefault) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, - setOf(channelDefault), appIcon, clickListener) + channelDefault, appIcon, clickListener) assertEquals("No channels should be shown when there is only the miscellaneous channel", - 0, controller.paddedChannels.size) + 0, controller.channelList.size) } @Test - fun testPrepareDialogForApp_noProvidedChannels_noException() { - group.channels = listOf() - - controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, - setOf(), appIcon, clickListener) - } - - @Test - fun testPrepareDialogForApp_retrievesUpTo4Channels() { + fun testPrepareDialogForApp_AddsAllChannelsAllGroups() { + val group2 = NotificationChannelGroup("two", "group two") val channel3 = NotificationChannel("test_channel_3", "Test channel 3", IMPORTANCE_DEFAULT) + channel3.group = group2.id val channel4 = NotificationChannel("test_channel_4", "Test channel 4", IMPORTANCE_DEFAULT) + channel4.group = group.id + val channel5 = NotificationChannel("test_channel_5", "Test channel 5", IMPORTANCE_DEFAULT) + channel5.group = group.id - group.channels = listOf(channel1, channel2, channel3, channel4) + `when`(mockNoMan.getRecentBlockedNotificationChannelGroupsForPackage( + eq(TEST_PACKAGE_NAME), eq(TEST_UID))) + .thenReturn(ParceledListSlice(listOf(group, group2))) + + group.channels = listOf(channel1, channel2, channel4, channel5) + group2.channels = listOf(channel3) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, - setOf(channel1), appIcon, clickListener) + channel1, appIcon, clickListener) - assertEquals("ChannelEditorDialog should fetch enough channels to show 4", - 4, controller.paddedChannels.size) + assertEquals("ChannelEditorDialog should show all channels", + 5, controller.channelList.size) } @Test fun testApply_demoteChannel() { group.channels = listOf(channel1, channel2) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, - setOf(channel1, channel2), appIcon, clickListener) + channel1, appIcon, clickListener) // propose an adjustment of channel1 controller.proposeEditForChannel(channel1, IMPORTANCE_NONE) @@ -145,14 +149,33 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { // Channel 2 shouldn't have changed assertEquals("Proposed edits should take effect after apply", + IMPORTANCE_NONE, channel2.importance) + } + + @Test + fun testApply_promoteChannel() { + group.channels = listOf(channel1, channel2) + controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, + channel1, appIcon, clickListener) + + // propose an adjustment of channel1 + controller.proposeEditForChannel(channel2, IMPORTANCE_DEFAULT) + + controller.apply() + + assertEquals("Proposed edits should take effect after apply", IMPORTANCE_DEFAULT, channel2.importance) + + // Channel 1 shouldn't have changed + assertEquals("Proposed edits should take effect after apply", + IMPORTANCE_DEFAULT, channel1.importance) } @Test fun testApply_demoteApp() { group.channels = listOf(channel1, channel2) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, - setOf(channel1, channel2), appIcon, clickListener) + channel1, appIcon, clickListener) controller.proposeSetAppNotificationsEnabled(false) controller.apply() @@ -168,7 +191,7 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { .thenReturn(false) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, - setOf(channel1, channel2), appIcon, clickListener) + channel1, appIcon, clickListener) controller.proposeSetAppNotificationsEnabled(true) controller.apply() @@ -181,7 +204,7 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { // GIVEN editor dialog group.channels = listOf(channel1, channel2) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, - setOf(channel1, channel2), appIcon, null) + channel1, appIcon, null) // WHEN user taps settings // Pass in any old view, it should never actually be used @@ -197,7 +220,7 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { group.channels = listOf(channel1, channel2) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, - setOf(channel1, channel2), appIcon, null) + channel1, appIcon, null) // WHEN the user proposes a change controller.proposeEditForChannel(channel1, IMPORTANCE_NONE) @@ -214,7 +237,7 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { group.channels = listOf(channel1, channel2) controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID, - setOf(channel1, channel2), appIcon, null) + channel1, appIcon, null) // WHEN the user proposes a change controller.proposeEditForChannel(channel1, IMPORTANCE_NONE) @@ -236,7 +259,6 @@ class ChannelEditorDialogControllerTest : SysuiTestCase() { const val TEST_PACKAGE_NAME = "test_package" const val TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME const val TEST_UID = 1 - const val MULTIPLE_CHANNEL_COUNT = 2 const val TEST_CHANNEL = "test_channel" const val TEST_CHANNEL_NAME = "Test Channel Name" const val TEST_CHANNEL2 = "test_channel2" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index ac8b42afd4b2..e37314301e28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -481,33 +481,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - public void testGetNumUniqueChildren_defaultChannel() throws Exception { - ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup(); - - assertEquals(1, groupRow.getNumUniqueChannels()); - } - - @Test - public void testGetNumUniqueChildren_multiChannel() throws Exception { - ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); - - List<ExpandableNotificationRow> childRows = - group.getChildrenContainer().getAttachedChildren(); - // Give each child a unique channel id/name. - int i = 0; - for (ExpandableNotificationRow childRow : childRows) { - modifyRanking(childRow.getEntry()) - .setChannel( - new NotificationChannel( - "id" + i, "dinnertime" + i, IMPORTANCE_DEFAULT)) - .build(); - i++; - } - - assertEquals(3, group.getNumUniqueChannels()); - } - - @Test public void testIconScrollXAfterTranslationAndReset() throws Exception { ExpandableNotificationRow group = mNotificationTestHelper.createGroup(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 9e0f83c9fc53..0f1e63f4c0df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -462,7 +462,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(mChannelEditorDialogController), eq(statusBarNotification.getPackageName()), any(NotificationChannel.class), - anySet(), eq(entry), any(NotificationInfo.OnSettingsClickListener.class), any(NotificationInfo.OnAppSettingsClickListener.class), @@ -496,7 +495,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(mChannelEditorDialogController), eq(statusBarNotification.getPackageName()), any(NotificationChannel.class), - anySet(), eq(entry), any(NotificationInfo.OnSettingsClickListener.class), any(NotificationInfo.OnAppSettingsClickListener.class), @@ -528,7 +526,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase { eq(mChannelEditorDialogController), eq(statusBarNotification.getPackageName()), any(NotificationChannel.class), - anySet(), eq(entry), any(NotificationInfo.OnSettingsClickListener.class), any(NotificationInfo.OnAppSettingsClickListener.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java index f0b4dd46654a..b59385cd5261 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java @@ -92,7 +92,6 @@ public class NotificationInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; private static final String TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME; private static final int TEST_UID = 1; - private static final int MULTIPLE_CHANNEL_COUNT = 2; private static final String TEST_CHANNEL = "test_channel"; private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME"; @@ -100,8 +99,6 @@ public class NotificationInfoTest extends SysuiTestCase { private NotificationInfo mNotificationInfo; private NotificationChannel mNotificationChannel; private NotificationChannel mDefaultNotificationChannel; - private Set<NotificationChannel> mNotificationChannelSet = new HashSet<>(); - private Set<NotificationChannel> mDefaultNotificationChannelSet = new HashSet<>(); private StatusBarNotification mSbn; private NotificationEntry mEntry; private UiEventLoggerFake mUiEventLogger = new UiEventLoggerFake(); @@ -162,11 +159,9 @@ public class NotificationInfoTest extends SysuiTestCase { // Some test channels. mNotificationChannel = new NotificationChannel( TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW); - mNotificationChannelSet.add(mNotificationChannel); mDefaultNotificationChannel = new NotificationChannel( NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME, IMPORTANCE_LOW); - mDefaultNotificationChannelSet.add(mDefaultNotificationChannel); mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, new Notification(), UserHandle.getUserHandleForUid(TEST_UID), null, 0); mEntry = new NotificationEntryBuilder().setSbn(mSbn).build(); @@ -185,7 +180,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -212,7 +206,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -235,7 +228,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -267,7 +259,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, entry, null, null, @@ -291,7 +282,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -320,7 +310,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -344,7 +333,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -367,7 +355,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mDefaultNotificationChannel, - mDefaultNotificationChannelSet, mEntry, null, null, @@ -394,7 +381,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mDefaultNotificationChannel, - mDefaultNotificationChannelSet, mEntry, null, null, @@ -417,7 +403,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -441,7 +426,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); @@ -470,7 +454,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -494,7 +477,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); @@ -519,7 +501,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -536,7 +517,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, (View v, NotificationChannel c, int appUid) -> { }, null, @@ -551,86 +531,6 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test - public void testOnClickListenerPassesNullChannelForBundle() throws Exception { - final CountDownLatch latch = new CountDownLatch(1); - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, mNotificationChannel, - createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT), - mEntry, - (View v, NotificationChannel c, int appUid) -> { - assertEquals(null, c); - latch.countDown(); - }, - null, - mUiEventLogger, - true, - true, - true, - mAssistantFeedbackController, - mMetricsLogger); - - mNotificationInfo.findViewById(R.id.info).performClick(); - // Verify that listener was triggered. - assertEquals(0, latch.getCount()); - } - - @Test - @UiThreadTest - public void testBindNotification_ChannelNameInvisibleWhenBundleFromDifferentChannels() - throws Exception { - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT), - mEntry, - null, - null, - mUiEventLogger, - true, - false, - true, - mAssistantFeedbackController, - mMetricsLogger); - final TextView channelNameView = - mNotificationInfo.findViewById(R.id.channel_name); - assertEquals(GONE, channelNameView.getVisibility()); - } - - @Test - @UiThreadTest - public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception { - mNotificationInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mOnUserInteractionCallback, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - createMultipleChannelSet(MULTIPLE_CHANNEL_COUNT), - mEntry, - null, - null, - mUiEventLogger, - true, - false, - true, - mAssistantFeedbackController, - mMetricsLogger); - assertEquals(GONE, mNotificationInfo.findViewById( - R.id.interruptiveness_settings).getVisibility()); - assertEquals(VISIBLE, mNotificationInfo.findViewById( - R.id.non_configurable_multichannel_text).getVisibility()); - } - - @Test public void testBindNotification_whenAppUnblockable() throws Exception { mNotificationInfo.bindNotification( mMockPackageManager, @@ -639,7 +539,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -683,7 +582,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -727,7 +625,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -755,7 +652,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -778,7 +674,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -803,7 +698,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -825,7 +719,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -847,7 +740,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -869,7 +761,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -893,7 +784,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -918,7 +808,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -946,7 +835,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -974,7 +862,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1003,7 +890,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1031,7 +917,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1067,7 +952,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1096,7 +980,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1138,7 +1021,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1176,7 +1058,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1209,7 +1090,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1246,7 +1126,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1285,7 +1164,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1317,7 +1195,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1356,7 +1233,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1386,7 +1262,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1418,7 +1293,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1454,7 +1328,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1488,7 +1361,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1522,7 +1394,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1549,7 +1420,6 @@ public class NotificationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, null, @@ -1562,21 +1432,4 @@ public class NotificationInfoTest extends SysuiTestCase { assertFalse(mNotificationInfo.willBeRemoved()); } - - private Set<NotificationChannel> createMultipleChannelSet(int howMany) { - Set<NotificationChannel> multiChannelSet = new HashSet<>(); - for (int i = 0; i < howMany; i++) { - if (i == 0) { - multiChannelSet.add(mNotificationChannel); - continue; - } - - NotificationChannel channel = new NotificationChannel( - TEST_CHANNEL, TEST_CHANNEL_NAME + i, IMPORTANCE_LOW); - - multiChannelSet.add(channel); - } - - return multiChannelSet; - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java index e42ce2789e0b..ccedd364ef67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java @@ -83,8 +83,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { private PartialConversationInfo mInfo; private NotificationChannel mNotificationChannel; private NotificationChannel mDefaultNotificationChannel; - private Set<NotificationChannel> mNotificationChannelSet = new HashSet<>(); - private Set<NotificationChannel> mDefaultNotificationChannelSet = new HashSet<>(); private StatusBarNotification mSbn; private NotificationEntry mEntry; @@ -144,11 +142,9 @@ public class PartialConversationInfoTest extends SysuiTestCase { // Some test channels. mNotificationChannel = new NotificationChannel( TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW); - mNotificationChannelSet.add(mNotificationChannel); mDefaultNotificationChannel = new NotificationChannel( NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME, IMPORTANCE_LOW); - mDefaultNotificationChannelSet.add(mDefaultNotificationChannel); Notification n = new Notification.Builder(mContext, mNotificationChannel.getId()) .setContentTitle(new SpannableString("title")) .build(); @@ -166,7 +162,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, true, @@ -185,7 +180,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, true, @@ -202,7 +196,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, true, @@ -228,7 +221,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, entry, null, true, @@ -247,7 +239,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); @@ -271,7 +262,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); @@ -294,7 +284,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, true, @@ -311,7 +300,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, (View v, NotificationChannel c, int appUid) -> { assertEquals(mNotificationChannel, c); @@ -330,7 +318,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { mChannelEditorDialogController, TEST_PACKAGE_NAME, mNotificationChannel, - mNotificationChannelSet, mEntry, null, true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt new file mode 100644 index 000000000000..8c3bfd55ecf1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.wrapper + +import android.app.PendingIntent +import android.app.PendingIntent.CancelListener +import android.content.Intent +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import android.testing.ViewUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.NotificationTestHelper +import com.android.systemui.statusbar.notification.row.wrapper.NotificationTemplateViewWrapper.ActionPendingIntentCancellationHandler +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mockito +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationTemplateViewWrapperTest : SysuiTestCase() { + + private lateinit var helper: NotificationTestHelper + + private lateinit var root: ViewGroup + private lateinit var view: ViewGroup + private lateinit var row: ExpandableNotificationRow + private lateinit var actions: ViewGroup + + private lateinit var looper: TestableLooper + + @Before + fun setUp() { + looper = TestableLooper.get(this) + allowTestableLooperAsMainThread() + helper = NotificationTestHelper(mContext, mDependency, looper) + row = helper.createRow() + // Some code in the view iterates through parents so we need some extra containers around + // it. + root = FrameLayout(mContext) + val root2 = FrameLayout(mContext) + root.addView(root2) + view = + (LayoutInflater.from(mContext) + .inflate(R.layout.notification_template_material_big_text, root2) as ViewGroup) + actions = view.findViewById(R.id.actions)!! + ViewUtils.attachView(root) + } + + @Test + fun noActionsPresent_noCrash() { + view.removeView(actions) + val wrapper = NotificationTemplateViewWrapper(mContext, view, row) + wrapper.onContentUpdated(row) + } + + @Test + fun actionPendingIntentCancelled_actionDisabled() { + val wrapper = NotificationTemplateViewWrapper(mContext, view, row) + val action1 = createActionWithPendingIntent() + val action2 = createActionWithPendingIntent() + val action3 = createActionWithPendingIntent() + wrapper.onContentUpdated(row) + waitForUiOffloadThread() // Wait for cancellation registration to execute. + + val pi3 = getPendingIntent(action3) + pi3.cancel() + looper.processAllMessages() // Wait for listener callbacks to execute + + assertThat(action1.isEnabled).isTrue() + assertThat(action2.isEnabled).isTrue() + assertThat(action3.isEnabled).isFalse() + assertThat(wrapper.mCancelledPendingIntents) + .doesNotContain(getPendingIntent(action1).hashCode()) + assertThat(wrapper.mCancelledPendingIntents) + .doesNotContain(getPendingIntent(action2).hashCode()) + assertThat(wrapper.mCancelledPendingIntents).contains(pi3.hashCode()) + } + + @Test + fun newActionWithSamePendingIntentPosted_actionDisabled() { + val wrapper = NotificationTemplateViewWrapper(mContext, view, row) + val action = createActionWithPendingIntent() + wrapper.onContentUpdated(row) + waitForUiOffloadThread() // Wait for cancellation registration to execute. + + // Cancel the intent and check action is now false. + val pi = getPendingIntent(action) + pi.cancel() + looper.processAllMessages() // Wait for listener callbacks to execute + assertThat(action.isEnabled).isFalse() + + // Create a NEW action and make sure that one will also be cancelled with same PI. + actions.removeView(action) + val newAction = createActionWithPendingIntent(pi) + wrapper.onContentUpdated(row) + looper.processAllMessages() // Wait for listener callbacks to execute + + assertThat(newAction.isEnabled).isFalse() + assertThat(wrapper.mCancelledPendingIntents).containsExactly(pi.hashCode()) + } + + @Test + fun twoActionsWithSameCancelledIntent_bothActionsDisabled() { + val wrapper = NotificationTemplateViewWrapper(mContext, view, row) + val action1 = createActionWithPendingIntent() + val action2 = createActionWithPendingIntent() + val action3 = createActionWithPendingIntent(getPendingIntent(action2)) + wrapper.onContentUpdated(row) + waitForUiOffloadThread() // Wait for cancellation registration to execute. + + val pi = getPendingIntent(action2) + pi.cancel() + looper.processAllMessages() // Wait for listener callbacks to execute + + assertThat(action1.isEnabled).isTrue() + assertThat(action2.isEnabled).isFalse() + assertThat(action3.isEnabled).isFalse() + } + + @Test + fun actionPendingIntentCancelled_whileDetached_actionDisabled() { + ViewUtils.detachView(root) + val wrapper = NotificationTemplateViewWrapper(mContext, view, row) + val action = createActionWithPendingIntent() + wrapper.onContentUpdated(row) + getPendingIntent(action).cancel() + ViewUtils.attachView(root) + waitForUiOffloadThread() + looper.processAllMessages() + + assertThat(action.isEnabled).isFalse() + } + + @Test + fun actionViewDetached_pendingIntentListenersDeregistered() { + val pi = + PendingIntent.getActivity( + mContext, + System.currentTimeMillis().toInt(), + Intent(Intent.ACTION_VIEW), + PendingIntent.FLAG_IMMUTABLE + ) + val spy = Mockito.spy(pi) + createActionWithPendingIntent(spy) + val wrapper = NotificationTemplateViewWrapper(mContext, view, row) + wrapper.onContentUpdated(row) + ViewUtils.detachView(root) + waitForUiOffloadThread() + looper.processAllMessages() + + val captor = ArgumentCaptor.forClass(CancelListener::class.java) + verify(spy, times(1)).registerCancelListener(captor.capture()) + verify(spy, times(1)).unregisterCancelListener(captor.value) + } + + @Test + fun actionViewUpdated_oldPendingIntentListenersRemoved() { + val pi = + PendingIntent.getActivity( + mContext, + System.currentTimeMillis().toInt(), + Intent(Intent.ACTION_VIEW), + PendingIntent.FLAG_IMMUTABLE + ) + val spy = Mockito.spy(pi) + val action = createActionWithPendingIntent(spy) + val wrapper = NotificationTemplateViewWrapper(mContext, view, row) + wrapper.onContentUpdated(row) + waitForUiOffloadThread() + looper.processAllMessages() + + // Grab set attach listener + val attachListener = + Mockito.spy(action.getTag(com.android.systemui.res.R.id.pending_intent_listener_tag)) + as ActionPendingIntentCancellationHandler + action.setTag(com.android.systemui.res.R.id.pending_intent_listener_tag, attachListener) + + // Update pending intent in the existing action + val newPi = + PendingIntent.getActivity( + mContext, + System.currentTimeMillis().toInt(), + Intent(Intent.ACTION_ALARM_CHANGED), + PendingIntent.FLAG_IMMUTABLE + ) + action.setTagInternal(R.id.pending_intent_tag, newPi) + wrapper.onContentUpdated(row) + waitForUiOffloadThread() + looper.processAllMessages() + + // Listeners for original pending intent need to be cleaned up now. + val captor = ArgumentCaptor.forClass(CancelListener::class.java) + verify(spy, times(1)).registerCancelListener(captor.capture()) + verify(spy, times(1)).unregisterCancelListener(captor.value) + // Attach listener has to be replaced with a new one. + assertThat(action.getTag(com.android.systemui.res.R.id.pending_intent_listener_tag)) + .isNotEqualTo(attachListener) + verify(attachListener).remove() + } + + private fun createActionWithPendingIntent(): View { + val pi = + PendingIntent.getActivity( + mContext, + System.currentTimeMillis().toInt(), + Intent(Intent.ACTION_VIEW), + PendingIntent.FLAG_IMMUTABLE + ) + return createActionWithPendingIntent(pi) + } + + private fun createActionWithPendingIntent(pi: PendingIntent): View { + val view = + LayoutInflater.from(mContext) + .inflate(R.layout.notification_material_action, null, false) + view.setTagInternal(R.id.pending_intent_tag, pi) + actions.addView(view) + return view + } + + private fun getPendingIntent(action: View): PendingIntent { + val pendingIntent = action.getTag(R.id.pending_intent_tag) as PendingIntent + assertThat(pendingIntent).isNotNull() + return pendingIntent + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index c71c0c579e3a..a5d348404240 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -24,7 +24,6 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.ActivityManager; @@ -35,14 +34,11 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.testing.AndroidTestingRunner; import android.view.HapticFeedbackConstants; -import android.view.WindowInsets; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.testing.FakeMetricsLogger; -import com.android.internal.statusbar.LetterboxDetails; -import com.android.internal.view.AppearanceRegion; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; @@ -100,7 +96,6 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private VibratorHelper mVibratorHelper; @Mock private Vibrator mVibrator; @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; - @Mock private SystemBarAttributesListener mSystemBarAttributesListener; @Mock private Lazy<CameraLauncher> mCameraLauncherLazy; @Mock private UserTracker mUserTracker; @Mock private QSHost mQSHost; @@ -139,7 +134,6 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { Optional.of(mVibrator), new DisableFlagsLogger(), DEFAULT_DISPLAY, - mSystemBarAttributesListener, mCameraLauncherLazy, mUserTracker, mQSHost, @@ -197,62 +191,6 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { } @Test - public void onSystemBarAttributesChanged_forwardsToSysBarAttrsListener() { - int displayId = DEFAULT_DISPLAY; - int appearance = 123; - AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{}; - boolean navbarColorManagedByIme = true; - int behavior = 456; - int requestedVisibleTypes = WindowInsets.Type.systemBars(); - String packageName = "test package name"; - LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{}; - - mSbcqCallbacks.onSystemBarAttributesChanged( - displayId, - appearance, - appearanceRegions, - navbarColorManagedByIme, - behavior, - requestedVisibleTypes, - packageName, - letterboxDetails); - - verify(mSystemBarAttributesListener).onSystemBarAttributesChanged( - displayId, - appearance, - appearanceRegions, - navbarColorManagedByIme, - behavior, - requestedVisibleTypes, - packageName, - letterboxDetails - ); - } - - @Test - public void onSystemBarAttributesChanged_differentDisplayId_doesNotForwardToAttrsListener() { - int appearance = 123; - AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{}; - boolean navbarColorManagedByIme = true; - int behavior = 456; - int requestedVisibleTypes = WindowInsets.Type.systemBars(); - String packageName = "test package name"; - LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{}; - - mSbcqCallbacks.onSystemBarAttributesChanged( - DEFAULT_DISPLAY + 1, - appearance, - appearanceRegions, - navbarColorManagedByIme, - behavior, - requestedVisibleTypes, - packageName, - letterboxDetails); - - verifyZeroInteractions(mSystemBarAttributesListener); - } - - @Test public void vibrateOnNavigationKeyDown_oneWayHapticsDisabled_usesVibrate() { mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 5c3dde59596e..e33fa22bfc0c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -165,7 +165,6 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; -import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -295,7 +294,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy; @Mock private BrightnessSliderController.Factory mBrightnessSliderFactory; @Mock private WallpaperController mWallpaperController; - @Mock private OngoingCallController mOngoingCallController; @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; @Mock private LockscreenShadeTransitionController mLockscreenTransitionController; @Mock private NotificationVisibilityProvider mVisibilityProvider; @@ -364,7 +362,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mock(NotifPipelineFlags.class), mock(KeyguardNotificationVisibilityProvider.class), mock(UiEventLogger.class), - mUserTracker); + mUserTracker, + mDeviceProvisionedController); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); @@ -534,7 +533,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mBrightnessSliderFactory, mScreenOffAnimationController, mWallpaperController, - mOngoingCallController, mStatusBarHideIconsForBouncerManager, mLockscreenTransitionController, mFeatureFlags, @@ -1169,7 +1167,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { NotifPipelineFlags flags, KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider, UiEventLogger uiEventLogger, - UserTracker userTracker) { + UserTracker userTracker, + DeviceProvisionedController deviceProvisionedController) { super( contentResolver, powerManager, @@ -1183,7 +1182,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { flags, keyguardNotificationVisibilityProvider, uiEventLogger, - userTracker + userTracker, + deviceProvisionedController ); mUseHeadsUp = true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt index b2dc0dc984c7..e7b287c5bdc8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt @@ -26,7 +26,6 @@ import com.android.internal.statusbar.LetterboxDetails import com.android.internal.view.AppearanceRegion import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager -import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -47,15 +46,12 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() { private val TEST_APPEARANCE_REGION_BOUNDS = Rect(0, 0, 20, 100) private val TEST_APPEARANCE_REGION = AppearanceRegion(TEST_APPEARANCE, TEST_APPEARANCE_REGION_BOUNDS) - private val TEST_APPEARANCE_REGIONS = arrayOf(TEST_APPEARANCE_REGION) + private val TEST_APPEARANCE_REGIONS = listOf(TEST_APPEARANCE_REGION) private val TEST_WINDOW_BOUNDS = Rect(0, 0, 500, 500) } @get:Rule var expect = Expect.create() - @Mock private lateinit var lightBarController: LightBarController - @Mock private lateinit var statusBarBoundsProvider: StatusBarBoundsProvider - @Mock private lateinit var statusBarFragmentComponent: StatusBarFragmentComponent @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var letterboxBackgroundProvider: LetterboxBackgroundProvider @@ -64,24 +60,21 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(statusBarFragmentComponent.boundsProvider).thenReturn(statusBarBoundsProvider) calculator = - LetterboxAppearanceCalculator( - lightBarController, dumpManager, letterboxBackgroundProvider) - calculator.onStatusBarViewInitialized(statusBarFragmentComponent) + LetterboxAppearanceCalculator(context, dumpManager, letterboxBackgroundProvider) whenever(letterboxBackgroundProvider.letterboxBackgroundColor).thenReturn(Color.BLACK) whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(false) } @Test fun getLetterboxAppearance_overlapStartSide_returnsOriginalWithScrim() { - whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100)) - whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100)) + val start = Rect(0, 0, 100, 100) + val end = Rect(200, 0, 300, 100) val letterbox = letterboxWithInnerBounds(Rect(50, 50, 150, 150)) val letterboxAppearance = calculator.getLetterboxAppearance( - TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox)) + TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end)) expect .that(letterboxAppearance.appearance) @@ -91,13 +84,13 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() { @Test fun getLetterboxAppearance_overlapEndSide_returnsOriginalWithScrim() { - whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100)) - whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100)) + val start = Rect(0, 0, 100, 100) + val end = Rect(200, 0, 300, 100) val letterbox = letterboxWithInnerBounds(Rect(150, 50, 250, 150)) val letterboxAppearance = calculator.getLetterboxAppearance( - TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox)) + TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end)) expect .that(letterboxAppearance.appearance) @@ -114,14 +107,12 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() { val statusBarStartSideBoundsCopy = Rect(statusBarStartSideBounds) val statusBarEndSideBoundsCopy = Rect(statusBarEndSideBounds) val letterBoxInnerBoundsCopy = Rect(letterBoxInnerBounds) - whenever(statusBarBoundsProvider.visibleStartSideBounds) - .thenReturn(statusBarStartSideBounds) - whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(statusBarEndSideBounds) calculator.getLetterboxAppearance( TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, - arrayOf(letterboxWithInnerBounds(letterBoxInnerBounds)) + listOf(letterboxWithInnerBounds(letterBoxInnerBounds)), + BoundsPair(statusBarStartSideBounds, statusBarEndSideBounds) ) expect.that(statusBarStartSideBounds).isEqualTo(statusBarStartSideBoundsCopy) @@ -132,13 +123,13 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() { @Test fun getLetterboxAppearance_noOverlap_BackgroundMultiColor_returnsAppearanceWithScrim() { whenever(letterboxBackgroundProvider.isLetterboxBackgroundMultiColored).thenReturn(true) - whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100)) - whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100)) + val start = Rect(0, 0, 100, 100) + val end = Rect(200, 0, 300, 100) val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100)) val letterboxAppearance = calculator.getLetterboxAppearance( - TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox)) + TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end)) expect .that(letterboxAppearance.appearance) @@ -148,66 +139,66 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() { @Test fun getLetterboxAppearance_noOverlap_returnsAppearanceWithoutScrim() { - whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100)) - whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100)) + val start = Rect(0, 0, 100, 100) + val end = Rect(200, 0, 300, 100) val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100)) val letterboxAppearance = calculator.getLetterboxAppearance( - TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox)) + TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end)) assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE) } @Test fun getLetterboxAppearance_letterboxContainsStartSide_returnsAppearanceWithoutScrim() { - whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100)) - whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100)) + val start = Rect(0, 0, 100, 100) + val end = Rect(200, 0, 300, 100) val letterbox = letterboxWithInnerBounds(Rect(0, 0, 101, 101)) val letterboxAppearance = calculator.getLetterboxAppearance( - TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox)) + TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end)) assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE) } @Test fun getLetterboxAppearance_letterboxContainsEndSide_returnsAppearanceWithoutScrim() { - whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100)) - whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100)) + val start = Rect(0, 0, 100, 100) + val end = Rect(200, 0, 300, 100) val letterbox = letterboxWithInnerBounds(Rect(199, 0, 301, 101)) val letterboxAppearance = calculator.getLetterboxAppearance( - TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox)) + TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end)) assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE) } @Test fun getLetterboxAppearance_letterboxContainsEntireStatusBar_returnsAppearanceWithoutScrim() { - whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100)) - whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100)) + val start = Rect(0, 0, 100, 100) + val end = Rect(200, 0, 300, 100) val letterbox = letterboxWithInnerBounds(Rect(0, 0, 300, 100)) val letterboxAppearance = calculator.getLetterboxAppearance( - TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox)) + TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, listOf(letterbox), BoundsPair(start, end)) assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE) } @Test fun getLetterboxAppearance_returnsAdaptedAppearanceRegions_basedOnLetterboxInnerBounds() { - whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 0, 0)) - whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(0, 0, 0, 0)) + val start = Rect(0, 0, 0, 0) + val end = Rect(0, 0, 0, 0) val letterbox = letterboxWithInnerBounds(Rect(150, 0, 300, 800)) val letterboxRegion = TEST_APPEARANCE_REGION.copy(bounds = letterbox.letterboxFullBounds) val letterboxAppearance = calculator.getLetterboxAppearance( - TEST_APPEARANCE, arrayOf(letterboxRegion), arrayOf(letterbox)) + TEST_APPEARANCE, listOf(letterboxRegion), listOf(letterbox), BoundsPair(start, end)) val letterboxAdaptedRegion = letterboxRegion.copy(bounds = letterbox.letterboxInnerBounds) assertThat(letterboxAppearance.appearanceRegions.toList()).contains(letterboxAdaptedRegion) @@ -216,8 +207,8 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() { @Test fun getLetterboxAppearance_returnsDefaultAppearanceRegions_basedOnLetterboxOuterBounds() { - whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 0, 0)) - whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(0, 0, 0, 0)) + val start = Rect(0, 0, 0, 0) + val end = Rect(0, 0, 0, 0) val letterbox = letterboxWithBounds( innerBounds = Rect(left = 25, top = 0, right = 75, bottom = 100), @@ -226,16 +217,20 @@ class LetterboxAppearanceCalculatorTest : SysuiTestCase() { val letterboxAppearance = calculator.getLetterboxAppearance( - TEST_APPEARANCE, arrayOf(letterboxRegion), arrayOf(letterbox)) + TEST_APPEARANCE, listOf(letterboxRegion), listOf(letterbox), BoundsPair(start, end)) val outerRegions = listOf( AppearanceRegion( - DEFAULT_APPEARANCE, Rect(left = 0, top = 0, right = 25, bottom = 100)), + DEFAULT_APPEARANCE, + Rect(left = 0, top = 0, right = 25, bottom = 100), + ), AppearanceRegion( - DEFAULT_APPEARANCE, Rect(left = 75, top = 0, right = 100, bottom = 100)), + DEFAULT_APPEARANCE, + Rect(left = 75, top = 0, right = 100, bottom = 100), + ), ) - assertThat(letterboxAppearance.appearanceRegions.toList()) + assertThat(letterboxAppearance.appearanceRegions) .containsAtLeastElementsIn(outerRegions) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index df3c1e50786b..c45ecf3fe225 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -22,12 +22,14 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARE import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -42,11 +44,16 @@ import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.util.ContrastColorUtil; import com.android.internal.view.AppearanceRegion; +import com.android.keyguard.TestScopeProvider; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.FakeDisplayTracker; +import com.android.systemui.statusbar.data.model.StatusBarAppearance; +import com.android.systemui.statusbar.data.model.StatusBarMode; +import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository; import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.util.kotlin.JavaAdapter; import org.junit.Before; import org.junit.Test; @@ -54,6 +61,10 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import kotlinx.coroutines.test.TestScope; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -62,10 +73,16 @@ public class LightBarControllerTest extends SysuiTestCase { private static final GradientColors COLORS_LIGHT = makeColors(Color.WHITE); private static final GradientColors COLORS_DARK = makeColors(Color.BLACK); + private static final BoundsPair STATUS_BAR_BOUNDS = new BoundsPair( + /* start= */ new Rect(0, 0, 10, 10), + /* end= */ new Rect(0, 0, 20, 20)); private LightBarTransitionsController mLightBarTransitionsController; private LightBarTransitionsController mNavBarController; private SysuiDarkIconDispatcher mStatusBarIconController; private LightBarController mLightBarController; + private final TestScope mTestScope = TestScopeProvider.getTestScope(); + private final FakeStatusBarModeRepository mStatusBarModeRepository = + new FakeStatusBarModeRepository(); @Before public void setup() { @@ -77,11 +94,14 @@ public class LightBarControllerTest extends SysuiTestCase { mLightBarTransitionsController); mLightBarController = new LightBarController( mContext, + new JavaAdapter(mTestScope), mStatusBarIconController, mock(BatteryController.class), mock(NavigationModeController.class), + mStatusBarModeRepository, mock(DumpManager.class), new FakeDisplayTracker(mContext)); + mLightBarController.start(); } private static GradientColors makeColors(@ColorInt int bgColor) { @@ -96,13 +116,19 @@ public class LightBarControllerTest extends SysuiTestCase { public void testOnStatusBarAppearanceChanged_multipleStacks_allStacksLight() { final Rect firstBounds = new Rect(0, 0, 1, 1); final Rect secondBounds = new Rect(1, 0, 2, 1); - final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{ + final List<AppearanceRegion> appearanceRegions = Arrays.asList( new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds), new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds) - }; - mLightBarController.onStatusBarAppearanceChanged( - appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT, - false /* navbarColorManagedByIme */); + ); + + mStatusBarModeRepository.getStatusBarAppearance().setValue( + new StatusBarAppearance( + StatusBarMode.TRANSPARENT, + STATUS_BAR_BOUNDS, + appearanceRegions, + /* navbarColorManagedByIme= */ false)); + mTestScope.getTestScheduler().advanceUntilIdle(); + verify(mStatusBarIconController).setIconsDarkArea(eq(null)); verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean()); } @@ -111,13 +137,19 @@ public class LightBarControllerTest extends SysuiTestCase { public void testOnStatusBarAppearanceChanged_multipleStacks_oneStackLightOneStackDark() { final Rect firstBounds = new Rect(0, 0, 1, 1); final Rect secondBounds = new Rect(1, 0, 2, 1); - final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{ + final List<AppearanceRegion> appearanceRegions = Arrays.asList( new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds), new AppearanceRegion(0 /* appearance */, secondBounds) - }; - mLightBarController.onStatusBarAppearanceChanged( - appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT, - false /* navbarColorManagedByIme */); + ); + + mStatusBarModeRepository.getStatusBarAppearance().setValue( + new StatusBarAppearance( + StatusBarMode.TRANSPARENT, + STATUS_BAR_BOUNDS, + appearanceRegions, + /* navbarColorManagedByIme= */ false)); + mTestScope.getTestScheduler().advanceUntilIdle(); + ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class); verify(mStatusBarIconController).setIconsDarkArea(captor.capture()); assertTrue(captor.getValue().contains(firstBounds)); @@ -128,13 +160,19 @@ public class LightBarControllerTest extends SysuiTestCase { public void testOnStatusBarAppearanceChanged_multipleStacks_oneStackDarkOneStackLight() { final Rect firstBounds = new Rect(0, 0, 1, 1); final Rect secondBounds = new Rect(1, 0, 2, 1); - final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{ + final List<AppearanceRegion> appearanceRegions = Arrays.asList( new AppearanceRegion(0 /* appearance */, firstBounds), new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds) - }; - mLightBarController.onStatusBarAppearanceChanged( - appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT, - false /* navbarColorManagedByIme */); + ); + + mStatusBarModeRepository.getStatusBarAppearance().setValue( + new StatusBarAppearance( + StatusBarMode.TRANSPARENT, + STATUS_BAR_BOUNDS, + appearanceRegions, + /* navbarColorManagedByIme= */ false)); + mTestScope.getTestScheduler().advanceUntilIdle(); + ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class); verify(mStatusBarIconController).setIconsDarkArea(captor.capture()); assertTrue(captor.getValue().contains(secondBounds)); @@ -146,14 +184,20 @@ public class LightBarControllerTest extends SysuiTestCase { final Rect firstBounds = new Rect(0, 0, 1, 1); final Rect secondBounds = new Rect(1, 0, 2, 1); final Rect thirdBounds = new Rect(2, 0, 3, 1); - final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{ + final List<AppearanceRegion> appearanceRegions = Arrays.asList( new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, firstBounds), new AppearanceRegion(0 /* appearance */, secondBounds), new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds) - }; - mLightBarController.onStatusBarAppearanceChanged( - appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT, - false /* navbarColorManagedByIme */); + ); + + mStatusBarModeRepository.getStatusBarAppearance().setValue( + new StatusBarAppearance( + StatusBarMode.TRANSPARENT, + STATUS_BAR_BOUNDS, + appearanceRegions, + /* navbarColorManagedByIme= */ false)); + mTestScope.getTestScheduler().advanceUntilIdle(); + ArgumentCaptor<ArrayList<Rect>> captor = ArgumentCaptor.forClass(ArrayList.class); verify(mStatusBarIconController).setIconsDarkArea(captor.capture()); assertTrue(captor.getValue().contains(firstBounds)); @@ -165,40 +209,121 @@ public class LightBarControllerTest extends SysuiTestCase { public void testOnStatusBarAppearanceChanged_multipleStacks_allStacksDark() { final Rect firstBounds = new Rect(0, 0, 1, 1); final Rect secondBounds = new Rect(1, 0, 2, 1); - final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{ + final List<AppearanceRegion> appearanceRegions = Arrays.asList( new AppearanceRegion(0 /* appearance */, firstBounds), new AppearanceRegion(0 /* appearance */, secondBounds) - }; - mLightBarController.onStatusBarAppearanceChanged( - appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT, - false /* navbarColorManagedByIme */); + ); + + mStatusBarModeRepository.getStatusBarAppearance().setValue( + new StatusBarAppearance( + StatusBarMode.TRANSPARENT, + STATUS_BAR_BOUNDS, + appearanceRegions, + /* navbarColorManagedByIme= */ false)); + mTestScope.getTestScheduler().advanceUntilIdle(); + verify(mLightBarTransitionsController).setIconsDark(eq(false), anyBoolean()); } @Test public void testOnStatusBarAppearanceChanged_singleStack_light() { - final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{ + final List<AppearanceRegion> appearanceRegions = List.of( new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1)) - }; - mLightBarController.onStatusBarAppearanceChanged( - appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT, - false /* navbarColorManagedByIme */); + ); + + mStatusBarModeRepository.getStatusBarAppearance().setValue( + new StatusBarAppearance( + StatusBarMode.TRANSPARENT, + STATUS_BAR_BOUNDS, + appearanceRegions, + /* navbarColorManagedByIme= */ false)); + mTestScope.getTestScheduler().advanceUntilIdle(); + verify(mStatusBarIconController).setIconsDarkArea(eq(null)); verify(mLightBarTransitionsController).setIconsDark(eq(true), anyBoolean()); } @Test public void testOnStatusBarAppearanceChanged_singleStack_dark() { - final AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{ + final List<AppearanceRegion> appearanceRegions = List.of( new AppearanceRegion(0, new Rect(0, 0, 1, 1)) - }; - mLightBarController.onStatusBarAppearanceChanged( - appearanceRegions, true /* sbModeChanged */, MODE_TRANSPARENT, - false /* navbarColorManagedByIme */); + ); + + mStatusBarModeRepository.getStatusBarAppearance().setValue( + new StatusBarAppearance( + StatusBarMode.TRANSPARENT, + STATUS_BAR_BOUNDS, + appearanceRegions, + /* navbarColorManagedByIme= */ false)); + mTestScope.getTestScheduler().advanceUntilIdle(); + verify(mLightBarTransitionsController).setIconsDark(eq(false), anyBoolean()); } @Test + public void testOnStatusBarAppearanceChanged_statusBarModeChanged_statusRedrawn() { + final List<AppearanceRegion> appearanceRegions = List.of( + new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1)) + ); + + mStatusBarModeRepository.getStatusBarAppearance().setValue( + new StatusBarAppearance( + StatusBarMode.TRANSPARENT, + STATUS_BAR_BOUNDS, + appearanceRegions, + /* navbarColorManagedByIme= */ false)); + mTestScope.getTestScheduler().advanceUntilIdle(); + reset(mStatusBarIconController); + + // WHEN the same appearance regions but different status bar mode is sent + mStatusBarModeRepository.getStatusBarAppearance().setValue( + new StatusBarAppearance( + StatusBarMode.LIGHTS_OUT_TRANSPARENT, + STATUS_BAR_BOUNDS, + appearanceRegions, + /* navbarColorManagedByIme= */ false)); + mTestScope.getTestScheduler().advanceUntilIdle(); + + // THEN the StatusBarIconController gets a redraw request + verify(mStatusBarIconController).setIconsDarkArea(any()); + } + + /** Regression test for b/301605450. */ + @Test + public void testOnStatusBarAppearanceChanged_statusBarBoundsChanged_statusRedrawn() { + final List<AppearanceRegion> appearanceRegions = List.of( + new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1)) + ); + BoundsPair startingBounds = new BoundsPair( + /* start= */ new Rect(0, 0, 10, 10), + /* end= */ new Rect(0, 0, 20, 20)); + + mStatusBarModeRepository.getStatusBarAppearance().setValue( + new StatusBarAppearance( + StatusBarMode.TRANSPARENT, + startingBounds, + appearanceRegions, + /* navbarColorManagedByIme= */ false)); + mTestScope.getTestScheduler().advanceUntilIdle(); + reset(mStatusBarIconController); + + // WHEN the same appearance regions but different status bar bounds are sent + BoundsPair newBounds = new BoundsPair( + /* start= */ new Rect(0, 0, 30, 30), + /* end= */ new Rect(0, 0, 40, 40)); + mStatusBarModeRepository.getStatusBarAppearance().setValue( + new StatusBarAppearance( + StatusBarMode.TRANSPARENT, + newBounds, + appearanceRegions, + /* navbarColorManagedByIme= */ false)); + mTestScope.getTestScheduler().advanceUntilIdle(); + + // THEN the StatusBarIconController gets a redraw request + verify(mStatusBarIconController).setIconsDarkArea(any()); + } + + @Test public void validateNavBarChangesUpdateIcons() { // On the launcher in dark mode buttons are light mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt index d84010dc7771..61da701ce971 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt @@ -24,14 +24,15 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.phone.StatusBarBoundsProvider.BoundsChangeListener +import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.any import org.mockito.Mockito.doAnswer import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -60,8 +61,9 @@ class StatusBarBoundsProviderTest : SysuiTestCase() { startSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(START_SIDE_BOUNDS) } endSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(END_SIDE_BOUNDS) } - boundsProvider = - StatusBarBoundsProvider(setOf(boundsChangeListener), startSideContent, endSideContent) + boundsProvider = StatusBarBoundsProvider(startSideContent, endSideContent) + boundsProvider.addChangeListener(boundsChangeListener) + reset(boundsChangeListener) } @Test @@ -81,7 +83,7 @@ class StatusBarBoundsProviderTest : SysuiTestCase() { startSideContent.setBoundsOnScreen(newBounds) - verify(boundsChangeListener).onStatusBarBoundsChanged() + verify(boundsChangeListener).onStatusBarBoundsChanged(any()) } @Test @@ -90,7 +92,7 @@ class StatusBarBoundsProviderTest : SysuiTestCase() { startSideContent.setBoundsOnScreen(newBounds) - verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any()) } @Test @@ -101,7 +103,7 @@ class StatusBarBoundsProviderTest : SysuiTestCase() { startSideContent.setBoundsOnScreen(newBounds) - verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any()) } @Test @@ -111,7 +113,7 @@ class StatusBarBoundsProviderTest : SysuiTestCase() { startSideContent.layout(newBounds) - verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any()) } @Test @@ -121,7 +123,7 @@ class StatusBarBoundsProviderTest : SysuiTestCase() { endSideContent.setBoundsOnScreen(newBounds) - verify(boundsChangeListener).onStatusBarBoundsChanged() + verify(boundsChangeListener).onStatusBarBoundsChanged(any()) } @Test @@ -130,7 +132,7 @@ class StatusBarBoundsProviderTest : SysuiTestCase() { endSideContent.setBoundsOnScreen(newBounds) - verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any()) } @Test @@ -141,7 +143,7 @@ class StatusBarBoundsProviderTest : SysuiTestCase() { endSideContent.setBoundsOnScreen(newBounds) - verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any()) } @Test @@ -151,7 +153,7 @@ class StatusBarBoundsProviderTest : SysuiTestCase() { endSideContent.layout(newBounds) - verify(boundsChangeListener, never()).onStatusBarBoundsChanged() + verify(boundsChangeListener, never()).onStatusBarBoundsChanged(any()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt deleted file mode 100644 index 0cd221496530..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt +++ /dev/null @@ -1,187 +0,0 @@ -package com.android.systemui.statusbar.phone - -import android.graphics.Rect -import android.testing.AndroidTestingRunner -import android.view.Display -import android.view.WindowInsets -import android.view.WindowInsetsController -import android.view.WindowInsetsController.* -import androidx.test.filters.SmallTest -import com.android.internal.statusbar.LetterboxDetails -import com.android.internal.view.AppearanceRegion -import com.android.systemui.SysuiTestCase -import com.android.systemui.dump.DumpManager -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.reset -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyZeroInteractions -import org.mockito.MockitoAnnotations -import org.mockito.Mockito.`when` as whenever - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class SystemBarAttributesListenerTest : SysuiTestCase() { - - @Mock private lateinit var dumpManager: DumpManager - @Mock private lateinit var lightBarController: LightBarController - @Mock private lateinit var letterboxAppearanceCalculator: LetterboxAppearanceCalculator - @Mock private lateinit var centralSurfaces: CentralSurfaces - - private lateinit var sysBarAttrsListener: SystemBarAttributesListener - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - whenever( - letterboxAppearanceCalculator.getLetterboxAppearance( - anyInt(), anyObject(), anyObject())) - .thenReturn(TEST_LETTERBOX_APPEARANCE) - - sysBarAttrsListener = - SystemBarAttributesListener( - centralSurfaces, - letterboxAppearanceCalculator, - lightBarController, - dumpManager) - } - - @Test - fun onSysBarAttrsChanged_forwardsAppearanceToCentralSurfaces() { - val appearance = APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS - - changeSysBarAttrs(appearance) - - verify(centralSurfaces).setAppearance(appearance) - } - - @Test - fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToCentralSurfaces() { - changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS) - - verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance) - } - - @Test - fun onSysBarAttrsChanged_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() { - changeSysBarAttrs(TEST_APPEARANCE, arrayOf<LetterboxDetails>()) - - verify(centralSurfaces).setAppearance(TEST_APPEARANCE) - } - - @Test - fun onSysBarAttrsChanged_forwardsAppearanceToLightBarController() { - changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS) - - verify(lightBarController) - .onStatusBarAppearanceChanged( - eq(TEST_APPEARANCE_REGIONS), anyBoolean(), anyInt(), anyBoolean()) - } - - @Test - fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToLightBarController() { - changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS) - - verify(lightBarController) - .onStatusBarAppearanceChanged( - eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions), - anyBoolean(), - anyInt(), - anyBoolean()) - } - - @Test - fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToLightBarController() { - changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS) - reset(centralSurfaces, lightBarController) - - sysBarAttrsListener.onStatusBarBoundsChanged() - - verify(lightBarController) - .onStatusBarAppearanceChanged( - eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions), - anyBoolean(), - anyInt(), - anyBoolean()) - } - - @Test - fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToCentralSurfaces() { - changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS) - reset(centralSurfaces, lightBarController) - - sysBarAttrsListener.onStatusBarBoundsChanged() - - verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance) - } - - @Test - fun onStatusBarBoundsChanged_previousCallEmptyLetterbox_doesNothing() { - changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf()) - reset(centralSurfaces, lightBarController) - - sysBarAttrsListener.onStatusBarBoundsChanged() - - verifyZeroInteractions(centralSurfaces, lightBarController) - } - - private fun changeSysBarAttrs(@Appearance appearance: Int) { - changeSysBarAttrs(appearance, arrayOf<LetterboxDetails>()) - } - - private fun changeSysBarAttrs( - @Appearance appearance: Int, - letterboxDetails: Array<LetterboxDetails> - ) { - changeSysBarAttrs(appearance, arrayOf(), letterboxDetails) - } - - private fun changeSysBarAttrs( - @Appearance appearance: Int, - appearanceRegions: Array<AppearanceRegion> - ) { - changeSysBarAttrs(appearance, appearanceRegions, arrayOf()) - } - - private fun changeSysBarAttrs( - @Appearance appearance: Int, - appearanceRegions: Array<AppearanceRegion>, - letterboxDetails: Array<LetterboxDetails> - ) { - sysBarAttrsListener.onSystemBarAttributesChanged( - Display.DEFAULT_DISPLAY, - appearance, - appearanceRegions, - /* navbarColorManagedByIme= */ false, - WindowInsetsController.BEHAVIOR_DEFAULT, - WindowInsets.Type.defaultVisible(), - "package name", - letterboxDetails) - } - - companion object { - private const val TEST_APPEARANCE = - APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS - private val TEST_APPEARANCE_REGION = AppearanceRegion(TEST_APPEARANCE, Rect(0, 0, 150, 300)) - private val TEST_APPEARANCE_REGIONS = arrayOf(TEST_APPEARANCE_REGION) - private val TEST_LETTERBOX_DETAILS = - arrayOf( - LetterboxDetails( - /* letterboxInnerBounds= */ Rect(0, 0, 0, 0), - /* letterboxFullBounds= */ Rect(0, 0, 0, 0), - /* appAppearance= */ 0)) - private val TEST_LETTERBOX_APPEARANCE = - LetterboxAppearance(/* appearance= */ APPEARANCE_LOW_PROFILE_BARS, arrayOf()) - } -} - -private fun <T> anyObject(): T { - return Mockito.anyObject<T>() -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index 32320b45a36d..49de5125cb4d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository +import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any @@ -85,6 +86,7 @@ class OngoingCallControllerTest : SysuiTestCase() { private val uiEventLoggerFake = UiEventLoggerFake() private val testScope = TestScope() private val statusBarModeRepository = FakeStatusBarModeRepository() + private val ongoingCallRepository = OngoingCallRepository() private lateinit var controller: OngoingCallController private lateinit var notifCollectionListener: NotifCollectionListener @@ -111,6 +113,7 @@ class OngoingCallControllerTest : SysuiTestCase() { controller = OngoingCallController( testScope.backgroundScope, context, + ongoingCallRepository, notificationCollection, clock, mockActivityStarter, @@ -140,10 +143,11 @@ class OngoingCallControllerTest : SysuiTestCase() { } @Test - fun onEntryUpdated_isOngoingCallNotif_listenerNotified() { + fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() { notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean()) + assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue() } @Test @@ -154,10 +158,11 @@ class OngoingCallControllerTest : SysuiTestCase() { } @Test - fun onEntryUpdated_notOngoingCallNotif_listenerNotNotified() { + fun onEntryUpdated_notOngoingCallNotif_listenerAndRepoNotNotified() { notifCollectionListener.onEntryUpdated(createNotCallNotifEntry()) verify(mockOngoingCallListener, never()).onOngoingCallStateChanged(anyBoolean()) + assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse() } @Test @@ -169,6 +174,16 @@ class OngoingCallControllerTest : SysuiTestCase() { .onOngoingCallStateChanged(anyBoolean()) } + @Test + fun onEntryUpdated_ongoingCallNotifThenScreeningCallNotif_repoUpdated() { + notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry()) + assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue() + + notifCollectionListener.onEntryUpdated(createScreeningCallNotifEntry()) + + assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse() + } + /** Regression test for b/191472854. */ @Test fun onEntryUpdated_notifHasNullContentIntent_noCrash() { @@ -276,6 +291,17 @@ class OngoingCallControllerTest : SysuiTestCase() { } @Test + fun onEntryRemoved_callNotifAddedThenRemoved_repoUpdated() { + val ongoingCallNotifEntry = createOngoingCallNotifEntry() + notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) + assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue() + + notifCollectionListener.onEntryRemoved(ongoingCallNotifEntry, REASON_USER_STOPPED) + + assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse() + } + + @Test fun onEntryUpdated_callNotifAddedThenRemoved_windowControllerUpdated() { val ongoingCallNotifEntry = createOngoingCallNotifEntry() notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) @@ -302,6 +328,22 @@ class OngoingCallControllerTest : SysuiTestCase() { verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean()) } + /** Regression test for b/188491504. */ + @Test + fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_repoUpdated() { + val ongoingCallNotifEntry = createOngoingCallNotifEntry() + notifCollectionListener.onEntryAdded(ongoingCallNotifEntry) + + // Create another notification based on the ongoing call one, but remove the features that + // made it a call notification. + val removedEntryBuilder = NotificationEntryBuilder(ongoingCallNotifEntry) + removedEntryBuilder.modifyNotification(context).style = null + + notifCollectionListener.onEntryRemoved(removedEntryBuilder.build(), REASON_USER_STOPPED) + + assertThat(ongoingCallRepository.hasOngoingCall.value).isFalse() + } + @Test fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_listenerNotNotified() { notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry()) @@ -313,6 +355,15 @@ class OngoingCallControllerTest : SysuiTestCase() { } @Test + fun onEntryRemoved_notifKeyDoesNotMatchOngoingCallNotif_repoNotUpdated() { + notifCollectionListener.onEntryAdded(createOngoingCallNotifEntry()) + + notifCollectionListener.onEntryRemoved(createNotCallNotifEntry(), REASON_USER_STOPPED) + + assertThat(ongoingCallRepository.hasOngoingCall.value).isTrue() + } + + @Test fun hasOngoingCall_noOngoingCallNotifSent_returnsFalse() { assertThat(controller.hasOngoingCall()).isFalse() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt new file mode 100644 index 000000000000..56aa7d6e3ca4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepositoryTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone.ongoingcall.data.repository + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +@SmallTest +class OngoingCallRepositoryTest : SysuiTestCase() { + private val underTest = OngoingCallRepository() + + @Test + fun hasOngoingCall_matchesSet() { + underTest.setHasOngoingCall(true) + + assertThat(underTest.hasOngoingCall.value).isTrue() + + underTest.setHasOngoingCall(false) + + assertThat(underTest.hasOngoingCall.value).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt index 99e4030e1192..b54fbd3c31b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt @@ -90,6 +90,8 @@ class FakeMobileConnectionsRepository( private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON) override val defaultMobileIconGroup = _defaultMobileIconGroup + override val isAnySimSecure = MutableStateFlow(false) + fun setSubscriptions(subs: List<SubscriptionModel>) { _subscriptions.value = subs } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index d005972043d7..4d4f33b63f3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -135,6 +135,7 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { FakeAirplaneModeRepository(), wifiRepository, mock(), + mock(), ) demoRepo = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 6f9764a907fc..9148c7580296 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -37,6 +37,8 @@ import android.telephony.TelephonyManager import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.telephony.PhoneConstants +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.R import com.android.settingslib.mobile.MobileMappings import com.android.systemui.SysuiTestCase @@ -104,6 +106,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Mock private lateinit var logger: MobileInputLogger @Mock private lateinit var summaryLogger: TableLogBuffer @Mock private lateinit var logBufferFactory: TableLogBufferFactory + @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor private val mobileMappings = FakeMobileMappingsProxy() private val subscriptionManagerProxy = FakeSubscriptionManagerProxy() @@ -214,6 +217,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { airplaneModeRepository, wifiRepository, fullConnectionFactory, + updateMonitor, ) testScope.runCurrent() @@ -1048,6 +1052,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { airplaneModeRepository, wifiRepository, fullConnectionFactory, + updateMonitor ) val latest by collectLastValue(underTest.defaultDataSubRatConfig) @@ -1103,7 +1108,6 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { @Test fun carrierConfig_initialValueIsFetched() = testScope.runTest { - // Value starts out false assertThat(underTest.defaultDataSubRatConfig.value.showAtLeast3G).isFalse() @@ -1151,6 +1155,26 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(latest).isEqualTo(null) } + @Test + fun anySimSecure_propagatesStateFromKeyguardUpdateMonitor() = + testScope.runTest { + val latest by collectLastValue(underTest.isAnySimSecure) + assertThat(latest).isFalse() + + val updateMonitorCallback = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(updateMonitor).registerCallback(updateMonitorCallback.capture()) + + whenever(updateMonitor.isSimPinSecure).thenReturn(true) + updateMonitorCallback.value.onSimStateChanged(0, 0, 0) + + assertThat(latest).isTrue() + + whenever(updateMonitor.isSimPinSecure).thenReturn(false) + updateMonitorCallback.value.onSimStateChanged(0, 0, 0) + + assertThat(latest).isFalse() + } + private fun TestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback { runCurrent() val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt index 15b9d61fc96b..c935dbb0ca1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt @@ -17,13 +17,14 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel import androidx.test.filters.SmallTest -import com.android.systemui.res.R +import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Text import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon +import com.android.systemui.res.R import com.android.systemui.statusbar.connectivity.WifiIcons import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor @@ -181,6 +182,8 @@ class InternetTileViewModelTest : SysuiTestCase() { assertThat(latest?.icon) .isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4])) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .doesNotContain(context.getString(WIFI_OTHER_DEVICE_CONNECTION)) } @Test @@ -192,6 +195,8 @@ class InternetTileViewModelTest : SysuiTestCase() { assertThat(latest?.icon) .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_tablet)) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION)) } @Test @@ -203,6 +208,8 @@ class InternetTileViewModelTest : SysuiTestCase() { assertThat(latest?.icon) .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_laptop)) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION)) } @Test @@ -214,6 +221,8 @@ class InternetTileViewModelTest : SysuiTestCase() { assertThat(latest?.icon) .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_watch)) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION)) } @Test @@ -225,6 +234,8 @@ class InternetTileViewModelTest : SysuiTestCase() { assertThat(latest?.icon) .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_auto)) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION)) } @Test @@ -236,6 +247,8 @@ class InternetTileViewModelTest : SysuiTestCase() { assertThat(latest?.icon) .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone)) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION)) } @Test @@ -247,6 +260,8 @@ class InternetTileViewModelTest : SysuiTestCase() { assertThat(latest?.icon) .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone)) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION)) } @Test @@ -258,6 +273,8 @@ class InternetTileViewModelTest : SysuiTestCase() { assertThat(latest?.icon) .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone)) + assertThat(latest?.stateDescription.loadContentDescription(context)) + .isEqualTo(context.getString(WIFI_OTHER_DEVICE_CONNECTION)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt index a520f6c109cc..49a2648a7cac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -18,8 +18,10 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.connectivity.WifiIcons @@ -136,6 +138,10 @@ class WifiViewModelTest : SysuiTestCase() { // is used instead assertThat(latest).isInstanceOf(WifiIcon.Visible::class.java) assertThat((latest as WifiIcon.Visible).res).isEqualTo(WifiIcons.WIFI_FULL_ICONS[1]) + assertThat( + (latest as WifiIcon.Visible).contentDescription.loadContentDescription(context) + ) + .doesNotContain(context.getString(WIFI_OTHER_DEVICE_CONNECTION)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java index 85359ca3aebe..bfc5bdb70c7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java @@ -342,6 +342,31 @@ public class ToastUITest extends SysuiTestCase { } @Test + public void testShowToast_afterShowToast_animationListenerCleanup() throws RemoteException { + mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, + mCallback, Display.DEFAULT_DISPLAY); + final SystemUIToast toast = mToastUI.mToast; + + View view = verifyWmAddViewAndAttachToParent(); + mToastUI.showToast(UID_2, PACKAGE_NAME_2, TOKEN_2, TEXT, WINDOW_TOKEN_2, Toast.LENGTH_LONG, + null, Display.DEFAULT_DISPLAY); + + if (toast.getOutAnimation() != null) { + assertThat(mToastUI.mToastOutAnimatorListener).isNotNull(); + assertThat(toast.getOutAnimation().getListeners() + .contains(mToastUI.mToastOutAnimatorListener)).isTrue(); + assertThat(toast.getOutAnimation().isRunning()).isTrue(); + toast.getOutAnimation().cancel(); // end early if applicable + assertThat(toast.getOutAnimation().getListeners()).isNull(); + } + + verify(mWindowManager).removeViewImmediate(view); + verify(mNotificationManager).finishToken(PACKAGE_NAME_1, TOKEN_1); + verify(mCallback).onToastHidden(); + assertThat(mToastUI.mToastOutAnimatorListener).isNull(); + } + + @Test public void testShowToast_logs() { mToastUI.showToast(UID_1, PACKAGE_NAME_1, TOKEN_1, TEXT, WINDOW_TOKEN_1, Toast.LENGTH_LONG, mCallback, Display.DEFAULT_DISPLAY); diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt index bbc49c859821..af941d03f191 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt @@ -34,7 +34,6 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.GuestResetOrExitSessionReceiver import com.android.systemui.GuestResumeSessionReceiver -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Text @@ -45,6 +44,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor @@ -1120,6 +1120,7 @@ class UserInteractorTest : SysuiTestCase() { ), uiEventLogger = uiEventLogger, featureFlags = featureFlags, + userRestrictionChecker = mock(), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt index 2433e123a309..a8db368d4150 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -267,6 +267,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = guestUserInteractor, uiEventLogger = uiEventLogger, + userRestrictionChecker = mock(), ) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 8c88f95d73a5..6932f5ed4b30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -45,6 +45,7 @@ import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper import com.android.systemui.user.shared.model.UserActionModel import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -176,6 +177,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() { refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = guestUserInteractor, uiEventLogger = uiEventLogger, + userRestrictionChecker = mock(), ), guestUserInteractor = guestUserInteractor, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 8e57dc1f8b2c..daf88773780e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -41,8 +41,8 @@ import static org.mockito.Mockito.when; import android.app.KeyguardManager; import android.content.res.Configuration; import android.media.AudioManager; -import android.os.Handler; import android.os.SystemClock; +import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Log; @@ -71,6 +71,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.FakeConfigurationController; +import com.android.systemui.util.settings.FakeSettings; import org.junit.After; import org.junit.Before; @@ -135,6 +136,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { private FakeFeatureFlags mFeatureFlags; private int mLongestHideShowAnimationDuration = 250; + private FakeSettings mSecureSettings; + @Rule public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); @@ -147,10 +150,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); allowTestableLooperAsMainThread(); - // Ensure previous tests have not left messages on main looper - Handler localHandler = new Handler(mTestableLooper.getLooper()); - localHandler.removeCallbacksAndMessages(null); - when(mPostureController.getDevicePosture()) .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); @@ -167,6 +166,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { mFeatureFlags = new FakeFeatureFlags(); + mSecureSettings = new FakeSettings(); + mDialog = new VolumeDialogImpl( getContext(), mVolumeDialogController, @@ -182,7 +183,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { mPostureController, mTestableLooper.getLooper(), mDumpManager, - mFeatureFlags); + mFeatureFlags, + mSecureSettings); mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); @@ -247,6 +249,18 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test + public void testSetTimeoutValue_ComputeTimeout() { + mSecureSettings.putInt(Settings.Secure.VOLUME_DIALOG_DISMISS_TIMEOUT, 7000); + Mockito.reset(mAccessibilityMgr); + mDialog.init(0, null); + mDialog.rescheduleTimeoutH(); + verify(mAccessibilityMgr).getRecommendedTimeoutMillis( + 7000, + AccessibilityManager.FLAG_CONTENT_CONTROLS); + } + + + @Test public void testComputeTimeout_tooltip() { Mockito.reset(mAccessibilityMgr); mDialog.showCaptionsTooltip(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt index 94ed608f4844..e59e4759fc7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt @@ -15,104 +15,119 @@ */ package com.android.systemui.wmshell -import android.content.ContentResolver import android.content.Context +import android.content.Intent import android.content.SharedPreferences +import android.content.pm.ShortcutInfo +import android.content.res.Resources +import android.os.UserHandle import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.core.content.edit import androidx.test.filters.SmallTest import com.android.systemui.model.SysUiStateTest import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.BubbleEducationController import com.android.wm.shell.bubbles.PREF_MANAGED_EDUCATION import com.android.wm.shell.bubbles.PREF_STACK_EDUCATION +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors.directExecutor import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mockito @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class BubbleEducationControllerTest : SysUiStateTest() { - private val sharedPrefsEditor = Mockito.mock(SharedPreferences.Editor::class.java) - private val sharedPrefs = Mockito.mock(SharedPreferences::class.java) - private val context = Mockito.mock(Context::class.java) + + private lateinit var sharedPrefs: SharedPreferences private lateinit var sut: BubbleEducationController @Before fun setUp() { - Mockito.`when`(context.packageName).thenReturn("packageName") - Mockito.`when`(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPrefs) - Mockito.`when`(context.contentResolver) - .thenReturn(Mockito.mock(ContentResolver::class.java)) - Mockito.`when`(sharedPrefs.edit()).thenReturn(sharedPrefsEditor) - sut = BubbleEducationController(context) + sharedPrefs = mContext.getSharedPreferences(mContext.packageName, Context.MODE_PRIVATE) + sharedPrefs.edit { + remove(PREF_STACK_EDUCATION) + remove(PREF_MANAGED_EDUCATION) + } + sut = BubbleEducationController(mContext) } @Test fun testSeenStackEducation_read() { - Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true) + sharedPrefs.edit { putBoolean(PREF_STACK_EDUCATION, true) } assertEquals(sut.hasSeenStackEducation, true) - Mockito.verify(sharedPrefs).getBoolean(PREF_STACK_EDUCATION, false) } @Test fun testSeenStackEducation_write() { sut.hasSeenStackEducation = true - Mockito.verify(sharedPrefsEditor).putBoolean(PREF_STACK_EDUCATION, true) + assertThat(sharedPrefs.getBoolean(PREF_STACK_EDUCATION, false)).isTrue() } @Test fun testSeenManageEducation_read() { - Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true) + sharedPrefs.edit { putBoolean(PREF_MANAGED_EDUCATION, true) } assertEquals(sut.hasSeenManageEducation, true) - Mockito.verify(sharedPrefs).getBoolean(PREF_MANAGED_EDUCATION, false) } @Test fun testSeenManageEducation_write() { sut.hasSeenManageEducation = true - Mockito.verify(sharedPrefsEditor).putBoolean(PREF_MANAGED_EDUCATION, true) + assertThat(sharedPrefs.getBoolean(PREF_MANAGED_EDUCATION, false)).isTrue() } @Test fun testShouldShowStackEducation() { - val bubble = Mockito.mock(Bubble::class.java) // When bubble is null assertEquals(sut.shouldShowStackEducation(null), false) + var bubble = createFakeBubble(isConversational = false) // When bubble is not conversation - Mockito.`when`(bubble.isConversation).thenReturn(false) assertEquals(sut.shouldShowStackEducation(bubble), false) // When bubble is conversation and has seen stack edu - Mockito.`when`(bubble.isConversation).thenReturn(true) - Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true) + bubble = createFakeBubble(isConversational = true) + sharedPrefs.edit { putBoolean(PREF_STACK_EDUCATION, true) } assertEquals(sut.shouldShowStackEducation(bubble), false) // When bubble is conversation and has not seen stack edu - Mockito.`when`(bubble.isConversation).thenReturn(true) - Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(false) + sharedPrefs.edit { remove(PREF_STACK_EDUCATION) } assertEquals(sut.shouldShowStackEducation(bubble), true) } @Test fun testShouldShowManageEducation() { - val bubble = Mockito.mock(Bubble::class.java) // When bubble is null assertEquals(sut.shouldShowManageEducation(null), false) + var bubble = createFakeBubble(isConversational = false) // When bubble is not conversation - Mockito.`when`(bubble.isConversation).thenReturn(false) assertEquals(sut.shouldShowManageEducation(bubble), false) // When bubble is conversation and has seen stack edu - Mockito.`when`(bubble.isConversation).thenReturn(true) - Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(true) + bubble = createFakeBubble(isConversational = true) + sharedPrefs.edit { putBoolean(PREF_MANAGED_EDUCATION, true) } assertEquals(sut.shouldShowManageEducation(bubble), false) // When bubble is conversation and has not seen stack edu - Mockito.`when`(bubble.isConversation).thenReturn(true) - Mockito.`when`(sharedPrefs.getBoolean(anyString(), anyBoolean())).thenReturn(false) + sharedPrefs.edit { remove(PREF_MANAGED_EDUCATION) } assertEquals(sut.shouldShowManageEducation(bubble), true) } + + private fun createFakeBubble(isConversational: Boolean): Bubble { + return if (isConversational) { + val shortcutInfo = ShortcutInfo.Builder(mContext, "fakeId").build() + Bubble( + "key", + shortcutInfo, + /* desiredHeight= */ 6, + Resources.ID_NULL, + "title", + /* taskId= */ 0, + "locus", + /* isDismissable= */ true, + directExecutor() + ) {} + } else { + val intent = Intent(Intent.ACTION_VIEW).setPackage(mContext.packageName) + Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor()) + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index d8511e8f38e2..424218cfc40c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -87,16 +87,19 @@ import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.launcher3.icons.BubbleIconFactory; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.scene.FakeWindowRootViewComponent; +import com.android.systemui.scene.SceneTestUtils; +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -104,11 +107,14 @@ import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeWindowLogger; +import com.android.systemui.shade.data.repository.FakeShadeRepository; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.NotificationEntryHelper; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -121,14 +127,19 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.user.domain.interactor.UserInteractor; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.Bubble; @@ -175,6 +186,8 @@ import java.util.HashMap; import java.util.List; import java.util.Optional; +import kotlinx.coroutines.test.TestScope; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -298,6 +311,9 @@ public class BubblesTest extends SysuiTestCase { @Mock private Icon mAppBubbleIcon; + private SceneTestUtils mUtils = new SceneTestUtils(this); + private TestScope mTestScope = mUtils.getTestScope(); + private ShadeInteractor mShadeInteractor; private ShellTaskOrganizer mShellTaskOrganizer; private TaskViewTransitions mTaskViewTransitions; @@ -334,6 +350,22 @@ public class BubblesTest extends SysuiTestCase { when(mNotificationShadeWindowView.getViewTreeObserver()) .thenReturn(mock(ViewTreeObserver.class)); + mShadeInteractor = new ShadeInteractor( + mTestScope.getBackgroundScope(), + new FakeDisableFlagsRepository(), + new FakeSceneContainerFlags(), + mUtils::sceneInteractor, + new FakeKeyguardRepository(), + new FakeUserSetupRepository(), + mock(DeviceProvisionedController.class), + mock(UserInteractor.class), + new SharedNotificationContainerInteractor( + new FakeConfigurationRepository(), + mContext, + new ResourcesSplitShadeStateController()), + new FakeShadeRepository() + ); + mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl( mContext, new FakeWindowRootViewComponent.Factory(mNotificationShadeWindowView), @@ -351,7 +383,9 @@ public class BubblesTest extends SysuiTestCase { mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager, - mShadeWindowLogger); + () -> mShadeInteractor, + mShadeWindowLogger + ); mNotificationShadeWindowController.fetchWindowRootView(); mNotificationShadeWindowController.attach(); @@ -391,7 +425,8 @@ public class BubblesTest extends SysuiTestCase { mock(NotifPipelineFlags.class), mock(KeyguardNotificationVisibilityProvider.class), mock(UiEventLogger.class), - mock(UserTracker.class) + mock(UserTracker.class), + mock(DeviceProvisionedController.class) ); mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java index 4e14bbf6ac1f..0df235dd2416 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java @@ -29,6 +29,7 @@ import com.android.systemui.statusbar.notification.interruption.KeyguardNotifica import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl; import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -48,7 +49,8 @@ public class TestableNotificationInterruptStateProviderImpl NotifPipelineFlags flags, KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider, UiEventLogger uiEventLogger, - UserTracker userTracker) { + UserTracker userTracker, + DeviceProvisionedController deviceProvisionedController) { super(contentResolver, powerManager, ambientDisplayConfiguration, @@ -61,7 +63,8 @@ public class TestableNotificationInterruptStateProviderImpl flags, keyguardNotificationVisibilityProvider, uiEventLogger, - userTracker); + userTracker, + deviceProvisionedController); mUseHeadsUp = true; } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index f2e45281da93..4fc3e3f66e6d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt @@ -24,20 +24,19 @@ import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationResultModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel +import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow class FakeAuthenticationRepository( + private val deviceEntryRepository: FakeDeviceEntryRepository, private val currentTime: () -> Long, ) : AuthenticationRepository { private val _isAutoConfirmEnabled = MutableStateFlow(false) override val isAutoConfirmEnabled: StateFlow<Boolean> = _isAutoConfirmEnabled.asStateFlow() - private val _isUnlocked = MutableStateFlow(false) - override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() - override val hintedPinLength: Int = HINTING_PIN_LENGTH private val _isPatternVisible = MutableStateFlow(true) @@ -53,7 +52,6 @@ class FakeAuthenticationRepository( override val minPatternLength: Int = 4 - private var isLockscreenEnabled = true private var failedAttemptCount = 0 private var throttlingEndTimestamp = 0L private var credentialOverride: List<Any>? = null @@ -72,13 +70,9 @@ class FakeAuthenticationRepository( credentialOverride = pin } - override suspend fun isLockscreenEnabled(): Boolean { - return isLockscreenEnabled - } - override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) { failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1 - _isUnlocked.value = isSuccessful + deviceEntryRepository.setUnlocked(isSuccessful) } override suspend fun getPinLength(): Int { @@ -97,18 +91,10 @@ class FakeAuthenticationRepository( _throttling.value = throttlingModel } - fun setUnlocked(isUnlocked: Boolean) { - _isUnlocked.value = isUnlocked - } - fun setAutoConfirmEnabled(isEnabled: Boolean) { _isAutoConfirmEnabled.value = isEnabled } - fun setLockscreenEnabled(isLockscreenEnabled: Boolean) { - this.isLockscreenEnabled = isLockscreenEnabled - } - override suspend fun setThrottleDuration(durationMs: Int) { throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0 } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt index 21a5eb7022b0..28557d3e4046 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt @@ -17,6 +17,7 @@ package com.android.systemui.broadcast import android.content.BroadcastReceiver +import android.content.BroadcastReceiver.PendingResult import android.content.Context import android.content.Intent import android.content.IntentFilter @@ -28,7 +29,6 @@ import com.android.systemui.SysuiTestableContext import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.dump.DumpManager import com.android.systemui.settings.UserTracker -import java.lang.IllegalStateException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executor @@ -96,8 +96,14 @@ class FakeBroadcastDispatcher( /** * Sends the given [intent] to *only* the receivers that were registered with an [IntentFilter] * that matches the intent. + * + * A non-null [pendingResult] can be used to pass the sending user. */ - fun sendIntentToMatchingReceiversOnly(context: Context, intent: Intent) { + fun sendIntentToMatchingReceiversOnly( + context: Context, + intent: Intent, + pendingResult: PendingResult? = null + ) { receivers.forEach { if ( it.filter.match( @@ -107,6 +113,9 @@ class FakeBroadcastDispatcher( /* logTag= */ "FakeBroadcastDispatcher", ) > 0 ) { + if (pendingResult != null) { + it.receiver.pendingResult = pendingResult + } it.receiver.onReceive(context, intent) } } @@ -130,4 +139,19 @@ class FakeBroadcastDispatcher( val receiver: BroadcastReceiver, val filter: IntentFilter, ) + + companion object { + fun fakePendingResultForUser(userId: Int) = + PendingResult( + /* resultCode = */ 0, + /* resultData = */ "", + /* resultExtras = */ null, + /* type = */ PendingResult.TYPE_REGISTERED, + /* ordered = */ false, + /* sticky = */ false, + /* token = */ null, + userId, + /* flags = */ 0, + ) + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt new file mode 100644 index 000000000000..5e60a09e006b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt @@ -0,0 +1,35 @@ +package com.android.systemui.deviceentry.data.repository + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Fake implementation of [DeviceEntryRepository] */ +class FakeDeviceEntryRepository : DeviceEntryRepository { + + private var isInsecureLockscreenEnabled = true + private var isBypassEnabled = false + + private val _isUnlocked = MutableStateFlow(false) + override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() + + override fun isBypassEnabled(): Boolean { + return isBypassEnabled + } + + override suspend fun isInsecureLockscreenEnabled(): Boolean { + return isInsecureLockscreenEnabled + } + + fun setUnlocked(isUnlocked: Boolean) { + _isUnlocked.value = isUnlocked + } + + fun setInsecureLockscreenEnabled(isLockscreenEnabled: Boolean) { + this.isInsecureLockscreenEnabled = isLockscreenEnabled + } + + fun setBypassEnabled(isBypassEnabled: Boolean) { + this.isBypassEnabled = isBypassEnabled + } +} 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 a5f5d52ef56c..aa52609d6d47 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 @@ -63,9 +63,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _isKeyguardShowing = MutableStateFlow(false) override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing - private val _isKeyguardUnlocked = MutableStateFlow(false) - override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow() - private val _isKeyguardOccluded = MutableStateFlow(false) override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded @@ -150,11 +147,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { return _isKeyguardShowing.value } - private var _isBypassEnabled = false - override fun isBypassEnabled(): Boolean { - return _isBypassEnabled - } - override fun setAnimateDozingTransitions(animate: Boolean) { _animateBottomAreaDozingTransitions.tryEmit(animate) } @@ -252,14 +244,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _statusBarState.value = state } - fun setKeyguardUnlocked(isUnlocked: Boolean) { - _isKeyguardUnlocked.value = isUnlocked - } - - fun setBypassEnabled(isEnabled: Boolean) { - _isBypassEnabled = isEnabled - } - fun setScreenModel(screenModel: ScreenModel) { _screenModel.value = screenModel } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt index ceab8e9e52ee..945aaede0087 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt @@ -50,6 +50,7 @@ import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode import com.android.systemui.user.domain.interactor.RefreshUsersScheduler import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.utils.UserRestrictionChecker import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.TestScope import org.mockito.Mockito.mock @@ -137,6 +138,7 @@ object KeyguardDismissInteractorFactory { refreshUsersScheduler = mock(RefreshUsersScheduler::class.java), guestUserInteractor = mock(GuestUserInteractor::class.java), uiEventLogger = mock(UiEventLogger::class.java), + userRestrictionChecker = mock(UserRestrictionChecker::class.java), ) return WithDependencies( trustRepository = trustRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt index 2e3bb2b545d8..1cae09b794b8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeCommandQueue @@ -42,6 +43,7 @@ object KeyguardInteractorFactory { sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(), repository: FakeKeyguardRepository = FakeKeyguardRepository(), commandQueue: FakeCommandQueue = FakeCommandQueue(), + deviceEntryRepository: FakeDeviceEntryRepository = FakeDeviceEntryRepository(), bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(), configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(), shadeRepository: FakeShadeRepository = FakeShadeRepository(), @@ -52,6 +54,7 @@ object KeyguardInteractorFactory { commandQueue = commandQueue, featureFlags = featureFlags, sceneContainerFlags = sceneContainerFlags, + deviceEntryRepository = deviceEntryRepository, bouncerRepository = bouncerRepository, configurationRepository = configurationRepository, shadeRepository = shadeRepository, @@ -60,6 +63,7 @@ object KeyguardInteractorFactory { commandQueue = commandQueue, featureFlags = featureFlags, sceneContainerFlags = sceneContainerFlags, + deviceEntryRepository = deviceEntryRepository, bouncerRepository = bouncerRepository, configurationRepository = configurationRepository, shadeRepository = shadeRepository, @@ -69,7 +73,7 @@ object KeyguardInteractorFactory { } /** Provide defaults, otherwise tests will throw an error */ - fun createFakeFeatureFlags(): FakeFeatureFlags { + private fun createFakeFeatureFlags(): FakeFeatureFlags { return FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, false) } } @@ -78,6 +82,7 @@ object KeyguardInteractorFactory { val commandQueue: FakeCommandQueue, val featureFlags: FakeFeatureFlags, val sceneContainerFlags: SceneContainerFlags, + val deviceEntryRepository: FakeDeviceEntryRepository, val bouncerRepository: FakeKeyguardBouncerRepository, val configurationRepository: FakeConfigurationRepository, val shadeRepository: FakeShadeRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt index bf26e719433d..cbf4ae5e3014 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt @@ -21,6 +21,6 @@ import com.android.systemui.plugins.qs.QSTile class FakeQSFactory(private val tileCreator: (String) -> QSTile?) : QSFactory { override fun createTile(tileSpec: String): QSTile? { - return tileCreator(tileSpec) + return tileCreator(tileSpec)?.also { it.tileSpec = tileSpec } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt index 9ea079fc9c4b..57ad28289ebd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeAutoAddRepository.kt @@ -16,15 +16,16 @@ package com.android.systemui.qs.pipeline.data.repository +import com.android.systemui.qs.pipeline.data.model.RestoreData import com.android.systemui.qs.pipeline.shared.TileSpec -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow class FakeAutoAddRepository : AutoAddRepository { private val autoAddedTilesPerUser = mutableMapOf<Int, MutableStateFlow<Set<TileSpec>>>() - override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> { + override suspend fun autoAddedTiles(userId: Int): StateFlow<Set<TileSpec>> { return getFlow(userId) } @@ -39,4 +40,8 @@ class FakeAutoAddRepository : AutoAddRepository { private fun getFlow(userId: Int): MutableStateFlow<Set<TileSpec>> = autoAddedTilesPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) } + + override suspend fun reconcileRestore(restoreData: RestoreData) { + with(getFlow(restoreData.userId)) { value = value + restoreData.restoredAutoAddedTiles } + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeQSSettingsRestoredRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeQSSettingsRestoredRepository.kt new file mode 100644 index 000000000000..e0c2154f2aba --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeQSSettingsRestoredRepository.kt @@ -0,0 +1,16 @@ +package com.android.systemui.qs.pipeline.data.repository + +import com.android.systemui.qs.pipeline.data.model.RestoreData +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow + +class FakeQSSettingsRestoredRepository : QSSettingsRestoredRepository { + private val _restoreData = MutableSharedFlow<RestoreData>() + + override val restoreData: Flow<RestoreData> + get() = _restoreData + + suspend fun onDataRestored(restoreData: RestoreData) { + _restoreData.emit(restoreData) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt index aa8dbe120ca4..ae4cf3afe671 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.pipeline.data.repository +import com.android.systemui.qs.pipeline.data.model.RestoreData import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END import com.android.systemui.qs.pipeline.shared.TileSpec import kotlinx.coroutines.flow.Flow @@ -26,7 +27,7 @@ class FakeTileSpecRepository : TileSpecRepository { private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>() - override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> { + override suspend fun tilesSpecs(userId: Int): Flow<List<TileSpec>> { return getFlow(userId).asStateFlow() } @@ -57,4 +58,13 @@ class FakeTileSpecRepository : TileSpecRepository { private fun getFlow(userId: Int): MutableStateFlow<List<TileSpec>> = tilesPerUser.getOrPut(userId) { MutableStateFlow(emptyList()) } + + override suspend fun reconcileRestore( + restoreData: RestoreData, + currentAutoAdded: Set<TileSpec> + ) { + with(getFlow(restoreData.userId)) { + value = UserTileSpecRepository.reconcileTiles(value, currentAutoAdded, restoreData) + } + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt new file mode 100644 index 000000000000..f62bf6014374 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.base.interactor + +class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor { + + var handleResult: Boolean = false + var policyResult: DisabledByPolicyInteractor.PolicyResult = + DisabledByPolicyInteractor.PolicyResult.TileEnabled + + override suspend fun isDisabled( + userId: Int, + userRestriction: String? + ): DisabledByPolicyInteractor.PolicyResult = policyResult + + override fun handlePolicyResult( + policyResult: DisabledByPolicyInteractor.PolicyResult + ): Boolean = handleResult +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt index 13437c925367..1cb4ab76c9d5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.base.interactor import javax.annotation.CheckReturnValue diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt index 4e0266e25716..9c99cb52d5ee 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.qs.tiles.base.interactor import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 69c89e8f4af6..179206ff87b1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -34,6 +34,9 @@ import com.android.systemui.common.ui.data.repository.FakeConfigurationRepositor import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository +import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeCommandQueue @@ -73,16 +76,10 @@ class SceneTestUtils( val testScope = TestScope(testDispatcher) val featureFlags = FakeFeatureFlagsClassic().apply { set(Flags.FACE_AUTH_REFACTOR, false) } val sceneContainerFlags = FakeSceneContainerFlags().apply { enabled = true } - private val userRepository: UserRepository by lazy { - FakeUserRepository().apply { - val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0)) - setUserInfos(users) - runBlocking { setSelectedUserInfo(users.first()) } - } - } - + val deviceEntryRepository: FakeDeviceEntryRepository by lazy { FakeDeviceEntryRepository() } val authenticationRepository: FakeAuthenticationRepository by lazy { FakeAuthenticationRepository( + deviceEntryRepository = deviceEntryRepository, currentTime = { testScope.currentTime }, ) } @@ -103,6 +100,14 @@ class SceneTestUtils( } val powerRepository: FakePowerRepository by lazy { FakePowerRepository() } + private val userRepository: UserRepository by lazy { + FakeUserRepository().apply { + val users = listOf(UserInfo(/* id= */ 0, "name", /* flags= */ 0)) + setUserInfos(users) + runBlocking { setSelectedUserInfo(users.first()) } + } + } + private val context = test.context private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() } @@ -145,31 +150,41 @@ class SceneTestUtils( ) } - fun authenticationRepository(): FakeAuthenticationRepository { - return authenticationRepository + fun deviceEntryInteractor( + repository: DeviceEntryRepository = deviceEntryRepository, + authenticationInteractor: AuthenticationInteractor, + sceneInteractor: SceneInteractor, + ): DeviceEntryInteractor { + return DeviceEntryInteractor( + applicationScope = applicationScope(), + repository = repository, + authenticationInteractor = authenticationInteractor, + sceneInteractor = sceneInteractor, + ) } fun authenticationInteractor( - repository: AuthenticationRepository, - sceneInteractor: SceneInteractor = sceneInteractor(), + repository: AuthenticationRepository = authenticationRepository, ): AuthenticationInteractor { return AuthenticationInteractor( applicationScope = applicationScope(), repository = repository, backgroundDispatcher = testDispatcher, userRepository = userRepository, - keyguardRepository = keyguardRepository, - sceneInteractor = sceneInteractor, + deviceEntryRepository = deviceEntryRepository, clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } } ) } - fun keyguardInteractor(repository: KeyguardRepository): KeyguardInteractor { + fun keyguardInteractor( + repository: KeyguardRepository = keyguardRepository + ): KeyguardInteractor { return KeyguardInteractor( repository = repository, commandQueue = FakeCommandQueue(), featureFlags = featureFlags, sceneContainerFlags = sceneContainerFlags, + deviceEntryRepository = FakeDeviceEntryRepository(), bouncerRepository = FakeKeyguardBouncerRepository(), configurationRepository = FakeConfigurationRepository(), shadeRepository = FakeShadeRepository(), @@ -185,6 +200,7 @@ class SceneTestUtils( } fun bouncerInteractor( + deviceEntryInteractor: DeviceEntryInteractor, authenticationInteractor: AuthenticationInteractor, sceneInteractor: SceneInteractor, ): BouncerInteractor { @@ -192,6 +208,7 @@ class SceneTestUtils( applicationScope = applicationScope(), applicationContext = context, repository = BouncerRepository(), + deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, flags = sceneContainerFlags, diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 0480c22233a6..11189cf9e39c 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -25,5 +25,12 @@ flag { name: "send_a11y_events_based_on_state" namespace: "accessibility" description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness." -bug: "295575684" -}
\ No newline at end of file + bug: "295575684" +} + +flag { + name: "add_window_token_without_lock" + namespace: "accessibility" + description: "Calls WMS.addWindowToken without holding A11yManagerService#mLock" + bug: "297972548" +} diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 05b6eb4a64c1..fa73cffb83ea 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1506,11 +1506,17 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - public void onAdded() { + /** + * Called when the connection is first created. Add a window token for all known displays. + * <p> + * <strong>Note:</strong> Should not be called while holding the AccessibilityManagerService + * lock because this calls out to WindowManagerService. + */ + void addWindowTokensForAllDisplays() { final Display[] displays = mDisplayManager.getDisplays(); for (int i = 0; i < displays.length; i++) { final int displayId = displays[i].getDisplayId(); - onDisplayAdded(displayId); + addWindowTokenForDisplay(displayId); } } @@ -1518,9 +1524,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ * Called whenever a logical display has been added to the system. Add a window token for adding * an accessibility overlay. * + * <p> + * <strong>Note:</strong> Should not be called while holding the AccessibilityManagerService + * lock because this calls out to WindowManagerService. + * * @param displayId The id of the logical display that was added. */ - public void onDisplayAdded(int displayId) { + void addWindowTokenForDisplay(int displayId) { final long identity = Binder.clearCallingIdentity(); try { final IBinder overlayWindowToken = new Binder(); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 93ba362070d3..60d4ee61fdd4 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -154,6 +154,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IntPair; +import com.android.internal.util.Preconditions; import com.android.server.AccessibilityManagerInternal; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -4500,6 +4501,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private int mSystemUiUid = 0; AccessibilityDisplayListener(Context context, Handler handler) { + if (Flags.addWindowTokenWithoutLock()) { + // Avoid concerns about one thread adding displays while another thread removes + // them by ensuring the looper is the main looper and the DisplayListener + // callbacks are always executed on the one main thread. + final boolean isMainHandler = handler.getLooper() == Looper.getMainLooper(); + final String errorMessage = + "AccessibilityDisplayListener must use the main handler"; + if (Build.IS_USERDEBUG || Build.IS_ENG) { + Preconditions.checkArgument(isMainHandler, errorMessage); + } else if (!isMainHandler) { + Slog.e(LOG_TAG, errorMessage); + } + } + mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); mDisplayManager.registerDisplayListener(this, handler); initializeDisplayList(); @@ -4541,11 +4556,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onDisplayAdded(int displayId) { + if (Flags.addWindowTokenWithoutLock()) { + final boolean isMainThread = Looper.getMainLooper().isCurrentThread(); + final String errorMessage = "onDisplayAdded must be called from the main thread"; + if (Build.IS_USERDEBUG || Build.IS_ENG) { + Preconditions.checkArgument(isMainThread, errorMessage); + } else if (!isMainThread) { + Slog.e(LOG_TAG, errorMessage); + } + } final Display display = mDisplayManager.getDisplay(displayId); if (!isValidDisplay(display)) { return; } + final List<AccessibilityServiceConnection> services; synchronized (mLock) { mDisplaysList.add(display); mA11yOverlayLayers.put( @@ -4554,21 +4579,42 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mInputFilter.onDisplayAdded(display); } AccessibilityUserState userState = getCurrentUserStateLocked(); - if (displayId != Display.DEFAULT_DISPLAY) { - final List<AccessibilityServiceConnection> services = userState.mBoundServices; - for (int i = 0; i < services.size(); i++) { - AccessibilityServiceConnection boundClient = services.get(i); - boundClient.onDisplayAdded(displayId); + if (Flags.addWindowTokenWithoutLock()) { + services = new ArrayList<>(userState.mBoundServices); + } else { + services = userState.mBoundServices; + if (displayId != Display.DEFAULT_DISPLAY) { + for (int i = 0; i < services.size(); i++) { + AccessibilityServiceConnection boundClient = services.get(i); + boundClient.addWindowTokenForDisplay(displayId); + } } } updateMagnificationLocked(userState); updateWindowsForAccessibilityCallbackLocked(userState); notifyClearAccessibilityCacheLocked(); } + if (Flags.addWindowTokenWithoutLock()) { + if (displayId != Display.DEFAULT_DISPLAY) { + for (int i = 0; i < services.size(); i++) { + AccessibilityServiceConnection boundClient = services.get(i); + boundClient.addWindowTokenForDisplay(displayId); + } + } + } } @Override public void onDisplayRemoved(int displayId) { + if (Flags.addWindowTokenWithoutLock()) { + final boolean isMainThread = Looper.getMainLooper().isCurrentThread(); + final String errorMessage = "onDisplayRemoved must be called from the main thread"; + if (Build.IS_USERDEBUG || Build.IS_ENG) { + Preconditions.checkArgument(isMainThread, errorMessage); + } else if (!isMainThread) { + Slog.e(LOG_TAG, errorMessage); + } + } synchronized (mLock) { if (!removeDisplayFromList(displayId)) { return; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 9e700734d821..7a2a60263d38 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -44,7 +44,6 @@ import android.util.Slog; import android.view.Display; import android.view.MotionEvent; - import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -169,6 +168,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public void onServiceConnected(ComponentName componentName, IBinder service) { + AccessibilityUserState userState = mUserStateWeakReference.get(); + if (userState != null && Flags.addWindowTokenWithoutLock()) { + addWindowTokensForAllDisplays(); + } synchronized (mLock) { if (mService != service) { if (mService != null) { @@ -184,7 +187,6 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service); - AccessibilityUserState userState = mUserStateWeakReference.get(); if (userState == null) return; userState.addServiceLocked(this); mSystemSupport.onClientChangeLocked(false); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index ab6cc71fecab..693526adc8d6 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -224,7 +224,9 @@ class AccessibilityUserState { void addServiceLocked(AccessibilityServiceConnection serviceConnection) { if (!mBoundServices.contains(serviceConnection)) { - serviceConnection.onAdded(); + if (!Flags.addWindowTokenWithoutLock()) { + serviceConnection.addWindowTokensForAllDisplays(); + } mBoundServices.add(serviceConnection); mComponentNameToServiceMap.put(serviceConnection.getComponentName(), serviceConnection); mServiceInfoChangeListener.onServiceInfoChangedLocked(this); diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 208acdfb49e3..53c629a9ed2d 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -25,9 +25,11 @@ import android.app.UiAutomation; import android.content.ComponentName; import android.content.Context; import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; +import android.os.Looper; import android.os.RemoteCallback; import android.os.RemoteException; import android.util.Slog; @@ -35,6 +37,7 @@ import android.view.Display; import android.view.accessibility.AccessibilityEvent; import com.android.internal.util.DumpUtils; +import com.android.internal.util.Preconditions; import com.android.server.utils.Slogf; import com.android.server.wm.WindowManagerInternal; @@ -98,46 +101,47 @@ class UiAutomationManager { accessibilityServiceInfo.setComponentName(COMPONENT_NAME); Slogf.i(LOG_TAG, "Registering UiTestAutomationService (id=%s) when called by user %d", accessibilityServiceInfo.getId(), Binder.getCallingUserHandle().getIdentifier()); - synchronized (mLock) { - if (mUiAutomationService != null) { - throw new IllegalStateException( - "UiAutomationService " + mUiAutomationService.mServiceInterface - + "already registered!"); - } - - try { - owner.linkToDeath(mUiAutomationServiceOwnerDeathRecipient, 0); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Couldn't register for the death of a UiTestAutomationService!", - re); - return; - } + if (mUiAutomationService != null) { + throw new IllegalStateException( + "UiAutomationService " + mUiAutomationService.mServiceInterface + + "already registered!"); + } - mUiAutomationFlags = flags; - mSystemSupport = systemSupport; - // Ignore registering UiAutomation if it is not allowed to use the accessibility - // subsystem. - if (!useAccessibility()) { - return; - } - mUiAutomationService = new UiAutomationService(context, accessibilityServiceInfo, id, - mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal, - systemActionPerformer, awm); - mUiAutomationServiceOwner = owner; - mUiAutomationService.mServiceInterface = serviceClient; - try { - mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService, - 0); - } catch (RemoteException re) { - Slog.e(LOG_TAG, "Failed registering death link: " + re); - destroyUiAutomationService(); - return; - } + try { + owner.linkToDeath(mUiAutomationServiceOwnerDeathRecipient, 0); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Couldn't register for the death of a UiTestAutomationService!", + re); + return; + } - mUiAutomationService.onAdded(); + mUiAutomationFlags = flags; + mSystemSupport = systemSupport; + // Ignore registering UiAutomation if it is not allowed to use the accessibility + // subsystem. + if (!useAccessibility()) { + return; + } + mUiAutomationService = new UiAutomationService(context, accessibilityServiceInfo, id, + mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal, + systemActionPerformer, awm); + mUiAutomationServiceOwner = owner; + mUiAutomationService.mServiceInterface = serviceClient; + try { + mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService, + 0); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Failed registering death link: " + re); + destroyUiAutomationService(); + return; + } - mUiAutomationService.connectServiceUnknownThread(); + if (!Flags.addWindowTokenWithoutLock()) { + mUiAutomationService.addWindowTokensForAllDisplays(); } + // UiAutomationService#connectServiceUnknownThread posts to a handler + // so this call should return immediately. + mUiAutomationService.connectServiceUnknownThread(); } void unregisterUiTestAutomationServiceLocked(IAccessibilityServiceClient serviceClient) { @@ -253,6 +257,13 @@ class UiAutomationManager { super(context, COMPONENT_NAME, accessibilityServiceInfo, id, mainHandler, lock, securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerformer, awm); + final boolean isMainHandler = mainHandler.getLooper() == Looper.getMainLooper(); + final String errorMessage = "UiAutomationService must use the main handler"; + if (Build.IS_USERDEBUG || Build.IS_ENG) { + Preconditions.checkArgument(isMainHandler, errorMessage); + } else if (!isMainHandler) { + Slog.e(LOG_TAG, errorMessage); + } mMainHandler = mainHandler; setDisplayTypes(DISPLAY_TYPE_DEFAULT | DISPLAY_TYPE_PROXY); } @@ -274,6 +285,9 @@ class UiAutomationManager { // If the serviceInterface is null, the UiAutomation has been shut down on // another thread. if (serviceInterface != null) { + if (Flags.addWindowTokenWithoutLock()) { + mUiAutomationService.addWindowTokensForAllDisplays(); + } if (mTrace.isA11yTracingEnabledForTypes( AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) { mTrace.logTrace("UiAutomationService.connectServiceUnknownThread", @@ -286,7 +300,7 @@ class UiAutomationManager { mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); } } catch (RemoteException re) { - Slog.w(LOG_TAG, "Error initialized connection", re); + Slog.w(LOG_TAG, "Error initializing connection", re); destroyUiAutomationService(); } }); diff --git a/services/companion/Android.bp b/services/companion/Android.bp index 25f57b3c5331..fb8db21c3090 100644 --- a/services/companion/Android.bp +++ b/services/companion/Android.bp @@ -19,7 +19,10 @@ filegroup { java_library_static { name: "services.companion", defaults: ["platform_service_defaults"], - srcs: [":services.companion-sources"], + srcs: [ + ":services.companion-sources", + ":VirtualCamera-aidl-sources", + ], libs: [ "app-compat-annotations", "services.core", @@ -29,3 +32,11 @@ java_library_static { "virtualdevice_flags_lib", ], } + +filegroup { + name: "VirtualCamera-aidl-sources", + srcs: [ + "java/com/android/server/companion/virtual/camera/*.aidl", + ], + path: "java", +} diff --git a/services/companion/OWNERS b/services/companion/OWNERS index 734d8b6c5f43..dcf2377b4be1 100644 --- a/services/companion/OWNERS +++ b/services/companion/OWNERS @@ -1 +1,3 @@ -include /core/java/android/companion/OWNERS
\ No newline at end of file +include /core/java/android/companion/OWNERS + +per-file Android.bp,lint-baseline.xml,OWNERS=file:java/com/android/server/companion/virtual/OWNERS
\ No newline at end of file diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index ba4533960db4..c79149816b1a 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -155,6 +155,7 @@ public class CompanionDeviceManagerService extends SystemService { "debug.cdm.cdmservice.removal_time_window"; private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90); + private static final int MAX_CN_LENGTH = 500; private final ActivityManager mActivityManager; private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener; @@ -757,6 +758,9 @@ public class CompanionDeviceManagerService extends SystemService { String callingPackage = component.getPackageName(); checkCanCallNotificationApi(callingPackage); // TODO: check userId. + if (component.flattenToString().length() > MAX_CN_LENGTH) { + throw new IllegalArgumentException("Component name is too long."); + } final long identity = Binder.clearCallingIdentity(); try { return PendingIntent.getActivityAsUser(getContext(), diff --git a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java index e8839a2a76ae..720687ef20cc 100644 --- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java +++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java @@ -562,7 +562,8 @@ public class SecureChannel { private byte[] constructToken(D2DHandshakeContext.Role role, byte[] authValue) throws GeneralSecurityException { MessageDigest hash = MessageDigest.getInstance("SHA-256"); - byte[] roleUtf8 = role.name().getBytes(StandardCharsets.UTF_8); + String roleName = role == Role.INITIATOR ? "Initiator" : "Responder"; + byte[] roleUtf8 = roleName.getBytes(StandardCharsets.UTF_8); int tokenLength = roleUtf8.length + authValue.length; return hash.digest(ByteBuffer.allocate(tokenLength) .put(roleUtf8) diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index a3ccb168aa4e..9dd0dca47f0e 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -113,13 +113,15 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController private final boolean mCrossTaskNavigationAllowedByDefault; @NonNull private final ArraySet<ComponentName> mCrossTaskNavigationExemptions; + @Nullable + private final ComponentName mPermissionDialogComponent; private final Object mGenericWindowPolicyControllerLock = new Object(); @Nullable private final ActivityBlockedCallback mActivityBlockedCallback; private int mDisplayId = Display.INVALID_DISPLAY; @NonNull @GuardedBy("mGenericWindowPolicyControllerLock") - final ArraySet<Integer> mRunningUids = new ArraySet<>(); + private final ArraySet<Integer> mRunningUids = new ArraySet<>(); @Nullable private final ActivityListener mActivityListener; @Nullable private final PipBlockedCallback mPipBlockedCallback; @Nullable private final IntentListenerCallback mIntentListenerCallback; @@ -171,6 +173,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController @NonNull Set<ComponentName> activityPolicyExemptions, boolean crossTaskNavigationAllowedByDefault, @NonNull Set<ComponentName> crossTaskNavigationExemptions, + @Nullable ComponentName permissionDialogComponent, @Nullable ActivityListener activityListener, @Nullable PipBlockedCallback pipBlockedCallback, @Nullable ActivityBlockedCallback activityBlockedCallback, @@ -185,6 +188,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController mActivityPolicyExemptions = activityPolicyExemptions; mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault; mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions); + mPermissionDialogComponent = permissionDialogComponent; mActivityBlockedCallback = activityBlockedCallback; setInterestedWindowFlags(windowFlags, systemWindowFlags); mActivityListener = activityListener; @@ -309,6 +313,13 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController return false; } + // mPermissionDialogComponent being null means we don't want to block permission Dialogs + // based on FLAG_STREAM_PERMISSIONS + if (mPermissionDialogComponent != null + && mPermissionDialogComponent.equals(activityComponent)) { + return false; + } + return true; } diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING index 253ef43fa086..3583a7816ab2 100644 --- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING +++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING @@ -39,7 +39,7 @@ "include-filter": "android.hardware.input.cts.tests" }, { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" } ], "file_patterns": ["Virtual[^/]*\\.java"] diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 203a152ccc73..8a2aa616f8e6 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -24,6 +24,7 @@ import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAUL import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS; +import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; @@ -49,6 +50,7 @@ import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; +import android.companion.virtual.camera.IVirtualCamera; import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorEvent; @@ -104,6 +106,7 @@ import com.android.internal.app.BlockedAppStreamingActivity; import com.android.server.LocalServices; import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener; import com.android.server.companion.virtual.audio.VirtualAudioController; +import com.android.server.companion.virtual.camera.VirtualCameraController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -113,7 +116,6 @@ import java.util.Objects; import java.util.Set; import java.util.function.Consumer; - final class VirtualDeviceImpl extends IVirtualDevice.Stub implements IBinder.DeathRecipient, RunningAppsChangedListener { @@ -177,6 +179,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final InputController mInputController; private final SensorController mSensorController; private final CameraAccessController mCameraAccessController; + @Nullable // Null if virtual camera flag is off. + private final VirtualCameraController mVirtualCameraController; private VirtualAudioController mVirtualAudioController; private final IBinder mAppToken; private final VirtualDeviceParams mParams; @@ -204,6 +208,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @GuardedBy("mVirtualDeviceLock") @NonNull private final Set<ComponentName> mActivityPolicyExemptions; + private final ComponentName mPermissionDialogComponent; private ActivityListener createListenerAdapter() { return new ActivityListener() { @@ -268,7 +273,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub soundEffectListener, runningAppsChangedCallback, params, - DisplayManagerGlobal.getInstance()); + DisplayManagerGlobal.getInstance(), + Flags.virtualCamera() ? new VirtualCameraController(context) : null); } @VisibleForTesting @@ -287,7 +293,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub IVirtualDeviceSoundEffectListener soundEffectListener, Consumer<ArraySet<Integer>> runningAppsChangedCallback, VirtualDeviceParams params, - DisplayManagerGlobal displayManager) { + DisplayManagerGlobal displayManager, + VirtualCameraController virtualCameraController) { super(PermissionEnforcer.fromContext(context)); mVirtualDeviceLog = virtualDeviceLog; mOwnerPackageName = attributionSource.getPackageName(); @@ -317,6 +324,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mParams.getVirtualSensorCallback(), mParams.getVirtualSensorConfigs()); mCameraAccessController = cameraAccessController; mCameraAccessController.startObservingIfNeeded(); + if (!Flags.streamPermissions()) { + mPermissionDialogComponent = getPermissionDialogComponent(); + } else { + mPermissionDialogComponent = null; + } + mVirtualCameraController = virtualCameraController; try { token.linkToDeath(this, 0); } catch (RemoteException e) { @@ -324,8 +337,14 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } mVirtualDeviceLog.logCreated(deviceId, mOwnerUid); - mPublicVirtualDeviceObject = new VirtualDevice( - this, getDeviceId(), getPersistentDeviceId(), mParams.getName()); + if (Flags.vdmPublicApis()) { + mPublicVirtualDeviceObject = new VirtualDevice( + this, getDeviceId(), getPersistentDeviceId(), mParams.getName(), + getDisplayName()); + } else { + mPublicVirtualDeviceObject = new VirtualDevice( + this, getDeviceId(), getPersistentDeviceId(), mParams.getName()); + } if (Flags.dynamicPolicy()) { mActivityPolicyExemptions = new ArraySet<>( @@ -551,6 +570,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } finally { Binder.restoreCallingIdentity(ident); } + if (mVirtualCameraController != null) { + mVirtualCameraController.close(); + } } @Override @@ -907,24 +929,37 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void registerVirtualCamera(@NonNull IVirtualCamera camera) { + super.registerVirtualCamera_enforcePermission(); + if (mVirtualCameraController == null) { + return; + } + mVirtualCameraController.registerCamera(Objects.requireNonNull(camera)); + } + @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + String indent = " "; fout.println(" VirtualDevice: "); - fout.println(" mDeviceId: " + mDeviceId); - fout.println(" mAssociationId: " + mAssociationInfo.getId()); - fout.println(" mOwnerPackageName: " + mOwnerPackageName); - fout.println(" mParams: "); - mParams.dump(fout, " "); - fout.println(" mVirtualDisplayIds: "); + fout.println(indent + "mDeviceId: " + mDeviceId); + fout.println(indent + "mAssociationId: " + mAssociationInfo.getId()); + fout.println(indent + "mOwnerPackageName: " + mOwnerPackageName); + fout.println(indent + "mParams: "); + mParams.dump(fout, indent + indent); + fout.println(indent + "mVirtualDisplayIds: "); synchronized (mVirtualDeviceLock) { fout.println(" mDevicePolicies: " + mDevicePolicies); for (int i = 0; i < mVirtualDisplays.size(); i++) { - fout.println(" " + mVirtualDisplays.keyAt(i)); + fout.println(indent + " " + mVirtualDisplays.keyAt(i)); } - fout.println(" mDefaultShowPointerIcon: " + mDefaultShowPointerIcon); + fout.println(indent + "mDefaultShowPointerIcon: " + mDefaultShowPointerIcon); } mInputController.dump(fout); mSensorController.dump(fout); + if (mVirtualCameraController != null) { + mVirtualCameraController.dump(fout, indent); + } } @GuardedBy("mVirtualDeviceLock") @@ -951,6 +986,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub /*crossTaskNavigationExemptions=*/crossTaskNavigationAllowedByDefault ? mParams.getBlockedCrossTaskNavigations() : mParams.getAllowedCrossTaskNavigations(), + mPermissionDialogComponent, createListenerAdapter(), this::onEnteringPipBlocked, this::onActivityBlocked, @@ -963,6 +999,13 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return gwpc; } + private ComponentName getPermissionDialogComponent() { + Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS); + PackageManager packageManager = mContext.getPackageManager(); + intent.setPackage(packageManager.getPermissionControllerPackageName()); + return intent.resolveActivity(packageManager); + } + int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig, @NonNull IVirtualDisplayCallback callback, String packageName) { GenericWindowPolicyController gwpc; diff --git a/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl b/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl new file mode 100644 index 000000000000..a4c1c4249697 --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual.camera; + +import android.companion.virtual.camera.IVirtualCamera; +import android.companion.virtual.camera.VirtualCameraHalConfig; + +/** + * AIDL Interface to communicate with the VirtualCamera HAL + * @hide + */ +interface IVirtualCameraService { + + /** + * Registers a new camera with the virtual camera hal. + * @return true if the camera was successfully registered + */ + boolean registerCamera(in IVirtualCamera camera); + + /** + * Unregisters the camera from the virtual camera hal. After this call the virtual camera won't + * be visible to the camera framework anymore. + */ + void unregisterCamera(in IVirtualCamera camera); +} diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java new file mode 100644 index 000000000000..031d94962844 --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual.camera; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.companion.virtual.camera.IVirtualCamera; +import android.companion.virtual.camera.VirtualCameraHalConfig; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * Manages the registration and removal of virtual camera from the server side. + * + * <p>This classes delegate calls to the virtual camera service, so it is dependent on the service + * to be up and running + */ +public class VirtualCameraController implements IBinder.DeathRecipient, ServiceConnection { + + private static class VirtualCameraInfo { + + private final IVirtualCamera mVirtualCamera; + private boolean mIsRegistered; + + VirtualCameraInfo(IVirtualCamera virtualCamera) { + mVirtualCamera = virtualCamera; + } + } + + private static final String TAG = "VirtualCameraController"; + + private static final String VIRTUAL_CAMERA_SERVICE_PACKAGE = "com.android.virtualcamera"; + private static final String VIRTUAL_CAMERA_SERVICE_CLASS = ".VirtualCameraService"; + private final Context mContext; + + @Nullable private IVirtualCameraService mVirtualCameraService = null; + + @GuardedBy("mCameras") + private final Map<IVirtualCamera, VirtualCameraInfo> mCameras = new HashMap<>(1); + + public VirtualCameraController(Context context) { + mContext = context; + connectVirtualCameraService(); + } + + private void connectVirtualCameraService() { + final long callingId = Binder.clearCallingIdentity(); + try { + Intent intent = new Intent(); + intent.setPackage(VIRTUAL_CAMERA_SERVICE_PACKAGE); + intent.setComponent( + ComponentName.createRelative( + VIRTUAL_CAMERA_SERVICE_PACKAGE, VIRTUAL_CAMERA_SERVICE_CLASS)); + mContext.startServiceAsUser(intent, UserHandle.SYSTEM); + if (!mContext.bindServiceAsUser( + intent, + this, + Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, + UserHandle.SYSTEM)) { + mContext.unbindService(this); + Log.w( + TAG, + "connectVirtualCameraService: Failed to connect to the virtual camera " + + "service"); + } + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + + private void forwardPendingRegistrations() { + IVirtualCameraService cameraService = mVirtualCameraService; + if (cameraService == null) { + return; + } + synchronized (mCameras) { + for (VirtualCameraInfo cameraInfo : mCameras.values()) { + if (cameraInfo.mIsRegistered) { + continue; + } + try { + cameraService.registerCamera(cameraInfo.mVirtualCamera); + cameraInfo.mIsRegistered = true; + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + } + + /** + * Remove the virtual camera with the provided name + * + * @param camera The name of the camera to remove + */ + public void unregisterCamera(@NonNull IVirtualCamera camera) { + IVirtualCameraService virtualCameraService = mVirtualCameraService; + if (virtualCameraService != null) { + try { + virtualCameraService.unregisterCamera(camera); + synchronized (mCameras) { + VirtualCameraInfo cameraInfo = mCameras.remove(camera); + cameraInfo.mIsRegistered = false; + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + /** + * Register a new virtual camera with the provided characteristics. + * + * @param camera The {@link IVirtualCamera} producing the image to communicate with the client. + * @throws IllegalArgumentException if the characteristics could not be parsed. + */ + public void registerCamera(@NonNull IVirtualCamera camera) { + IVirtualCameraService service = mVirtualCameraService; + VirtualCameraInfo virtualCameraInfo = new VirtualCameraInfo(camera); + synchronized (mCameras) { + mCameras.put(camera, virtualCameraInfo); + } + if (service != null) { + try { + if (service.registerCamera(camera)) { + virtualCameraInfo.mIsRegistered = true; + return; + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + // Service was not available or registration failed, save the registration for later + connectVirtualCameraService(); + } + + @Override + public void binderDied() { + Log.d(TAG, "binderDied"); + mVirtualCameraService = null; + } + + @Override + public void onBindingDied(ComponentName name) { + mVirtualCameraService = null; + Log.d(TAG, "onBindingDied() called with: name = [" + name + "]"); + } + + @Override + public void onNullBinding(ComponentName name) { + mVirtualCameraService = null; + Log.d(TAG, "onNullBinding() called with: name = [" + name + "]"); + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + Log.d(TAG, "onServiceConnected: " + name.toString()); + mVirtualCameraService = IVirtualCameraService.Stub.asInterface(service); + try { + service.linkToDeath(this, 0); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + forwardPendingRegistrations(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.d(TAG, "onServiceDisconnected() called with: name = [" + name + "]"); + mVirtualCameraService = null; + } + + /** Release resources associated with this controller. */ + public void close() { + if (mVirtualCameraService == null) { + return; + } + synchronized (mCameras) { + mCameras.forEach( + (name, cameraInfo) -> { + try { + mVirtualCameraService.unregisterCamera(name); + } catch (RemoteException e) { + Log.w( + TAG, + "close(): Camera failed to be removed on camera service.", + e); + } + }); + } + mContext.unbindService(this); + } + + /** Dumps information about this {@link VirtualCameraController} for debugging purposes. */ + public void dump(PrintWriter fout, String indent) { + fout.println(indent + "VirtualCameraController:"); + indent += indent; + fout.printf("%sService:%s\n", indent, mVirtualCameraService); + synchronized (mCameras) { + fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size()); + for (VirtualCameraInfo info : mCameras.values()) { + VirtualCameraHalConfig config = null; + try { + config = info.mVirtualCamera.getHalConfig(); + } catch (RemoteException ex) { + Log.w(TAG, ex); + } + fout.printf( + "%s- %s isRegistered: %s, token: %s\n", + indent, + config == null ? "" : config.displayName, + info.mIsRegistered, + info.mVirtualCamera); + } + } + } +} diff --git a/services/core/Android.bp b/services/core/Android.bp index d9c269410b93..6521fabe5b7c 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -194,6 +194,7 @@ java_library_static { "notification_flags_lib", "camera_platform_flags_core_java_lib", "biometrics_flags_lib", + "am_flags_lib", ], javac_shard_size: 50, javacflags: [ diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java index 2a46d862b991..a1e6b58f971f 100644 --- a/services/core/java/com/android/server/IntentResolver.java +++ b/services/core/java/com/android/server/IntentResolver.java @@ -81,7 +81,7 @@ public abstract class IntentResolver<F, R extends Object> { * Returns whether an intent matches the IntentFilter with a pre-resolved type. */ public static boolean intentMatchesFilter( - IntentFilter filter, Intent intent, String resolvedType) { + IntentFilter filter, Intent intent, String resolvedType, boolean defaultOnly) { final boolean debug = localLOGV || ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); @@ -94,9 +94,13 @@ public abstract class IntentResolver<F, R extends Object> { filter.dump(logPrinter, " "); } - final int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(), + int match = filter.match(intent.getAction(), resolvedType, intent.getScheme(), intent.getData(), intent.getCategories(), TAG); + if (match >= 0 && defaultOnly && !filter.hasCategory(Intent.CATEGORY_DEFAULT)) { + match = IntentFilter.NO_MATCH_CATEGORY; + } + if (match >= 0) { if (debug) { Slog.v(TAG, "Filter matched! match=0x" + Integer.toHexString(match)); diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java index d6f1348a6f3d..ced7773eff56 100644 --- a/services/core/java/com/android/server/MmsServiceBroker.java +++ b/services/core/java/com/android/server/MmsServiceBroker.java @@ -342,7 +342,10 @@ public class MmsServiceBroker extends SystemService { // Check if user is associated with the subscription if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId, - Binder.getCallingUserHandle())) { + Binder.getCallingUserHandle()) + // For inactive sub, fall through to MMS service to have it recorded in metrics. + && isActiveSubId(subId)) { + // Try remind user to use another profile to send. TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId, Binder.getCallingUid(), callingPkg); return; @@ -550,6 +553,17 @@ public class MmsServiceBroker extends SystemService { } } + /** @return true if the subId is active. */ + private boolean isActiveSubId(int subId) { + final long token = Binder.clearCallingIdentity(); + try { + SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class); + return subManager != null && subManager.isActiveSubscriptionId(subId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + private int getPhoneIdFromSubId(int subId) { SubscriptionManager subManager = (SubscriptionManager) mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 25ca509cb949..8cc2665b3562 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -265,17 +265,17 @@ class StorageManagerService extends IStorageManager.Stub @Override public void onUserUnlocking(@NonNull TargetUser user) { - mStorageManagerService.onUnlockUser(user.getUserIdentifier()); + mStorageManagerService.onUserUnlocking(user.getUserIdentifier()); } @Override public void onUserStopped(@NonNull TargetUser user) { - mStorageManagerService.onCleanupUser(user.getUserIdentifier()); + mStorageManagerService.onUserStopped(user.getUserIdentifier()); } @Override public void onUserStopping(@NonNull TargetUser user) { - mStorageManagerService.onStopUser(user.getUserIdentifier()); + mStorageManagerService.onUserStopping(user.getUserIdentifier()); } @Override @@ -1163,8 +1163,8 @@ class StorageManagerService extends IStorageManager.Stub } } - private void onUnlockUser(int userId) { - Slog.d(TAG, "onUnlockUser " + userId); + private void onUserUnlocking(int userId) { + Slog.d(TAG, "onUserUnlocking " + userId); if (userId != UserHandle.USER_SYSTEM) { // Check if this user shares media with another user @@ -1227,8 +1227,8 @@ class StorageManagerService extends IStorageManager.Stub } } - private void onCleanupUser(int userId) { - Slog.d(TAG, "onCleanupUser " + userId); + private void onUserStopped(int userId) { + Slog.d(TAG, "onUserStopped " + userId); try { mVold.onUserStopped(userId); @@ -1242,8 +1242,8 @@ class StorageManagerService extends IStorageManager.Stub } } - private void onStopUser(int userId) { - Slog.i(TAG, "onStopUser " + userId); + private void onUserStopping(int userId) { + Slog.i(TAG, "onUserStopping " + userId); try { mStorageSessionController.onUserStopping(userId); } catch (Exception e) { diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index c20f0aa4a62a..9716cf69015c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -225,7 +225,7 @@ final class ActivityManagerConstants extends ContentObserver { /** * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}. */ - private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false; + private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = Flags.oomadjusterCorrectnessRewrite(); /** * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} diff --git a/services/core/java/com/android/server/am/Android.bp b/services/core/java/com/android/server/am/Android.bp new file mode 100644 index 000000000000..af1200e4bdf8 --- /dev/null +++ b/services/core/java/com/android/server/am/Android.bp @@ -0,0 +1,10 @@ +aconfig_declarations { + name: "am_flags", + package: "com.android.server.am", + srcs: ["*.aconfig"], +} + +java_aconfig_library { + name: "am_flags_lib", + aconfig_declarations: "am_flags", +} diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java index 128bbdf9de9b..907069de8c97 100644 --- a/services/core/java/com/android/server/am/AppBatteryTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryTracker.java @@ -82,6 +82,7 @@ import com.android.server.am.AppRestrictionController.TrackerType; import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider; import com.android.server.pm.UserManagerInternal; +import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Constructor; import java.util.Arrays; @@ -571,8 +572,18 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> builder = new BatteryUsageStatsQuery.Builder() .includeProcessStateData() .aggregateSnapshots(lastUidBatteryUsageStartTs, curStart); - updateBatteryUsageStatsOnceInternal(0, buf, builder, userIds, batteryStatsInternal); + final BatteryUsageStats statsCommit = + updateBatteryUsageStatsOnceInternal(0, + buf, + builder, + userIds, + batteryStatsInternal); curDuration += curStart - lastUidBatteryUsageStartTs; + try { + statsCommit.close(); + } catch (IOException e) { + Slog.w(TAG, "Failed to close a stat"); + } } if (needUpdateUidBatteryUsageInWindow && curDuration >= windowSize) { // If we do have long enough data for the window, save it. @@ -648,8 +659,14 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } } } + try { + stats.close(); + } catch (IOException e) { + Slog.w(TAG, "Failed to close a stat"); + } } + // The BatteryUsageStats object MUST BE CLOSED when finished using private BatteryUsageStats updateBatteryUsageStatsOnceInternal(long expectedDuration, SparseArray<BatteryUsage> buf, BatteryUsageStatsQuery.Builder builder, ArraySet<UserHandle> userIds, BatteryStatsInternal batteryStatsInternal) { @@ -662,7 +679,16 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> // Shouldn't happen unless in test. return null; } + // We need the first stat in the list, so we should + // close out the others. final BatteryUsageStats stats = statsList.get(0); + for (int i = 1; i < statsList.size(); i++) { + try { + statsList.get(i).close(); + } catch (IOException e) { + Slog.w(TAG, "Failed to close a stat in BatteryUsageStats List"); + } + } final List<UidBatteryConsumer> uidConsumers = stats.getUidBatteryConsumers(); if (uidConsumers != null) { final long start = stats.getStatsStartTimestamp(); diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 5fa0ffaa9606..6005b64ca1bc 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -1442,7 +1442,6 @@ public final class CachedAppOptimizer { uidRec.setFrozen(false); postUidFrozenMessage(uidRec.getUid(), false); } - reportProcessFreezableChangedLocked(app); opt.setFreezerOverride(false); if (pid == 0 || !opt.isFrozen()) { @@ -1481,6 +1480,7 @@ public final class CachedAppOptimizer { if (processKilled) { return; } + reportProcessFreezableChangedLocked(app); long freezeTime = opt.getFreezeUnfreezeTime(); diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index e84fed7e0a4d..4b622f589adb 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -173,6 +173,16 @@ final class CoreSettingsObserver extends ContentObserver { TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU, TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class, TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT)); + + // Register all text aconfig flags. + for (String flag : TextFlags.TEXT_ACONFIGS_FLAGS) { + sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>( + TextFlags.NAMESPACE, + flag, + TextFlags.getKeyForFlag(flag), + boolean.class, + false)); // All aconfig flags are false by default. + } // add other device configs here... } private static volatile boolean sDeviceConfigContextEntriesLoaded = false; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 3d11c6843338..0615ecf029fa 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -3087,6 +3087,8 @@ public final class ProcessList { if (old == proc && proc.isPersistent()) { // We are re-adding a persistent process. Whatevs! Just leave it there. Slog.w(TAG, "Re-adding persistent process " + proc); + // Ensure that the mCrashing flag is cleared, since this is a restart + proc.resetCrashingOnRestart(); } else if (old != null) { if (old.isKilled()) { // The old process has been killed, we probably haven't had diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index cfbb5a5bc195..d8a269598bdc 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -618,6 +618,10 @@ class ProcessRecord implements WindowProcessListener { mPkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.longVersionCode)); } + void resetCrashingOnRestart() { + mErrorState.setCrashing(false); + } + @GuardedBy(anyOf = {"mService", "mProcLock"}) UidRecord getUidRecord() { return mUidRecord; diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig new file mode 100644 index 000000000000..b03cc6295b8d --- /dev/null +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -0,0 +1,9 @@ +package: "com.android.server.am" + +flag { + name: "oomadjuster_correctness_rewrite" + namespace: "android_platform_power_optimization" + description: "Utilize new OomAdjuster implementation" + bug: "298055811" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 2c83c6f419c8..6cca130af35d 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -150,16 +150,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { public boolean isChangeEnabledByUid(long changeId, int uid) { super.isChangeEnabledByUid_enforcePermission(); - String[] packages = mContext.getPackageManager().getPackagesForUid(uid); - if (packages == null || packages.length == 0) { - return mCompatConfig.defaultChangeIdValue(changeId); - } - boolean enabled = true; - for (String packageName : packages) { - enabled &= isChangeEnabledByPackageName(changeId, packageName, - UserHandle.getUserId(uid)); - } - return enabled; + return isChangeEnabledByUidInternal(changeId, uid); } /** @@ -208,6 +199,25 @@ public class PlatformCompat extends IPlatformCompat.Stub { return false; } + /** + * Internal version of {@link #isChangeEnabledByUid(long, int)}. + * + * <p>Does not perform costly permission check. + */ + public boolean isChangeEnabledByUidInternal(long changeId, int uid) { + String[] packages = mContext.getPackageManager().getPackagesForUid(uid); + if (packages == null || packages.length == 0) { + return mCompatConfig.defaultChangeIdValue(changeId); + } + boolean enabled = true; + final int userId = UserHandle.getUserId(uid); + for (String packageName : packages) { + final var appInfo = getApplicationInfo(packageName, userId); + enabled &= isChangeEnabledInternal(changeId, appInfo); + } + return enabled; + } + @Override @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) public void setOverrides(CompatibilityChangeConfig overrides, String packageName) { diff --git a/services/core/java/com/android/server/connectivity/OWNERS b/services/core/java/com/android/server/connectivity/OWNERS index 62c5737a2e8e..c24680e9b06a 100644 --- a/services/core/java/com/android/server/connectivity/OWNERS +++ b/services/core/java/com/android/server/connectivity/OWNERS @@ -1,2 +1,2 @@ set noparent -file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking +file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java index 13ee47eb91c8..40b2f5ab852f 100644 --- a/services/core/java/com/android/server/display/BrightnessRangeController.java +++ b/services/core/java/com/android/server/display/BrightnessRangeController.java @@ -43,14 +43,16 @@ class BrightnessRangeController { BrightnessRangeController(HighBrightnessModeController hbmController, Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) { this(hbmController, modeChangeCallback, displayDeviceConfig, - new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags); + new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags, + displayToken, info); } BrightnessRangeController(HighBrightnessModeController hbmController, Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, - HdrClamper hdrClamper, DisplayManagerFlags flags) { + HdrClamper hdrClamper, DisplayManagerFlags flags, IBinder displayToken, + DisplayDeviceInfo info) { mHbmController = hbmController; mModeChangeCallback = modeChangeCallback; mHdrClamper = hdrClamper; @@ -60,10 +62,7 @@ class BrightnessRangeController { mNormalBrightnessModeController.resetNbmData( displayDeviceConfig.getLuxThrottlingData()); } - if (mUseHdrClamper) { - mHdrClamper.resetHdrConfig(displayDeviceConfig.getHdrBrightnessData()); - } - + updateHdrClamper(info, displayToken, displayDeviceConfig); } void dump(PrintWriter pw) { @@ -101,13 +100,12 @@ class BrightnessRangeController { displayDeviceConfig::getHdrBrightnessFromSdr); } ); - if (mUseHdrClamper) { - mHdrClamper.resetHdrConfig(displayDeviceConfig.getHdrBrightnessData()); - } + updateHdrClamper(info, token, displayDeviceConfig); } void stop() { mHbmController.stop(); + mHdrClamper.stop(); } void setAutoBrightnessEnabled(int state) { @@ -151,6 +149,18 @@ class BrightnessRangeController { return mHbmController.getTransitionPoint(); } + private void updateHdrClamper(DisplayDeviceInfo info, IBinder token, + DisplayDeviceConfig displayDeviceConfig) { + if (mUseHdrClamper) { + DisplayDeviceConfig.HighBrightnessModeData hbmData = + displayDeviceConfig.getHighBrightnessModeData(); + float minimumHdrPercentOfScreen = + hbmData == null ? -1f : hbmData.minimumHdrPercentOfScreen; + mHdrClamper.resetHdrConfig(displayDeviceConfig.getHdrBrightnessData(), info.width, + info.height, minimumHdrPercentOfScreen, token); + } + } + private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) { if (mUseNbmController) { boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean(); diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 8642fb888556..098cb87940b2 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; +import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayViewport; import android.os.IBinder; import android.util.Slog; @@ -205,6 +206,24 @@ abstract class DisplayDevice { */ public Runnable requestDisplayStateLocked(int state, float brightnessState, float sdrBrightnessState) { + return requestDisplayStateLocked(state, brightnessState, sdrBrightnessState, null); + } + + /** + * Sets the display state, if supported. + * + * @param state The new display state. + * @param brightnessState The new display brightnessState. + * @param sdrBrightnessState The new display brightnessState for SDR layers. + * @param displayOffloadSession {@link DisplayOffloadSession} associated with current device. + * @return A runnable containing work to be deferred until after we have exited the critical + * section, or null if none. + */ + public Runnable requestDisplayStateLocked( + int state, + float brightnessState, + float sdrBrightnessState, + @Nullable DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) { return null; } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 507ae2676b16..9e92c8d7342d 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -500,6 +500,8 @@ public class DisplayDeviceConfig { public static final String DEFAULT_ID = "default"; + public static final int DEFAULT_LOW_REFRESH_RATE = 60; + private static final float BRIGHTNESS_DEFAULT = 0.5f; private static final String ETC_DIR = "etc"; private static final String DISPLAY_CONFIG_DIR = "displayconfig"; @@ -513,7 +515,6 @@ public class DisplayDeviceConfig { private static final int DEFAULT_PEAK_REFRESH_RATE = 0; private static final int DEFAULT_REFRESH_RATE = 60; private static final int DEFAULT_REFRESH_RATE_IN_HBM = 0; - private static final int DEFAULT_LOW_REFRESH_RATE = 60; private static final int DEFAULT_HIGH_REFRESH_RATE = 0; private static final float[] DEFAULT_BRIGHTNESS_THRESHOLDS = new float[]{}; diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 213ee646fc34..3529b048bd34 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -16,6 +16,8 @@ package com.android.server.display; +import static android.view.Display.Mode.INVALID_MODE_ID; + import android.hardware.display.DeviceProductInfo; import android.hardware.display.DisplayViewport; import android.util.DisplayMetrics; @@ -275,6 +277,11 @@ final class DisplayDeviceInfo { public int defaultModeId; /** + * The mode of the display which is preferred by user. + */ + public int userPreferredModeId = INVALID_MODE_ID; + + /** * The supported modes of the display. */ public Display.Mode[] supportedModes = Display.Mode.EMPTY_ARRAY; @@ -472,6 +479,7 @@ final class DisplayDeviceInfo { || modeId != other.modeId || renderFrameRate != other.renderFrameRate || defaultModeId != other.defaultModeId + || userPreferredModeId != other.userPreferredModeId || !Arrays.equals(supportedModes, other.supportedModes) || !Arrays.equals(supportedColorModes, other.supportedColorModes) || !Objects.equals(hdrCapabilities, other.hdrCapabilities) @@ -517,6 +525,7 @@ final class DisplayDeviceInfo { modeId = other.modeId; renderFrameRate = other.renderFrameRate; defaultModeId = other.defaultModeId; + userPreferredModeId = other.userPreferredModeId; supportedModes = other.supportedModes; colorMode = other.colorMode; supportedColorModes = other.supportedColorModes; @@ -559,6 +568,7 @@ final class DisplayDeviceInfo { sb.append(", modeId ").append(modeId); sb.append(", renderFrameRate ").append(renderFrameRate); sb.append(", defaultModeId ").append(defaultModeId); + sb.append(", userPreferredModeId ").append(userPreferredModeId); sb.append(", supportedModes ").append(Arrays.toString(supportedModes)); sb.append(", colorMode ").append(colorMode); sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes)); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 46ef6c3bd3e4..e942c1711088 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -557,7 +557,7 @@ public final class DisplayManagerService extends SystemService { mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, new FoldSettingProvider(mContext, new SettingsWrapper()), mDisplayDeviceRepo, new LogicalDisplayListener(), mSyncRoot, mHandler, mFlags); - mDisplayModeDirector = new DisplayModeDirector(context, mHandler); + mDisplayModeDirector = new DisplayModeDirector(context, mHandler, mFlags); mBrightnessSynchronizer = new BrightnessSynchronizer(mContext); Resources resources = mContext.getResources(); mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger( @@ -1772,7 +1772,7 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { // main display adapter registerDisplayAdapterLocked(mInjector.getLocalDisplayAdapter(mSyncRoot, mContext, - mHandler, mDisplayDeviceRepo)); + mHandler, mDisplayDeviceRepo, mFlags)); // Standalone VR devices rely on a virtual display as their primary display for // 2D UI. We register virtual display adapter along side the main display adapter @@ -2028,9 +2028,6 @@ public final class DisplayManagerService extends SystemService { mDisplayBrightnesses.delete(displayId); DisplayManagerGlobal.invalidateLocalDisplayInfoCaches(); - sendDisplayEventLocked(display, event); - scheduleTraversalLocked(false); - if (mDisplayWindowPolicyControllers.contains(displayId)) { final IVirtualDevice virtualDevice = mDisplayWindowPolicyControllers.removeReturnOld(displayId).first; @@ -2041,6 +2038,9 @@ public final class DisplayManagerService extends SystemService { }); } } + + sendDisplayEventLocked(display, event); + scheduleTraversalLocked(false); } private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) { @@ -2093,8 +2093,11 @@ public final class DisplayManagerService extends SystemService { // Only send a request for display state if display state has already been initialized. if (state != Display.STATE_UNKNOWN) { final BrightnessPair brightnessPair = mDisplayBrightnesses.get(displayId); - return device.requestDisplayStateLocked(state, brightnessPair.brightness, - brightnessPair.sdrBrightness); + return device.requestDisplayStateLocked( + state, + brightnessPair.brightness, + brightnessPair.sdrBrightness, + display.getDisplayOffloadSessionLocked()); } } return null; @@ -3183,9 +3186,10 @@ public final class DisplayManagerService extends SystemService { } LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, - Handler handler, - DisplayAdapter.Listener displayAdapterListener) { - return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener); + Handler handler, DisplayAdapter.Listener displayAdapterListener, + DisplayManagerFlags flags) { + return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener, + flags); } long getDefaultDisplayDelayTimeout() { @@ -4806,6 +4810,49 @@ public final class DisplayManagerService extends SystemService { } return displayGroupIds; } + + @Override + public DisplayManagerInternal.DisplayOffloadSession registerDisplayOffloader( + int displayId, @NonNull DisplayManagerInternal.DisplayOffloader displayOffloader) { + if (!mFlags.isDisplayOffloadEnabled()) { + return null; + } + synchronized (mSyncRoot) { + LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId); + if (logicalDisplay == null) { + Slog.w(TAG, "registering DisplayOffloader: LogicalDisplay for displayId=" + + displayId + " is not found. No Op."); + return null; + } + + DisplayPowerControllerInterface displayPowerController = + mDisplayPowerControllers.get(logicalDisplay.getDisplayIdLocked()); + if (displayPowerController == null) { + Slog.w(TAG, + "setting doze state override: DisplayPowerController for displayId=" + + displayId + " is unavailable. No Op."); + return null; + } + + DisplayOffloadSession session = + new DisplayOffloadSession() { + @Override + public void setDozeStateOverride(int displayState) { + synchronized (mSyncRoot) { + displayPowerController.overrideDozeScreenState(displayState); + } + } + + @Override + public DisplayOffloader getDisplayOffloader() { + return displayOffloader; + } + }; + logicalDisplay.setDisplayOffloadSessionLocked(session); + displayPowerController.setDisplayOffloadSession(session); + return session; + } + } } class DesiredDisplayModeSpecsObserver diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 83f4df97c5dc..ce98559abe30 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -34,6 +34,8 @@ import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.metrics.LogMaker; @@ -588,8 +590,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call new SparseArray<>(); private boolean mBootCompleted; - private final DisplayManagerFlags mFlags; + private int mDozeStateOverride = Display.STATE_UNKNOWN; + private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; /** * Creates the display power controller. @@ -684,7 +687,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback); mBrightnessRangeController = new BrightnessRangeController(hbmController, - modeChangeCallback, mDisplayDeviceConfig, mHandler, flags); + modeChangeCallback, mDisplayDeviceConfig, mHandler, flags, + mDisplayDevice.getDisplayTokenLocked(), + mDisplayDevice.getDisplayDeviceInfoLocked()); mBrightnessThrottler = createBrightnessThrottlerLocked(); @@ -957,6 +962,23 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } @Override + public void overrideDozeScreenState(int displayState) { + synchronized (mLock) { + if (mDisplayOffloadSession == null || + !DisplayOffloadSession.isSupportedOffloadState(displayState)) { + return; + } + mDozeStateOverride = displayState; + sendUpdatePowerState(); + } + } + + @Override + public void setDisplayOffloadSession(DisplayOffloadSession session) { + mDisplayOffloadSession = session; + } + + @Override public BrightnessConfiguration getDefaultBrightnessConfiguration() { if (mAutomaticBrightnessController == null) { return null; @@ -1518,6 +1540,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } else { state = Display.STATE_DOZE; } + state = mDozeStateOverride == Display.STATE_UNKNOWN ? state : mDozeStateOverride; if (!mAllowAutoBrightnessWhileDozingConfig) { brightnessState = mPowerRequest.dozeScreenBrightness; mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE); @@ -1937,6 +1960,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // We want to scale HDR brightness level with the SDR level, we also need to restore // SDR brightness immediately when entering dim or low power mode. animateValue = mBrightnessRangeController.getHdrBrightnessValue(); + mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR); } final float currentBrightness = mPowerState.getScreenBrightness(); @@ -3001,6 +3025,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mLeadDisplayId=" + mLeadDisplayId); pw.println(" mLightSensor=" + mLightSensor); pw.println(" mDisplayBrightnessFollowers=" + mDisplayBrightnessFollowers); + pw.println(" mDozeStateOverride=" + mDozeStateOverride); pw.println(); pw.println("Display Power Controller Locked State:"); diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index b0d293a1709f..1652871963b9 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -31,6 +31,8 @@ import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.metrics.LogMaker; @@ -470,9 +472,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal new SparseArray(); private boolean mBootCompleted; - private final DisplayManagerFlags mFlags; + private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; + /** * Creates the display power controller. */ @@ -549,7 +552,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mBrightnessThrottler = createBrightnessThrottlerLocked(); mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController, - modeChangeCallback, mDisplayDeviceConfig, mHandler, flags); + modeChangeCallback, mDisplayDeviceConfig, mHandler, flags, + mDisplayDevice.getDisplayTokenLocked(), + mDisplayDevice.getDisplayDeviceInfoLocked()); mDisplayBrightnessController = new DisplayBrightnessController(context, null, @@ -588,21 +593,24 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal if (mDisplayId == Display.DEFAULT_DISPLAY) { mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class); - boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() { - @Override - public void onReduceBrightColorsActivationChanged(boolean activated, - boolean userInitiated) { - applyReduceBrightColorsSplineAdjustment(); - - } - - @Override - public void onReduceBrightColorsStrengthChanged(int strength) { + if (mCdsi != null) { + boolean active = mCdsi.setReduceBrightColorsListener( + new ReduceBrightColorsListener() { + @Override + public void onReduceBrightColorsActivationChanged(boolean activated, + boolean userInitiated) { + applyReduceBrightColorsSplineAdjustment(); + + } + + @Override + public void onReduceBrightColorsStrengthChanged(int strength) { + applyReduceBrightColorsSplineAdjustment(); + } + }); + if (active) { applyReduceBrightColorsSplineAdjustment(); } - }); - if (active) { - applyReduceBrightColorsSplineAdjustment(); } } else { mCdsi = null; @@ -760,6 +768,24 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } @Override + public void overrideDozeScreenState(int displayState) { + mHandler.postAtTime(() -> { + if (mDisplayOffloadSession == null + || !(DisplayOffloadSession.isSupportedOffloadState(displayState) + || displayState == Display.STATE_UNKNOWN)) { + return; + } + mDisplayStateController.overrideDozeScreenState(displayState); + sendUpdatePowerState(); + }, mClock.uptimeMillis()); + } + + @Override + public void setDisplayOffloadSession(DisplayOffloadSession session) { + mDisplayOffloadSession = session; + } + + @Override public BrightnessConfiguration getDefaultBrightnessConfiguration() { if (mAutomaticBrightnessController == null) { return null; @@ -1540,6 +1566,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // SDR brightness immediately when entering dim or low power mode. animateValue = mBrightnessRangeController.getHdrBrightnessValue(); customTransitionRate = mBrightnessRangeController.getHdrTransitionRate(); + mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR); } final float currentBrightness = mPowerState.getScreenBrightness(); @@ -1871,15 +1898,12 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private HighBrightnessModeController createHbmControllerLocked( HighBrightnessModeMetadata hbmMetadata, Runnable modeChangeCallback) { - final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); - final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); - final IBinder displayToken = - mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked(); - final String displayUniqueId = - mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); + final DisplayDeviceConfig ddConfig = mDisplayDevice.getDisplayDeviceConfig(); + final IBinder displayToken = mDisplayDevice.getDisplayTokenLocked(); + final String displayUniqueId = mDisplayDevice.getUniqueId(); final DisplayDeviceConfig.HighBrightnessModeData hbmData = ddConfig != null ? ddConfig.getHighBrightnessModeData() : null; - final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + final DisplayDeviceInfo info = mDisplayDevice.getDisplayDeviceInfoLocked(); return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height, displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData, (sdrBrightness, maxDesiredHdrSdrRatio) -> @@ -3025,9 +3049,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal BrightnessRangeController getBrightnessRangeController( HighBrightnessModeController hbmController, Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) { return new BrightnessRangeController(hbmController, - modeChangeCallback, displayDeviceConfig, handler, flags); + modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info); } DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler, diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java index e3108c955a95..181386a93c71 100644 --- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java +++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java @@ -139,6 +139,10 @@ public interface DisplayPowerControllerInterface { boolean requestPowerState(DisplayManagerInternal.DisplayPowerRequest request, boolean waitForNegativeProximity); + void overrideDozeScreenState(int displayState); + + void setDisplayOffloadSession(DisplayManagerInternal.DisplayOffloadSession session); + /** * Sets up the temporary autobrightness adjustment when the user is yet to settle down to a * value. diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 924b1b3c66ab..0a1f316ac059 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -22,6 +22,9 @@ import static android.view.Display.Mode.INVALID_MODE_ID; import android.app.ActivityThread; import android.content.Context; import android.content.res.Resources; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.DisplayOffloader; +import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; import android.hardware.sidekick.SidekickInternal; import android.os.Build; import android.os.Handler; @@ -47,6 +50,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; @@ -80,21 +84,24 @@ final class LocalDisplayAdapter extends DisplayAdapter { private final boolean mIsBootDisplayModeSupported; + private final DisplayManagerFlags mFlags; + private Context mOverlayContext; // Called with SyncRoot lock held. LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, - Handler handler, Listener listener) { - this(syncRoot, context, handler, listener, new Injector()); + Handler handler, Listener listener, DisplayManagerFlags flags) { + this(syncRoot, context, handler, listener, flags, new Injector()); } @VisibleForTesting LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, - Listener listener, Injector injector) { + Listener listener, DisplayManagerFlags flags, Injector injector) { super(syncRoot, context, handler, listener, TAG); mInjector = injector; mSurfaceControlProxy = mInjector.getSurfaceControlProxy(); mIsBootDisplayModeSupported = mSurfaceControlProxy.getBootDisplayModeSupport(); + mFlags = flags; } @Override @@ -224,6 +231,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private boolean mAllmRequested; private boolean mGameContentTypeRequested; private boolean mSidekickActive; + private boolean mDisplayOffloadActive; private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo; // The supported display modes according to SurfaceFlinger private SurfaceControl.DisplayMode[] mSfDisplayModes; @@ -640,6 +648,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mInfo.modeId = mActiveModeId; mInfo.renderFrameRate = mActiveRenderFrameRate; mInfo.defaultModeId = getPreferredModeId(); + mInfo.userPreferredModeId = mUserPreferredModeId; mInfo.supportedModes = getDisplayModes(mSupportedModes); mInfo.colorMode = mActiveColorMode; mInfo.allmSupported = mAllmSupported; @@ -745,8 +754,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { } @Override - public Runnable requestDisplayStateLocked(final int state, final float brightnessState, - final float sdrBrightnessState) { + public Runnable requestDisplayStateLocked( + final int state, + final float brightnessState, + final float sdrBrightnessState, + DisplayOffloadSession displayOffloadSession) { + // Assume that the brightness is off if the display is being turned off. assert state != Display.STATE_OFF || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT; @@ -812,18 +825,39 @@ final class LocalDisplayAdapter extends DisplayAdapter { + ", state=" + Display.stateToString(state) + ")"); } - // We must tell sidekick to stop controlling the display before we - // can change its power mode, so do that first. - if (mSidekickActive) { - Trace.traceBegin(Trace.TRACE_TAG_POWER, - "SidekickInternal#endDisplayControl"); - try { - mSidekickInternal.endDisplayControl(); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_POWER); + DisplayOffloader displayOffloader = + displayOffloadSession == null + ? null + : displayOffloadSession.getDisplayOffloader(); + + boolean isDisplayOffloadEnabled = mFlags.isDisplayOffloadEnabled(); + + // We must tell sidekick/displayoffload to stop controlling the display + // before we can change its power mode, so do that first. + if (isDisplayOffloadEnabled) { + if (mDisplayOffloadActive && displayOffloader != null) { + Trace.traceBegin(Trace.TRACE_TAG_POWER, + "DisplayOffloader#stopOffload"); + try { + displayOffloader.stopOffload(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } + mDisplayOffloadActive = false; + } + } else { + if (mSidekickActive) { + Trace.traceBegin(Trace.TRACE_TAG_POWER, + "SidekickInternal#endDisplayControl"); + try { + mSidekickInternal.endDisplayControl(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } + mSidekickActive = false; } - mSidekickActive = false; } + final int mode = getPowerModeForState(state); Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayState(" + "id=" + physicalDisplayId @@ -835,16 +869,32 @@ final class LocalDisplayAdapter extends DisplayAdapter { Trace.traceEnd(Trace.TRACE_TAG_POWER); } setCommittedState(state); + // If we're entering a suspended (but not OFF) power state and we - // have a sidekick available, tell it now that it can take control. - if (Display.isSuspendedState(state) && state != Display.STATE_OFF - && mSidekickInternal != null && !mSidekickActive) { - Trace.traceBegin(Trace.TRACE_TAG_POWER, - "SidekickInternal#startDisplayControl"); - try { - mSidekickActive = mSidekickInternal.startDisplayControl(state); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_POWER); + // have a sidekick/displayoffload available, tell it now that it can take + // control. + if (isDisplayOffloadEnabled) { + if (DisplayOffloadSession.isSupportedOffloadState(state) && + displayOffloader != null + && !mDisplayOffloadActive) { + Trace.traceBegin( + Trace.TRACE_TAG_POWER, "DisplayOffloader#startOffload"); + try { + mDisplayOffloadActive = displayOffloader.startOffload(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } + } + } else { + if (Display.isSuspendedState(state) && state != Display.STATE_OFF + && mSidekickInternal != null && !mSidekickActive) { + Trace.traceBegin(Trace.TRACE_TAG_POWER, + "SidekickInternal#startDisplayControl"); + try { + mSidekickActive = mSidekickInternal.startDisplayControl(state); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_POWER); + } } } } @@ -857,6 +907,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } + private void setDisplayBrightness(float brightnessState, float sdrBrightnessState) { // brightnessState includes invalid, off and full range. @@ -1344,6 +1395,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { public interface DisplayEventListener { void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected); + void onHotplugConnectionError(long timestampNanos, int connectionError); void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId, long renderPeriod); void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId, @@ -1366,6 +1418,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { } @Override + public void onHotplugConnectionError(long timestampNanos, int errorCode) { + mListener.onHotplugConnectionError(timestampNanos, errorCode); + } + + @Override public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId, long renderPeriod) { mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod); @@ -1391,6 +1448,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { } @Override + public void onHotplugConnectionError(long timestampNanos, int connectionError) { + if (DEBUG) { + Slog.d(TAG, "onHotplugConnectionError(" + + "timestampNanos=" + timestampNanos + + ", connectionError=" + connectionError + ")"); + } + } + + @Override public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId, long renderPeriod) { if (DEBUG) { diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 0405ebe8b5d2..bd82b81513df 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -137,6 +137,9 @@ final class LogicalDisplay { private final Rect mTempLayerStackRect = new Rect(); private final Rect mTempDisplayRect = new Rect(); + /** A session token that controls the offloading operations of this logical display. */ + private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; + /** * Name of a display group to which the display is assigned. */ @@ -470,6 +473,7 @@ final class LogicalDisplay { mBaseDisplayInfo.modeId = deviceInfo.modeId; mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate; mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId; + mBaseDisplayInfo.userPreferredModeId = deviceInfo.userPreferredModeId; mBaseDisplayInfo.supportedModes = Arrays.copyOf( deviceInfo.supportedModes, deviceInfo.supportedModes.length); mBaseDisplayInfo.colorMode = deviceInfo.colorMode; @@ -940,6 +944,15 @@ final class LogicalDisplay { return mDisplayGroupName; } + public void setDisplayOffloadSessionLocked( + DisplayManagerInternal.DisplayOffloadSession session) { + mDisplayOffloadSession = session; + } + + public DisplayManagerInternal.DisplayOffloadSession getDisplayOffloadSessionLocked() { + return mDisplayOffloadSession; + } + public void dumpLocked(PrintWriter pw) { pw.println("mDisplayId=" + mDisplayId); pw.println("mIsEnabled=" + mIsEnabled); diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING index 6f243e18f6db..049b2fd032db 100644 --- a/services/core/java/com/android/server/display/TEST_MAPPING +++ b/services/core/java/com/android/server/display/TEST_MAPPING @@ -4,7 +4,6 @@ "name": "DisplayServiceTests", "options": [ {"include-filter": "com.android.server.display"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java index a514136e62a2..652e6cfd67be 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java @@ -17,9 +17,14 @@ package com.android.server.display.brightness.clamper; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.os.Handler; +import android.os.IBinder; import android.os.PowerManager; +import android.view.SurfaceControlHdrLayerInfoListener; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.BrightnessUtils; import com.android.server.display.config.HdrBrightnessData; import java.io.PrintWriter; @@ -33,11 +38,18 @@ public class HdrClamper { private final Runnable mDebouncer; + private final HdrLayerInfoListener mHdrListener; + @Nullable private HdrBrightnessData mHdrBrightnessData = null; + @Nullable + private IBinder mRegisteredDisplayToken = null; + private float mAmbientLux = Float.MAX_VALUE; + private boolean mHdrVisible = false; + private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX; private float mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX; @@ -47,6 +59,12 @@ public class HdrClamper { public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener, Handler handler) { + this(clamperChangeListener, handler, new Injector()); + } + + @VisibleForTesting + public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener, + Handler handler, Injector injector) { mClamperChangeListener = clamperChangeListener; mHandler = handler; mDebouncer = () -> { @@ -54,6 +72,10 @@ public class HdrClamper { mMaxBrightness = mDesiredMaxBrightness; mClamperChangeListener.onChanged(); }; + mHdrListener = injector.getHdrListener((visible) -> { + mHdrVisible = visible; + recalculateBrightnessCap(mHdrBrightnessData, mAmbientLux, mHdrVisible); + }, handler); } // Called in same looper: mHandler.getLooper() @@ -72,16 +94,37 @@ public class HdrClamper { */ public void onAmbientLuxChange(float ambientLux) { mAmbientLux = ambientLux; - recalculateBrightnessCap(mHdrBrightnessData, ambientLux); + recalculateBrightnessCap(mHdrBrightnessData, ambientLux, mHdrVisible); } /** * Updates brightness cap config. * Called in same looper: mHandler.getLooper() */ - public void resetHdrConfig(HdrBrightnessData data) { + @SuppressLint("AndroidFrameworkRequiresPermission") + public void resetHdrConfig(HdrBrightnessData data, int width, int height, + float minimumHdrPercentOfScreen, IBinder displayToken) { mHdrBrightnessData = data; - recalculateBrightnessCap(data, mAmbientLux); + mHdrListener.mHdrMinPixels = (float) (width * height) * minimumHdrPercentOfScreen; + if (displayToken != mRegisteredDisplayToken) { // token changed, resubscribe + if (mRegisteredDisplayToken != null) { // previous token not null, unsubscribe + mHdrListener.unregister(mRegisteredDisplayToken); + mHdrVisible = false; + } + if (displayToken != null) { // new token not null, subscribe + mHdrListener.register(displayToken); + } + mRegisteredDisplayToken = displayToken; + } + recalculateBrightnessCap(data, mAmbientLux, mHdrVisible); + } + + /** Clean up all resources */ + @SuppressLint("AndroidFrameworkRequiresPermission") + public void stop() { + if (mRegisteredDisplayToken != null) { + mHdrListener.unregister(mRegisteredDisplayToken); + } } /** @@ -98,13 +141,28 @@ public class HdrClamper { pw.println(" mAmbientLux=" + mAmbientLux); } - private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux) { - if (data == null) { - mHandler.removeCallbacks(mDebouncer); + private void reset() { + if (mMaxBrightness == PowerManager.BRIGHTNESS_MAX + && mDesiredMaxBrightness == PowerManager.BRIGHTNESS_MAX && mTransitionRate == -1f + && mDesiredTransitionRate == -1f) { // already done reset, do nothing return; } - float expectedMaxBrightness = findBrightnessLimit(data, ambientLux); + mHandler.removeCallbacks(mDebouncer); + mMaxBrightness = PowerManager.BRIGHTNESS_MAX; + mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX; + mDesiredTransitionRate = -1f; + mTransitionRate = 1f; + mClamperChangeListener.onChanged(); + } + private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux, + boolean hdrVisible) { + if (data == null || !hdrVisible) { + reset(); + return; + } + + float expectedMaxBrightness = findBrightnessLimit(data, ambientLux); if (mMaxBrightness == expectedMaxBrightness) { mDesiredMaxBrightness = mMaxBrightness; mDesiredTransitionRate = -1f; @@ -121,12 +179,18 @@ public class HdrClamper { debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis; transitionDuration = mHdrBrightnessData.mBrightnessDecreaseDurationMillis; } + + float maxHlg = BrightnessUtils.convertLinearToGamma(mMaxBrightness); + float desiredMaxHlg = BrightnessUtils.convertLinearToGamma(mDesiredMaxBrightness); + mDesiredTransitionRate = Math.abs( - (mMaxBrightness - mDesiredMaxBrightness) * 1000f / transitionDuration); + (maxHlg - desiredMaxHlg) * 1000f / transitionDuration); mHandler.removeCallbacks(mDebouncer); mHandler.postDelayed(mDebouncer, debounceTime); } + // do nothing if expectedMaxBrightness == mDesiredMaxBrightness + // && expectedMaxBrightness != mMaxBrightness } private float findBrightnessLimit(HdrBrightnessData data, float ambientLux) { @@ -143,4 +207,36 @@ public class HdrClamper { } return foundMaxBrightness; } + + @FunctionalInterface + interface HdrListener { + void onHdrVisible(boolean visible); + } + + static class HdrLayerInfoListener extends SurfaceControlHdrLayerInfoListener { + private final HdrListener mHdrListener; + + private final Handler mHandler; + + private float mHdrMinPixels = Float.MAX_VALUE; + + HdrLayerInfoListener(HdrListener hdrListener, Handler handler) { + mHdrListener = hdrListener; + mHandler = handler; + } + + @Override + public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW, + int maxH, int flags, float maxDesiredHdrSdrRatio) { + mHandler.post(() -> + mHdrListener.onHdrVisible( + numberOfHdrLayers > 0 && (float) (maxW * maxH) >= mHdrMinPixels)); + } + } + + static class Injector { + HdrLayerInfoListener getHdrListener(HdrListener hdrListener, Handler handler) { + return new HdrLayerInfoListener(hdrListener, handler); + } + } } diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 3f6bf1adfe48..b6273e1daf82 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -47,6 +47,26 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1, Flags::enableAdaptiveToneImprovements1); + private final FlagState mDisplayOffloadFlagState = new FlagState( + Flags.FLAG_ENABLE_DISPLAY_OFFLOAD, + Flags::enableDisplayOffload); + + private final FlagState mDisplayResolutionRangeVotingState = new FlagState( + Flags.FLAG_ENABLE_DISPLAY_RESOLUTION_RANGE_VOTING, + Flags::enableDisplayResolutionRangeVoting); + + private final FlagState mUserPreferredModeVoteState = new FlagState( + Flags.FLAG_ENABLE_USER_PREFERRED_MODE_VOTE, + Flags::enableUserPreferredModeVote); + + private final FlagState mExternalDisplayLimitModeState = new FlagState( + Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY, + Flags::enableModeLimitForExternalDisplay); + + private final FlagState mDisplaysRefreshRatesSynchronizationState = new FlagState( + Flags.FLAG_ENABLE_DISPLAYS_REFRESH_RATES_SYNCHRONIZATION, + Flags::enableDisplaysRefreshRatesSynchronization); + /** Returns whether connected display management is enabled or not. */ public boolean isConnectedDisplayManagementEnabled() { return mConnectedDisplayManagementFlagState.isEnabled(); @@ -68,6 +88,38 @@ public class DisplayManagerFlags { return mAdaptiveToneImprovements1.isEnabled(); } + /** Returns whether resolution range voting feature is enabled or not. */ + public boolean isDisplayResolutionRangeVotingEnabled() { + return mDisplayResolutionRangeVotingState.isEnabled(); + } + + /** + * @return Whether user preferred mode is added as a vote in + * {@link com.android.server.display.mode.DisplayModeDirector} + */ + public boolean isUserPreferredModeVoteEnabled() { + return mUserPreferredModeVoteState.isEnabled(); + } + + /** + * @return Whether external display mode limitation is enabled. + */ + public boolean isExternalDisplayLimitModeEnabled() { + return mExternalDisplayLimitModeState.isEnabled(); + } + + /** + * @return Whether displays refresh rate synchronization is enabled. + */ + public boolean isDisplaysRefreshRatesSynchronizationEnabled() { + return mDisplaysRefreshRatesSynchronizationState.isEnabled(); + } + + /** Returns whether displayoffload is enabled on not */ + public boolean isDisplayOffloadEnabled() { + return mDisplayOffloadFlagState.isEnabled(); + } + private static class FlagState { private final String mName; diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 4d8600448c33..542f26cbec69 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -5,7 +5,7 @@ package: "com.android.server.display.feature.flags" flag { name: "enable_connected_display_management" namespace: "display_manager" - description: "Feature flag for Connected Display managment" + description: "Feature flag for Connected Display management" bug: "280739508" is_fixed_read_only: true } @@ -34,3 +34,41 @@ flag { is_fixed_read_only: true } +flag { + name: "enable_display_resolution_range_voting" + namespace: "display_manager" + description: "Feature flag to enable voting for ranges of resolutions" + bug: "299297058" + is_fixed_read_only: true +} + +flag { + name: "enable_user_preferred_mode_vote" + namespace: "display_manager" + description: "Feature flag to use voting for UserPreferredMode for display" + bug: "297018612" + is_fixed_read_only: true +} + +flag { + name: "enable_mode_limit_for_external_display" + namespace: "display_manager" + description: "Feature limiting external display resolution and refresh rate" + bug: "242093547" + is_fixed_read_only: true +} + +flag { + name: "enable_displays_refresh_rates_synchronization" + namespace: "display_manager" + description: "Enables synchronization of refresh rates across displays" + bug: "294015845" +} + +flag { + name: "enable_display_offload" + namespace: "display_manager" + description: "Feature flag for DisplayOffload" + bug: "299521647" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 2c2af3f7f435..71ea8cc30405 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -19,6 +19,9 @@ package com.android.server.display.mode; import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED; import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE; import static android.os.PowerManager.BRIGHTNESS_INVALID_FLOAT; +import static android.view.Display.Mode.INVALID_MODE_ID; + +import static com.android.server.display.DisplayDeviceConfig.DEFAULT_LOW_REFRESH_RATE; import android.annotation.IntegerRes; import android.annotation.NonNull; @@ -71,6 +74,7 @@ import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.utils.AmbientFilter; import com.android.server.display.utils.AmbientFilterFactory; import com.android.server.display.utils.DeviceConfigParsingUtils; @@ -84,9 +88,11 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Callable; import java.util.function.Function; import java.util.function.IntSupplier; @@ -96,6 +102,8 @@ import java.util.function.IntSupplier; * picked by the system based on system-wide and display-specific configuration. */ public class DisplayModeDirector { + public static final float SYNCHRONIZED_REFRESH_RATE_TARGET = DEFAULT_LOW_REFRESH_RATE; + public static final float SYNCHRONIZED_REFRESH_RATE_TOLERANCE = 1; private static final String TAG = "DisplayModeDirector"; private boolean mLoggingEnabled; @@ -151,12 +159,38 @@ public class DisplayModeDirector { @DisplayManager.SwitchingType private int mModeSwitchingType = DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS; - public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) { - this(context, handler, new RealInjector(context)); + /** + * Whether resolution range voting feature is enabled. + */ + private final boolean mIsDisplayResolutionRangeVotingEnabled; + + /** + * Whether user preferred mode voting feature is enabled. + */ + private final boolean mIsUserPreferredModeVoteEnabled; + + /** + * Whether limit display mode feature is enabled. + */ + private final boolean mIsExternalDisplayLimitModeEnabled; + + private final boolean mIsDisplaysRefreshRatesSynchronizationEnabled; + + public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler, + @NonNull DisplayManagerFlags displayManagerFlags) { + this(context, handler, new RealInjector(context), displayManagerFlags); } public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler, - @NonNull Injector injector) { + @NonNull Injector injector, + @NonNull DisplayManagerFlags displayManagerFlags) { + mIsDisplayResolutionRangeVotingEnabled = displayManagerFlags + .isDisplayResolutionRangeVotingEnabled(); + mIsUserPreferredModeVoteEnabled = displayManagerFlags.isUserPreferredModeVoteEnabled(); + mIsExternalDisplayLimitModeEnabled = displayManagerFlags + .isExternalDisplayLimitModeEnabled(); + mIsDisplaysRefreshRatesSynchronizationEnabled = displayManagerFlags + .isDisplaysRefreshRatesSynchronizationEnabled(); mContext = context; mHandler = new DisplayModeDirectorHandler(handler.getLooper()); mInjector = injector; @@ -230,6 +264,8 @@ public class DisplayModeDirector { public float maxRenderFrameRate; public int width; public int height; + public int minWidth; + public int minHeight; public boolean disableRefreshRateSwitching; public float appRequestBaseModeRefreshRate; @@ -244,6 +280,8 @@ public class DisplayModeDirector { maxRenderFrameRate = Float.POSITIVE_INFINITY; width = Vote.INVALID_SIZE; height = Vote.INVALID_SIZE; + minWidth = 0; + minHeight = 0; disableRefreshRateSwitching = false; appRequestBaseModeRefreshRate = 0f; } @@ -256,6 +294,8 @@ public class DisplayModeDirector { + ", maxRenderFrameRate=" + maxRenderFrameRate + ", width=" + width + ", height=" + height + + ", minWidth=" + minWidth + + ", minHeight=" + minHeight + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate; } @@ -277,7 +317,6 @@ public class DisplayModeDirector { continue; } - // For physical refresh rates, just use the tightest bounds of all the votes. // The refresh rate cannot be lower than the minimal render frame rate. final float minPhysicalRefreshRate = Math.max(vote.refreshRateRanges.physical.min, @@ -298,10 +337,18 @@ public class DisplayModeDirector { // For display size, disable refresh rate switching and base mode refresh rate use only // the first vote we come across (i.e. the highest priority vote that includes the // attribute). - if (summary.height == Vote.INVALID_SIZE && summary.width == Vote.INVALID_SIZE - && vote.height > 0 && vote.width > 0) { - summary.width = vote.width; - summary.height = vote.height; + if (vote.height > 0 && vote.width > 0) { + if (summary.width == Vote.INVALID_SIZE && summary.height == Vote.INVALID_SIZE) { + summary.width = vote.width; + summary.height = vote.height; + summary.minWidth = vote.minWidth; + summary.minHeight = vote.minHeight; + } else if (mIsDisplayResolutionRangeVotingEnabled) { + summary.width = Math.min(summary.width, vote.width); + summary.height = Math.min(summary.height, vote.height); + summary.minWidth = Math.max(summary.minWidth, vote.minWidth); + summary.minHeight = Math.max(summary.minHeight, vote.minHeight); + } } if (!summary.disableRefreshRateSwitching && vote.disableRefreshRateSwitching) { summary.disableRefreshRateSwitching = true; @@ -413,6 +460,8 @@ public class DisplayModeDirector { || primarySummary.width == Vote.INVALID_SIZE) { primarySummary.width = defaultMode.getPhysicalWidth(); primarySummary.height = defaultMode.getPhysicalHeight(); + } else if (mIsDisplayResolutionRangeVotingEnabled) { + updateSummaryWithBestAllowedResolution(modes, primarySummary); } availableModes = filterModes(modes, primarySummary); @@ -654,6 +703,38 @@ public class DisplayModeDirector { return availableModes; } + private void updateSummaryWithBestAllowedResolution(final Display.Mode[] supportedModes, + VoteSummary outSummary) { + final int maxAllowedWidth = outSummary.width; + final int maxAllowedHeight = outSummary.height; + if (mLoggingEnabled) { + Slog.i(TAG, "updateSummaryWithBestAllowedResolution " + outSummary); + } + outSummary.width = Vote.INVALID_SIZE; + outSummary.height = Vote.INVALID_SIZE; + + int maxNumberOfPixels = 0; + for (Display.Mode mode : supportedModes) { + if (mode.getPhysicalWidth() > maxAllowedWidth + || mode.getPhysicalHeight() > maxAllowedHeight + || mode.getPhysicalWidth() < outSummary.minWidth + || mode.getPhysicalHeight() < outSummary.minHeight) { + continue; + } + + int numberOfPixels = mode.getPhysicalHeight() * mode.getPhysicalWidth(); + if (numberOfPixels > maxNumberOfPixels || (mode.getPhysicalWidth() == maxAllowedWidth + && mode.getPhysicalHeight() == maxAllowedHeight)) { + if (mLoggingEnabled) { + Slog.i(TAG, "updateSummaryWithBestAllowedResolution updated with " + mode); + } + maxNumberOfPixels = numberOfPixels; + outSummary.width = mode.getPhysicalWidth(); + outSummary.height = mode.getPhysicalHeight(); + } + } + } + /** * Gets the observer responsible for application display mode requests. */ @@ -1393,11 +1474,38 @@ public class DisplayModeDirector { private final Context mContext; private final Handler mHandler; private final VotesStorage mVotesStorage; + private int mExternalDisplayPeakWidth; + private int mExternalDisplayPeakHeight; + private int mExternalDisplayPeakRefreshRate; + private final boolean mRefreshRateSynchronizationEnabled; + private final Set<Integer> mExternalDisplaysConnected = new HashSet<>(); DisplayObserver(Context context, Handler handler, VotesStorage votesStorage) { mContext = context; mHandler = handler; mVotesStorage = votesStorage; + mExternalDisplayPeakRefreshRate = mContext.getResources().getInteger( + R.integer.config_externalDisplayPeakRefreshRate); + mExternalDisplayPeakWidth = mContext.getResources().getInteger( + R.integer.config_externalDisplayPeakWidth); + mExternalDisplayPeakHeight = mContext.getResources().getInteger( + R.integer.config_externalDisplayPeakHeight); + mRefreshRateSynchronizationEnabled = mContext.getResources().getBoolean( + R.bool.config_refreshRateSynchronizationEnabled); + } + + private boolean isExternalDisplayLimitModeEnabled() { + return mExternalDisplayPeakWidth > 0 + && mExternalDisplayPeakHeight > 0 + && mExternalDisplayPeakRefreshRate > 0 + && mIsExternalDisplayLimitModeEnabled + && mIsDisplayResolutionRangeVotingEnabled + && mIsUserPreferredModeVoteEnabled; + } + + private boolean isRefreshRateSynchronizationEnabled() { + return mRefreshRateSynchronizationEnabled + && mIsDisplaysRefreshRatesSynchronizationEnabled; } public void observe() { @@ -1428,6 +1536,9 @@ public class DisplayModeDirector { DisplayInfo displayInfo = getDisplayInfo(displayId); updateDisplayModes(displayId, displayInfo); updateLayoutLimitedFrameRate(displayId, displayInfo); + updateUserSettingDisplayPreferredSize(displayInfo); + updateDisplaysPeakRefreshRateAndResolution(displayInfo); + addDisplaysSynchronizedPeakRefreshRate(displayInfo); } @Override @@ -1437,6 +1548,9 @@ public class DisplayModeDirector { mDefaultModeByDisplay.remove(displayId); } updateLayoutLimitedFrameRate(displayId, null); + removeUserSettingDisplayPreferredSize(displayId); + removeDisplaysPeakRefreshRateAndResolution(displayId); + removeDisplaysSynchronizedPeakRefreshRate(displayId); } @Override @@ -1444,6 +1558,7 @@ public class DisplayModeDirector { DisplayInfo displayInfo = getDisplayInfo(displayId); updateDisplayModes(displayId, displayInfo); updateLayoutLimitedFrameRate(displayId, displayInfo); + updateUserSettingDisplayPreferredSize(displayInfo); } @Nullable @@ -1460,6 +1575,111 @@ public class DisplayModeDirector { mVotesStorage.updateVote(displayId, Vote.PRIORITY_LAYOUT_LIMITED_FRAME_RATE, vote); } + private void removeUserSettingDisplayPreferredSize(int displayId) { + if (!mIsUserPreferredModeVoteEnabled) { + return; + } + mVotesStorage.updateVote(displayId, Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE, + null); + } + + private void updateUserSettingDisplayPreferredSize(@Nullable DisplayInfo info) { + if (info == null || !mIsUserPreferredModeVoteEnabled) { + return; + } + + var preferredMode = findDisplayPreferredMode(info); + if (preferredMode == null) { + removeUserSettingDisplayPreferredSize(info.displayId); + return; + } + + mVotesStorage.updateVote(info.displayId, + Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE, + Vote.forSize(/* width */ preferredMode.getPhysicalWidth(), + /* height */ preferredMode.getPhysicalHeight())); + } + + @Nullable + private Display.Mode findDisplayPreferredMode(@NonNull DisplayInfo info) { + if (info.userPreferredModeId == INVALID_MODE_ID) { + return null; + } + for (var mode : info.supportedModes) { + if (mode.getModeId() == info.userPreferredModeId) { + return mode; + } + } + return null; + } + + private void removeDisplaysPeakRefreshRateAndResolution(int displayId) { + if (!isExternalDisplayLimitModeEnabled()) { + return; + } + + mVotesStorage.updateVote(displayId, + Vote.PRIORITY_LIMIT_MODE, null); + } + + private void updateDisplaysPeakRefreshRateAndResolution(@Nullable final DisplayInfo info) { + // Only consider external display, only in case the refresh rate and resolution limits + // are non-zero. + if (info == null || info.type != Display.TYPE_EXTERNAL + || !isExternalDisplayLimitModeEnabled()) { + return; + } + + mVotesStorage.updateVote(info.displayId, + Vote.PRIORITY_LIMIT_MODE, + Vote.forSizeAndPhysicalRefreshRatesRange( + /* minWidth */ 0, /* minHeight */ 0, + mExternalDisplayPeakWidth, + mExternalDisplayPeakHeight, + /* minPhysicalRefreshRate */ 0, + mExternalDisplayPeakRefreshRate)); + } + + /** + * Sets 60Hz target refresh rate as the vote with + * {@link Vote#PRIORITY_SYNCHRONIZED_REFRESH_RATE} priority. + */ + private void addDisplaysSynchronizedPeakRefreshRate(@Nullable final DisplayInfo info) { + if (info == null || info.type != Display.TYPE_EXTERNAL + || !isRefreshRateSynchronizationEnabled()) { + return; + } + synchronized (mLock) { + mExternalDisplaysConnected.add(info.displayId); + if (mExternalDisplaysConnected.size() != 1) { + return; + } + } + // set minRefreshRate as the max refresh rate. + mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, + Vote.forPhysicalRefreshRates( + SYNCHRONIZED_REFRESH_RATE_TARGET + - SYNCHRONIZED_REFRESH_RATE_TOLERANCE, + SYNCHRONIZED_REFRESH_RATE_TARGET + + SYNCHRONIZED_REFRESH_RATE_TOLERANCE)); + } + + private void removeDisplaysSynchronizedPeakRefreshRate(final int displayId) { + if (!isRefreshRateSynchronizationEnabled()) { + return; + } + synchronized (mLock) { + if (!mExternalDisplaysConnected.contains(displayId)) { + return; + } + mExternalDisplaysConnected.remove(displayId); + if (mExternalDisplaysConnected.size() != 0) { + return; + } + } + mVotesStorage.updateGlobalVote(Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE, null); + } + private void updateDisplayModes(int displayId, @Nullable DisplayInfo info) { if (info == null) { return; diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java index a42d8f257ddf..b6a6069b5a63 100644 --- a/services/core/java/com/android/server/display/mode/Vote.java +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -18,6 +18,8 @@ package com.android.server.display.mode; import android.view.SurfaceControl; +import java.util.Objects; + final class Vote { // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest // priority vote, it's overridden by all other considerations. It acts to set a default @@ -36,12 +38,15 @@ final class Vote { // It votes [minRefreshRate, Float.POSITIVE_INFINITY] static final int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3; + // User setting preferred display resolution. + static final int PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE = 4; + // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render // frame rate in certain cases, mostly to preserve power. // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate]. - static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 4; + static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 5; // We split the app request into different priorities in case we can satisfy one desire // without the other. @@ -67,40 +72,47 @@ final class Vote { // The preferred refresh rate is set on the main surface of the app outside of // DisplayModeDirector. // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded - static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5; - static final int PRIORITY_APP_REQUEST_SIZE = 6; + static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 6; + + static final int PRIORITY_APP_REQUEST_SIZE = 7; // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the // rest of low priority voters. It votes [0, max(PEAK, MIN)] - static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7; + static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 8; + + // Restrict all displays to 60Hz when external display is connected. It votes [59Hz, 61Hz]. + static final int PRIORITY_SYNCHRONIZED_REFRESH_RATE = 9; + + // Restrict displays max available resolution and refresh rates. It votes [0, LIMIT] + static final int PRIORITY_LIMIT_MODE = 10; // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh // rate to max value (same as for PRIORITY_UDFPS) on lock screen - static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8; + static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 11; // For concurrent displays we want to limit refresh rate on all displays - static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9; + static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 12; // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if // Settings.Global.LOW_POWER_MODE is on. - static final int PRIORITY_LOW_POWER_MODE = 10; + static final int PRIORITY_LOW_POWER_MODE = 13; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. // It's used to avoid refresh rate switches in certain conditions which may result in the // user seeing the display flickering when the switches occur. - static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11; + static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 14; // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL. - static final int PRIORITY_SKIN_TEMPERATURE = 12; + static final int PRIORITY_SKIN_TEMPERATURE = 15; // The proximity sensor needs the refresh rate to be locked in order to function, so this is // set to a high priority. - static final int PRIORITY_PROXIMITY = 13; + static final int PRIORITY_PROXIMITY = 16; // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order // to function, so this needs to be the highest priority of all votes. - static final int PRIORITY_UDFPS = 14; + static final int PRIORITY_UDFPS = 17; // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString. @@ -127,6 +139,14 @@ final class Vote { */ public final int height; /** + * Min requested width of the display in pixels, or 0; + */ + public final int minWidth; + /** + * Min requested height of the display in pixels, or 0; + */ + public final int minHeight; + /** * Information about the refresh rate frame rate ranges DM would like to set the display to. */ public final SurfaceControl.RefreshRateRanges refreshRateRanges; @@ -144,42 +164,82 @@ final class Vote { public final float appRequestBaseModeRefreshRate; static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) { - return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate, 0, - Float.POSITIVE_INFINITY, - minRefreshRate == maxRefreshRate, 0f); + return new Vote(/* minWidth= */ 0, /* minHeight= */ 0, + /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE, + /* minPhysicalRefreshRate= */ minRefreshRate, + /* maxPhysicalRefreshRate= */ maxRefreshRate, + /* minRenderFrameRate= */ 0, + /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY, + /* disableRefreshRateSwitching= */ minRefreshRate == maxRefreshRate, + /* baseModeRefreshRate= */ 0f); } static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) { - return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, minFrameRate, + return new Vote(/* minWidth= */ 0, /* minHeight= */ 0, + /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE, + /* minPhysicalRefreshRate= */ 0, + /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY, + minFrameRate, maxFrameRate, - false, 0f); + /* disableRefreshRateSwitching= */ false, + /* baseModeRefreshRate= */ 0f); } static Vote forSize(int width, int height) { - return new Vote(width, height, 0, Float.POSITIVE_INFINITY, 0, Float.POSITIVE_INFINITY, - false, - 0f); + return new Vote(/* minWidth= */ width, /* minHeight= */ height, + width, height, + /* minPhysicalRefreshRate= */ 0, + /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY, + /* minRenderFrameRate= */ 0, + /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY, + /* disableRefreshRateSwitching= */ false, + /* baseModeRefreshRate= */ 0f); + } + + static Vote forSizeAndPhysicalRefreshRatesRange(int minWidth, int minHeight, + int width, int height, float minRefreshRate, float maxRefreshRate) { + return new Vote(minWidth, minHeight, + width, height, + minRefreshRate, + maxRefreshRate, + /* minRenderFrameRate= */ 0, + /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY, + /* disableRefreshRateSwitching= */ minRefreshRate == maxRefreshRate, + /* baseModeRefreshRate= */ 0f); } static Vote forDisableRefreshRateSwitching() { - return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0, - Float.POSITIVE_INFINITY, true, - 0f); + return new Vote(/* minWidth= */ 0, /* minHeight= */ 0, + /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE, + /* minPhysicalRefreshRate= */ 0, + /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY, + /* minRenderFrameRate= */ 0, + /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY, + /* disableRefreshRateSwitching= */ true, + /* baseModeRefreshRate= */ 0f); } static Vote forBaseModeRefreshRate(float baseModeRefreshRate) { - return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0, - Float.POSITIVE_INFINITY, false, - baseModeRefreshRate); + return new Vote(/* minWidth= */ 0, /* minHeight= */ 0, + /* width= */ INVALID_SIZE, /* height= */ INVALID_SIZE, + /* minPhysicalRefreshRate= */ 0, + /* maxPhysicalRefreshRate= */ Float.POSITIVE_INFINITY, + /* minRenderFrameRate= */ 0, + /* maxRenderFrameRate= */ Float.POSITIVE_INFINITY, + /* disableRefreshRateSwitching= */ false, + /* baseModeRefreshRate= */ baseModeRefreshRate); } - private Vote(int width, int height, + private Vote(int minWidth, int minHeight, + int width, int height, float minPhysicalRefreshRate, float maxPhysicalRefreshRate, float minRenderFrameRate, float maxRenderFrameRate, boolean disableRefreshRateSwitching, float baseModeRefreshRate) { + this.minWidth = minWidth; + this.minHeight = minHeight; this.width = width; this.height = height; this.refreshRateRanges = new SurfaceControl.RefreshRateRanges( @@ -215,6 +275,12 @@ final class Vote { return "PRIORITY_UDFPS"; case PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE: return "PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE"; + case PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE: + return "PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE"; + case PRIORITY_LIMIT_MODE: + return "PRIORITY_LIMIT_MODE"; + case PRIORITY_SYNCHRONIZED_REFRESH_RATE: + return "PRIORITY_SYNCHRONIZED_REFRESH_RATE"; case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE: return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE"; case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE: @@ -229,9 +295,29 @@ final class Vote { @Override public String toString() { return "Vote: {" - + "width: " + width + ", height: " + height + + "minWidth: " + minWidth + ", minHeight: " + minHeight + + ", width: " + width + ", height: " + height + ", refreshRateRanges: " + refreshRateRanges + ", disableRefreshRateSwitching: " + disableRefreshRateSwitching + ", appRequestBaseModeRefreshRate: " + appRequestBaseModeRefreshRate + "}"; } + + @Override + public int hashCode() { + return Objects.hash(minWidth, minHeight, width, height, refreshRateRanges, + disableRefreshRateSwitching, appRequestBaseModeRefreshRate); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Vote)) return false; + final var vote = (Vote) o; + return minWidth == vote.minWidth && minHeight == vote.minHeight + && width == vote.width && height == vote.height + && disableRefreshRateSwitching == vote.disableRefreshRateSwitching + && Float.compare(vote.appRequestBaseModeRefreshRate, + appRequestBaseModeRefreshRate) == 0 + && refreshRateRanges.equals(vote.refreshRateRanges); + } } diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java index bdd2ab7d63b4..49c587aa5596 100644 --- a/services/core/java/com/android/server/display/mode/VotesStorage.java +++ b/services/core/java/com/android/server/display/mode/VotesStorage.java @@ -31,7 +31,8 @@ class VotesStorage { private static final String TAG = "VotesStorage"; // Special ID used to indicate that given vote is to be applied globally, rather than to a // specific display. - private static final int GLOBAL_ID = -1; + @VisibleForTesting + static final int GLOBAL_ID = -1; private boolean mLoggingEnabled; @@ -91,6 +92,7 @@ class VotesStorage { + ", vote=" + vote); return; } + boolean changed = false; SparseArray<Vote> votes; synchronized (mStorageLock) { if (mVotesByDisplay.contains(displayId)) { @@ -99,10 +101,13 @@ class VotesStorage { votes = new SparseArray<>(); mVotesByDisplay.put(displayId, votes); } - if (vote != null) { + var currentVote = votes.get(priority); + if (vote != null && !vote.equals(currentVote)) { votes.put(priority, vote); - } else { + changed = true; + } else if (vote == null && currentVote != null) { votes.remove(priority); + changed = true; } } Trace.traceCounter(Trace.TRACE_TAG_POWER, @@ -111,7 +116,9 @@ class VotesStorage { if (mLoggingEnabled) { Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes); } - mListener.onChanged(); + if (changed) { + mListener.onChanged(); + } } /** dump class values, for debugging */ diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java index b1a1c601cc0c..5d6e65071479 100644 --- a/services/core/java/com/android/server/display/state/DisplayStateController.java +++ b/services/core/java/com/android/server/display/state/DisplayStateController.java @@ -32,6 +32,7 @@ import java.io.PrintWriter; public class DisplayStateController { private DisplayPowerProximityStateController mDisplayPowerProximityStateController; private boolean mPerformScreenOffTransition = false; + private int mDozeStateOverride = Display.STATE_UNKNOWN; public DisplayStateController(DisplayPowerProximityStateController displayPowerProximityStateController) { @@ -65,6 +66,7 @@ public class DisplayStateController { } else { state = Display.STATE_DOZE; } + state = mDozeStateOverride == Display.STATE_UNKNOWN ? state : mDozeStateOverride; break; case DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM: case DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT: @@ -84,6 +86,10 @@ public class DisplayStateController { return state; } + public void overrideDozeScreenState(int displayState) { + mDozeStateOverride = displayState; + } + /** * Checks if the screen off transition is to be performed or not. */ @@ -100,6 +106,8 @@ public class DisplayStateController { pw.println(); pw.println("DisplayStateController:"); pw.println(" mPerformScreenOffTransition:" + mPerformScreenOffTransition); + pw.println(" mDozeStateOverride=" + mDozeStateOverride); + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); if (mDisplayPowerProximityStateController != null) { mDisplayPowerProximityStateController.dumpLocal(ipw); diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java index 41053e908a00..68848a2ad426 100644 --- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java +++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java @@ -16,35 +16,65 @@ package com.android.server.grammaticalinflection; +import static android.app.Flags.systemTermsOfAddressEnabled; import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED; import android.annotation.Nullable; +import android.app.GrammaticalInflectionManager; import android.app.IGrammaticalInflectionManager; import android.content.Context; import android.content.pm.PackageManagerInternal; import android.os.Binder; -import android.os.IBinder; +import android.os.Environment; import android.os.Process; +import android.os.ResultReceiver; +import android.os.ShellCallback; import android.os.SystemProperties; +import android.util.AtomicFile; import android.util.Log; +import android.util.SparseIntArray; +import android.util.Xml; import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.XmlUtils; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.wm.ActivityTaskManagerInternal; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + /** * The implementation of IGrammaticalInflectionManager.aidl. * * <p>This service is API entry point for storing app-specific grammatical inflection. */ public class GrammaticalInflectionService extends SystemService { - private final String TAG = "GrammaticalInflection"; + private static final String TAG = "GrammaticalInflection"; + private static final String ATTR_NAME = "grammatical_gender"; + private static final String USER_SETTINGS_FILE_NAME = "user_settings.xml"; + private static final String TAG_GRAMMATICAL_INFLECTION = "grammatical_inflection"; + private static final String GRAMMATICAL_INFLECTION_ENABLED = + "i18n.grammatical_Inflection.enabled"; + private final GrammaticalInflectionBackupHelper mBackupHelper; private final ActivityTaskManagerInternal mActivityTaskManagerInternal; + private final Object mLock = new Object(); + private final SparseIntArray mGrammaticalGenderCache = new SparseIntArray(); + private PackageManagerInternal mPackageManagerInternal; - private static final String GRAMMATICAL_INFLECTION_ENABLED = - "i18n.grammatical_Inflection.enabled"; + private GrammaticalInflectionService.GrammaticalInflectionBinderService mBinderService; /** * Initializes the system service. @@ -62,22 +92,46 @@ public class GrammaticalInflectionService extends SystemService { mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mBackupHelper = new GrammaticalInflectionBackupHelper( this, context.getPackageManager()); + mBinderService = new GrammaticalInflectionBinderService(); } @Override public void onStart() { - publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mService); + publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mBinderService); LocalServices.addService(GrammaticalInflectionManagerInternal.class, new GrammaticalInflectionManagerInternalImpl()); } - private final IBinder mService = new IGrammaticalInflectionManager.Stub() { + private final class GrammaticalInflectionBinderService extends + IGrammaticalInflectionManager.Stub { @Override public void setRequestedApplicationGrammaticalGender( String appPackageName, int userId, int gender) { GrammaticalInflectionService.this.setRequestedApplicationGrammaticalGender( appPackageName, userId, gender); } + + @Override + public void setSystemWideGrammaticalGender(int userId, int grammaticalGender) { + checkCallerIsSystem(); + checkSystemTermsOfAddressIsEnabled(); + GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender, + userId); + } + + @Override + public int getSystemGrammaticalGender(int userId) { + checkSystemTermsOfAddressIsEnabled(); + return GrammaticalInflectionService.this.getSystemGrammaticalGender(userId); + } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, + FileDescriptor err, String[] args, ShellCallback callback, + ResultReceiver resultReceiver) { + (new GrammaticalInflectionShellCommand(mBinderService)) + .exec(this, in, out, err, args, callback, resultReceiver); + } }; private final class GrammaticalInflectionManagerInternalImpl @@ -94,12 +148,6 @@ public class GrammaticalInflectionService extends SystemService { public void stageAndApplyRestoredPayload(byte[] payload, int userId) { mBackupHelper.stageAndApplyRestoredPayload(payload, userId); } - - private void checkCallerIsSystem() { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException("Caller is not system."); - } - } } protected int getApplicationGrammaticalGender(String appPackageName, int userId) { @@ -137,4 +185,105 @@ public class GrammaticalInflectionService extends SystemService { updater.setGrammaticalGender(gender).commit(); } + + protected void setSystemWideGrammaticalGender(int grammaticalGender, int userId) { + if (!GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains( + grammaticalGender)) { + throw new IllegalArgumentException("Unknown grammatical gender"); + } + + synchronized (mLock) { + final File file = getGrammaticalGenderFile(userId); + final AtomicFile atomicFile = new AtomicFile(file); + FileOutputStream stream = null; + try { + stream = atomicFile.startWrite(); + stream.write(toXmlByteArray(grammaticalGender, stream)); + atomicFile.finishWrite(stream); + mGrammaticalGenderCache.put(userId, grammaticalGender); + } catch (IOException e) { + Log.e(TAG, "Failed to write file " + atomicFile, e); + if (stream != null) { + atomicFile.failWrite(stream); + } + throw new RuntimeException(e); + } + } + } + + // TODO(b/298591009): Add a new AppOp value for the apps that want to access the grammatical + // gender. + public int getSystemGrammaticalGender(int userId) { + synchronized (mLock) { + final File file = getGrammaticalGenderFile(userId); + if (!file.exists()) { + Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file."); + return GRAMMATICAL_GENDER_NOT_SPECIFIED; + } + + if (mGrammaticalGenderCache.indexOfKey(userId) < 0) { + try { + InputStream in = new FileInputStream(file); + final TypedXmlPullParser parser = Xml.resolvePullParser(in); + mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser)); + } catch (IOException | XmlPullParserException e) { + Log.e(TAG, "Failed to parse XML configuration from " + file, e); + } + } + return mGrammaticalGenderCache.get(userId); + } + } + + private File getGrammaticalGenderFile(int userId) { + final File dir = new File(Environment.getDataSystemCeDirectory(userId), + TAG_GRAMMATICAL_INFLECTION); + return new File(dir, USER_SETTINGS_FILE_NAME); + } + + private byte[] toXmlByteArray(int grammaticalGender, FileOutputStream fileStream) { + + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + TypedXmlSerializer out = Xml.resolveSerializer(fileStream); + out.setOutput(outputStream, StandardCharsets.UTF_8.name()); + out.startDocument(/* encoding= */ null, /* standalone= */ true); + out.startTag(null, TAG_GRAMMATICAL_INFLECTION); + out.attributeInt(null, ATTR_NAME, grammaticalGender); + out.endTag(null, TAG_GRAMMATICAL_INFLECTION); + out.endDocument(); + + return outputStream.toByteArray(); + } catch (IOException e) { + return null; + } + } + + private int getGrammaticalGenderFromXml(TypedXmlPullParser parser) + throws IOException, XmlPullParserException { + + XmlUtils.nextElement(parser); + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + String tagName = parser.getName(); + if (TAG_GRAMMATICAL_INFLECTION.equals(tagName)) { + return parser.getAttributeInt(null, ATTR_NAME); + } else { + XmlUtils.nextElement(parser); + } + } + + return GRAMMATICAL_GENDER_NOT_SPECIFIED; + } + + private void checkCallerIsSystem() { + int callingUid = Binder.getCallingUid(); + if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID) { + throw new SecurityException("Caller is not system and shell."); + } + } + + private void checkSystemTermsOfAddressIsEnabled() { + if (!systemTermsOfAddressEnabled()) { + throw new RuntimeException("The flag must be enabled to allow calling the API."); + } + } } diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java new file mode 100644 index 000000000000..d22372860ead --- /dev/null +++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.grammaticalinflection; + +import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED; + +import android.app.ActivityManager; +import android.app.GrammaticalInflectionManager; +import android.app.IGrammaticalInflectionManager; +import android.content.res.Configuration; +import android.os.RemoteException; +import android.os.ShellCommand; +import android.os.UserHandle; +import android.util.SparseArray; + +import java.io.PrintWriter; + +/** + * Shell commands for {@link GrammaticalInflectionService} + */ +class GrammaticalInflectionShellCommand extends ShellCommand { + + private static final SparseArray<String> GRAMMATICAL_GENDER_MAP = new SparseArray<>(); + static { + GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED, + "Not specified (0)"); + GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_NEUTRAL, "Neuter (1)"); + GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_FEMININE, "Feminine (2)"); + GRAMMATICAL_GENDER_MAP.put(Configuration.GRAMMATICAL_GENDER_MASCULINE, "Masculine (3)"); + } + + private final IGrammaticalInflectionManager mBinderService; + + GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager) { + mBinderService = grammaticalInflectionManager; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + switch (cmd) { + case "set-system-grammatical-gender": + return runSetSystemWideGrammaticalGender(); + case "get-system-grammatical-gender": + return runGetSystemGrammaticalGender(); + default: { + return handleDefaultCommands(cmd); + } + } + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("Grammatical inflection manager (grammatical_inflection) shell commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println( + " set-system-grammatical-gender [--user <USER_ID>] [--grammaticalGender " + + "<GRAMMATICAL_GENDER>]"); + pw.println(" Set the system grammatical gender for system."); + pw.println(" --user <USER_ID>: apply for the given user, " + + "the current user is used when unspecified."); + pw.println( + " --grammaticalGender <GRAMMATICAL_GENDER>: The terms of address the user " + + "preferred in system, not specified (0) is used when unspecified."); + pw.println( + " eg. 0 = not_specified, 1 = neuter, 2 = feminine, 3 = masculine" + + "."); + pw.println( + " get-system-grammatical-gender [--user <USER_ID>]"); + pw.println(" Get the system grammatical gender for system."); + pw.println(" --user <USER_ID>: apply for the given user, " + + "the current user is used when unspecified."); + } + + private int runSetSystemWideGrammaticalGender() { + int userId = ActivityManager.getCurrentUser(); + int grammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED; + do { + String option = getNextOption(); + if (option == null) { + break; + } + switch (option) { + case "--user": { + userId = UserHandle.parseUserArg(getNextArgRequired()); + break; + } + case "-g": + case "--grammaticalGender": { + grammaticalGender = parseGrammaticalGender(); + break; + } + default: { + throw new IllegalArgumentException("Unknown option: " + option); + } + } + } while (true); + + try { + mBinderService.setSystemWideGrammaticalGender(userId, grammaticalGender); + } catch (RemoteException e) { + getOutPrintWriter().println("Remote Exception: " + e); + } + return 0; + } + + private int runGetSystemGrammaticalGender() { + int userId = ActivityManager.getCurrentUser(); + do { + String option = getNextOption(); + if (option == null) { + break; + } + switch (option) { + case "--user": { + userId = UserHandle.parseUserArg(getNextArgRequired()); + break; + } + default: { + throw new IllegalArgumentException("Unknown option: " + option); + } + } + } while (true); + + try { + int grammaticalGender = mBinderService.getSystemGrammaticalGender(userId); + getOutPrintWriter().println(GRAMMATICAL_GENDER_MAP.get(grammaticalGender)); + } catch (RemoteException e) { + getOutPrintWriter().println("Remote Exception: " + e); + } + return 0; + } + + private int parseGrammaticalGender() { + String arg = getNextArg(); + if (arg == null) { + return GRAMMATICAL_GENDER_NOT_SPECIFIED; + } else { + int grammaticalGender = Integer.parseInt(arg); + if (GrammaticalInflectionManager.VALID_GRAMMATICAL_GENDER_VALUES.contains( + grammaticalGender)) { + return grammaticalGender; + } else { + return GRAMMATICAL_GENDER_NOT_SPECIFIED; + } + } + } +} diff --git a/services/core/java/com/android/server/grammaticalinflection/OWNERS b/services/core/java/com/android/server/grammaticalinflection/OWNERS index 5f16ba9123b7..41d079ed9e75 100644 --- a/services/core/java/com/android/server/grammaticalinflection/OWNERS +++ b/services/core/java/com/android/server/grammaticalinflection/OWNERS @@ -2,3 +2,4 @@ allenwtsu@google.com goldmanj@google.com calvinpan@google.com +zoeychen@google.com diff --git a/services/core/java/com/android/server/lights/TEST_MAPPING b/services/core/java/com/android/server/lights/TEST_MAPPING index f868ea093500..17b98ce8e2d5 100644 --- a/services/core/java/com/android/server/lights/TEST_MAPPING +++ b/services/core/java/com/android/server/lights/TEST_MAPPING @@ -4,16 +4,14 @@ "name": "CtsHardwareTestCases", "options": [ {"include-filter": "com.android.hardware.lights"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.LargeTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"} + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "androidx.test.filters.LargeTest"} ] }, { "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.lights"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index c3abfc16ca7d..f168f435b5d7 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -349,17 +349,17 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void onUserStarting(@NonNull TargetUser user) { - mLockSettingsService.onStartUser(user.getUserIdentifier()); + mLockSettingsService.onUserStarting(user.getUserIdentifier()); } @Override public void onUserUnlocking(@NonNull TargetUser user) { - mLockSettingsService.onUnlockUser(user.getUserIdentifier()); + mLockSettingsService.onUserUnlocking(user.getUserIdentifier()); } @Override public void onUserStopped(@NonNull TargetUser user) { - mLockSettingsService.onCleanupUser(user.getUserIdentifier()); + mLockSettingsService.onUserStopped(user.getUserIdentifier()); } } @@ -784,7 +784,7 @@ public class LockSettingsService extends ILockSettings.Stub { } @VisibleForTesting - void onCleanupUser(int userId) { + void onUserStopped(int userId) { hideEncryptionNotification(new UserHandle(userId)); // User is stopped with its CE key evicted. Restore strong auth requirement to the default // flags after boot since stopping and restarting a user later is equivalent to rebooting @@ -796,7 +796,7 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private void onStartUser(final int userId) { + private void onUserStarting(final int userId) { maybeShowEncryptionNotificationForUser(userId, "user started"); } @@ -832,7 +832,7 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private void onUnlockUser(final int userId) { + private void onUserUnlocking(final int userId) { // Perform tasks which require locks in LSS on a handler, as we are callbacks from // ActivityManager.unlockUser() mHandler.post(new Runnable() { diff --git a/services/core/java/com/android/server/locksettings/TEST_MAPPING b/services/core/java/com/android/server/locksettings/TEST_MAPPING index b881b44bbaa0..ddf3d76e97ae 100644 --- a/services/core/java/com/android/server/locksettings/TEST_MAPPING +++ b/services/core/java/com/android/server/locksettings/TEST_MAPPING @@ -7,7 +7,7 @@ "include-annotation": "com.android.cts.devicepolicy.annotations.LockSettingsTest" }, { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] } @@ -20,7 +20,7 @@ "include-filter": "com.android.server.locksettings." }, { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] } diff --git a/services/core/java/com/android/server/logcat/TEST_MAPPING b/services/core/java/com/android/server/logcat/TEST_MAPPING index f4b13a0e0bfa..904155226d7f 100644 --- a/services/core/java/com/android/server/logcat/TEST_MAPPING +++ b/services/core/java/com/android/server/logcat/TEST_MAPPING @@ -4,7 +4,6 @@ "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.logcat"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 4892c22449cb..83a3125884ac 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -22,6 +22,7 @@ import static android.content.Intent.ACTION_SCREEN_ON; import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; import static android.media.MediaRouter2Utils.getOriginalId; import static android.media.MediaRouter2Utils.getProviderId; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.media.MediaFeatureFlagManager.FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE; @@ -487,12 +488,13 @@ class MediaRouter2ServiceImpl { final int callerUid = Binder.getCallingUid(); final int callerPid = Binder.getCallingPid(); - final int userId = UserHandle.getUserHandleForUid(callerUid).getIdentifier(); + final int callerUserId = UserHandle.getUserHandleForUid(callerUid).getIdentifier(); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - registerManagerLocked(manager, callerUid, callerPid, callerPackageName, userId); + registerManagerLocked( + manager, callerUid, callerPid, callerPackageName, callerUserId); } } finally { Binder.restoreCallingIdentity(token); @@ -1156,8 +1158,12 @@ class MediaRouter2ServiceImpl { } @GuardedBy("mLock") - private void registerManagerLocked(@NonNull IMediaRouter2Manager manager, - int callerUid, int callerPid, @NonNull String callerPackageName, int userId) { + private void registerManagerLocked( + @NonNull IMediaRouter2Manager manager, + int callerUid, + int callerPid, + @NonNull String callerPackageName, + int callerUserId) { final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); @@ -1167,14 +1173,17 @@ class MediaRouter2ServiceImpl { return; } - Slog.i(TAG, TextUtils.formatSimple( - "registerManager | callerUid: %d, callerPid: %d, package: %s, user: %d", - callerUid, callerPid, callerPackageName, userId)); + Slog.i( + TAG, + TextUtils.formatSimple( + "registerManager | callerUid: %d, callerPid: %d, callerPackage: %s," + + " callerUserId: %d", + callerUid, callerPid, callerPackageName, callerUserId)); mContext.enforcePermission(Manifest.permission.MEDIA_CONTENT_CONTROL, callerPid, callerUid, "Must hold MEDIA_CONTENT_CONTROL permission."); - UserRecord userRecord = getOrCreateUserRecordLocked(userId); + UserRecord userRecord = getOrCreateUserRecordLocked(callerUserId); managerRecord = new ManagerRecord( userRecord, manager, callerUid, callerPid, callerPackageName); try { diff --git a/services/core/java/com/android/server/media/projection/TEST_MAPPING b/services/core/java/com/android/server/media/projection/TEST_MAPPING index a792498b8521..7aa9118e45ee 100644 --- a/services/core/java/com/android/server/media/projection/TEST_MAPPING +++ b/services/core/java/com/android/server/media/projection/TEST_MAPPING @@ -4,9 +4,6 @@ "name": "MediaProjectionTests", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { diff --git a/services/core/java/com/android/server/net/OWNERS b/services/core/java/com/android/server/net/OWNERS index 9c96d46f15b8..d0e95dd55b6c 100644 --- a/services/core/java/com/android/server/net/OWNERS +++ b/services/core/java/com/android/server/net/OWNERS @@ -1,5 +1,5 @@ set noparent -file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking +file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking jsharkey@android.com sudheersai@google.com diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index c6f6fe2c01f3..fe91050917f5 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -1021,7 +1021,7 @@ abstract public class ManagedServices { synchronized (mSnoozing) { mSnoozing.remove(user); } - rebindServices(true, user); + unbindUserServices(user); } public void onUserSwitched(int user) { @@ -1408,12 +1408,24 @@ abstract public class ManagedServices { void unbindOtherUserServices(int currentUser) { TimingsTraceAndSlog t = new TimingsTraceAndSlog(); t.traceBegin("ManagedServices.unbindOtherUserServices_current" + currentUser); - final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>(); + unbindServicesImpl(currentUser, true /* allExceptUser */); + t.traceEnd(); + } + + void unbindUserServices(int user) { + TimingsTraceAndSlog t = new TimingsTraceAndSlog(); + t.traceBegin("ManagedServices.unbindUserServices" + user); + unbindServicesImpl(user, false /* allExceptUser */); + t.traceEnd(); + } + void unbindServicesImpl(int user, boolean allExceptUser) { + final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>(); synchronized (mMutex) { final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices(); for (ManagedServiceInfo info : removableBoundServices) { - if (info.userid != currentUser) { + if ((allExceptUser && (info.userid != user)) + || (!allExceptUser && (info.userid == user))) { Set<ComponentName> toUnbind = componentsToUnbind.get(info.userid, new ArraySet<>()); toUnbind.add(info.component); @@ -1422,7 +1434,6 @@ abstract public class ManagedServices { } } unbindFromServices(componentsToUnbind); - t.traceEnd(); } protected void unbindFromServices(SparseArray<Set<ComponentName>> componentsToUnbind) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 802dfb182297..a3c71c2e0218 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -177,6 +177,7 @@ import android.app.compat.CompatChanges; import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; import android.app.usage.UsageEvents; +import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; import android.compat.annotation.ChangeId; @@ -263,6 +264,7 @@ import android.telecom.TelecomManager; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -4159,7 +4161,7 @@ public class NotificationManagerService extends SystemService { String pkg) { checkCallerIsSystemOrSameApp(pkg); return mPreferencesHelper.getNotificationChannelGroups( - pkg, Binder.getCallingUid(), false, false, true); + pkg, Binder.getCallingUid(), false, false, true, true, null); } @Override @@ -4280,7 +4282,36 @@ public class NotificationManagerService extends SystemService { String pkg, int uid, boolean includeDeleted) { enforceSystemOrSystemUI("getNotificationChannelGroupsForPackage"); return mPreferencesHelper.getNotificationChannelGroups( - pkg, uid, includeDeleted, true, false); + pkg, uid, includeDeleted, true, false, true, null); + } + + @Override + public ParceledListSlice<NotificationChannelGroup> + getRecentBlockedNotificationChannelGroupsForPackage(String pkg, int uid) { + enforceSystemOrSystemUI("getRecentBlockedNotificationChannelGroupsForPackage"); + Set<String> recentlySentChannels = new HashSet<>(); + long now = System.currentTimeMillis(); + long startTime = now - (DateUtils.DAY_IN_MILLIS * 14); + UsageEvents events = mUsageStatsManagerInternal.queryEventsForUser( + UserHandle.getUserId(uid), startTime, now, UsageEvents.SHOW_ALL_EVENT_DATA); + // get all channelids that sent notifs in the past 2 weeks + if (events != null) { + UsageEvents.Event event = new UsageEvents.Event(); + while (events.hasNextEvent()) { + events.getNextEvent(event); + if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) { + if (pkg.equals(event.mPackage)) { + String channelId = event.mNotificationChannelId; + if (channelId != null) { + recentlySentChannels.add(channelId); + } + } + } + } + } + + return mPreferencesHelper.getNotificationChannelGroups( + pkg, uid, false, true, false, true, recentlySentChannels); } @Override diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index b132a23b575b..de698d916cce 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -1459,9 +1459,9 @@ public class PreferencesHelper implements RankingConfig { } } - @Override public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, - int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) { + int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty, + boolean includeBlocked, Set<String> activeChannelFilter) { Objects.requireNonNull(pkg); Map<String, NotificationChannelGroup> groups = new ArrayMap<>(); synchronized (mPackagePreferences) { @@ -1473,7 +1473,11 @@ public class PreferencesHelper implements RankingConfig { int N = r.channels.size(); for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); - if (includeDeleted || !nc.isDeleted()) { + boolean includeChannel = (includeDeleted || !nc.isDeleted()) + && (activeChannelFilter == null + || (includeBlocked && nc.getImportance() == IMPORTANCE_NONE) + || activeChannelFilter.contains(nc.getId())); + if (includeChannel) { if (nc.getGroup() != null) { if (r.groups.get(nc.getGroup()) != null) { NotificationChannelGroup ncg = groups.get(nc.getGroup()); @@ -1481,7 +1485,6 @@ public class PreferencesHelper implements RankingConfig { ncg = r.groups.get(nc.getGroup()).clone(); ncg.setChannels(new ArrayList<>()); groups.put(nc.getGroup(), ncg); - } ncg.addChannel(nc); } diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index fec359198e88..8df24c9911a6 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -40,8 +40,6 @@ public interface RankingConfig { int uid); void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromTargetApp, int callingUid, boolean isSystemOrSystemUi); - ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg, - int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty); boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess, int callingUid, boolean isSystemOrSystemUi); diff --git a/services/core/java/com/android/server/notification/TEST_MAPPING b/services/core/java/com/android/server/notification/TEST_MAPPING index 7db2e8b1333f..468c4518602e 100644 --- a/services/core/java/com/android/server/notification/TEST_MAPPING +++ b/services/core/java/com/android/server/notification/TEST_MAPPING @@ -4,9 +4,6 @@ "name": "CtsNotificationTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { @@ -14,9 +11,6 @@ }, { "exclude-annotation": "androidx.test.filters.LargeTest" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" } ] }, @@ -24,9 +18,6 @@ "name": "FrameworksUiServicesTests", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { @@ -34,9 +25,6 @@ }, { "exclude-annotation": "androidx.test.filters.LargeTest" - }, - { - "exclude-annotation": "androidx.test.filters.LargeTest" } ] } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 52eef47b3759..b9464d96a019 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -18,9 +18,6 @@ package com.android.server.om; import static android.app.AppGlobals.getPackageManager; import static android.content.Intent.ACTION_OVERLAY_CHANGED; -import static android.content.Intent.ACTION_PACKAGE_ADDED; -import static android.content.Intent.ACTION_PACKAGE_CHANGED; -import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.ACTION_USER_ADDED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_PACKAGE_NAME; @@ -31,10 +28,10 @@ import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISA import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED; import static android.content.om.OverlayManagerTransaction.Request.TYPE_UNREGISTER_FABRICATED; import static android.content.pm.PackageManager.SIGNATURE_MATCH; +import static android.os.Process.INVALID_UID; import static android.os.Trace.TRACE_TAG_RRO; import static android.os.Trace.traceBegin; import static android.os.Trace.traceEnd; - import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException; import android.annotation.NonNull; @@ -82,6 +79,7 @@ import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.content.PackageMonitor; import com.android.internal.content.om.OverlayConfig; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -261,6 +259,8 @@ public final class OverlayManagerService extends SystemService { private final OverlayActorEnforcer mActorEnforcer; + private final PackageMonitor mPackageMonitor = new OverlayManagerPackageMonitor(); + private int mPrevStartedUserId = -1; public OverlayManagerService(@NonNull final Context context) { @@ -277,16 +277,9 @@ public final class OverlayManagerService extends SystemService { OverlayConfig.getSystemInstance(), getDefaultOverlayPackages()); mActorEnforcer = new OverlayActorEnforcer(mPackageManager); - HandlerThread packageReceiverThread = new HandlerThread(TAG); - packageReceiverThread.start(); - - final IntentFilter packageFilter = new IntentFilter(); - packageFilter.addAction(ACTION_PACKAGE_ADDED); - packageFilter.addAction(ACTION_PACKAGE_CHANGED); - packageFilter.addAction(ACTION_PACKAGE_REMOVED); - packageFilter.addDataScheme("package"); - getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, - packageFilter, null, packageReceiverThread.getThreadHandler()); + HandlerThread packageMonitorThread = new HandlerThread(TAG); + packageMonitorThread.start(); + mPackageMonitor.register(context, packageMonitorThread.getLooper(), true); final IntentFilter userFilter = new IntentFilter(); userFilter.addAction(ACTION_USER_ADDED); @@ -372,166 +365,171 @@ public final class OverlayManagerService extends SystemService { return defaultPackages.toArray(new String[defaultPackages.size()]); } - private final class PackageReceiver extends BroadcastReceiver { + private final class OverlayManagerPackageMonitor extends PackageMonitor { + @Override - public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { - final String action = intent.getAction(); - if (action == null) { - Slog.e(TAG, "Cannot handle package broadcast with null action"); - return; - } - final Uri data = intent.getData(); - if (data == null) { - Slog.e(TAG, "Cannot handle package broadcast with null data"); - return; - } - final String packageName = data.getSchemeSpecificPart(); + public void onPackageAppearedWithExtras(String packageName, Bundle extras) { + handlePackageAdd(packageName, extras); + } - final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); - final boolean systemUpdateUninstall = - intent.getBooleanExtra(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, false); + @Override + public void onPackageChangedWithExtras(String packageName, Bundle extras) { + handlePackageChange(packageName, extras); + } - final int[] userIds; - final int extraUid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL); - if (extraUid == UserHandle.USER_NULL) { - userIds = mUserManager.getUserIds(); - } else { - userIds = new int[] { UserHandle.getUserId(extraUid) }; - } + @Override + public void onPackageDisappearedWithExtras(String packageName, Bundle extras) { + handlePackageRemove(packageName, extras); + } + } - switch (action) { - case ACTION_PACKAGE_ADDED: - if (replacing) { - onPackageReplaced(packageName, userIds); - } else { - onPackageAdded(packageName, userIds); - } - break; - case ACTION_PACKAGE_CHANGED: - // ignore the intent if it was sent by the package manager as a result of the - // overlay manager having sent ACTION_OVERLAY_CHANGED - if (!ACTION_OVERLAY_CHANGED.equals(intent.getStringExtra(EXTRA_REASON))) { - onPackageChanged(packageName, userIds); - } - break; - case ACTION_PACKAGE_REMOVED: - if (replacing) { - onPackageReplacing(packageName, systemUpdateUninstall, userIds); - } else { - onPackageRemoved(packageName, userIds); - } - break; - default: - // do nothing - break; - } + private int[] getUserIds(int uid) { + final int[] userIds; + if (uid == INVALID_UID) { + userIds = mUserManager.getUserIds(); + } else { + userIds = new int[] { UserHandle.getUserId(uid) }; } + return userIds; + } - private void onPackageAdded(@NonNull final String packageName, - @NonNull final int[] userIds) { - try { - traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName); - for (final int userId : userIds) { - synchronized (mLock) { - var packageState = mPackageManager.onPackageAdded(packageName, userId); - if (packageState != null && !mPackageManager.isInstantApp(packageName, - userId)) { - try { - updateTargetPackagesLocked( - mImpl.onPackageAdded(packageName, userId)); - } catch (OperationFailedException e) { - Slog.e(TAG, "onPackageAdded internal error", e); - } + private void handlePackageAdd(String packageName, Bundle extras) { + final boolean replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); + final int uid = extras.getInt(Intent.EXTRA_UID, 0); + final int[] userIds = getUserIds(uid); + if (replacing) { + onPackageReplaced(packageName, userIds); + } else { + onPackageAdded(packageName, userIds); + } + } + + private void handlePackageChange(String packageName, Bundle extras) { + final int uid = extras.getInt(Intent.EXTRA_UID, 0); + final int[] userIds = getUserIds(uid); + if (!ACTION_OVERLAY_CHANGED.equals(extras.getString(EXTRA_REASON))) { + onPackageChanged(packageName, userIds); + } + } + + private void handlePackageRemove(String packageName, Bundle extras) { + final boolean replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false); + final boolean systemUpdateUninstall = + extras.getBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, false); + final int uid = extras.getInt(Intent.EXTRA_UID, 0); + final int[] userIds = getUserIds(uid); + + if (replacing) { + onPackageReplacing(packageName, systemUpdateUninstall, userIds); + } else { + onPackageRemoved(packageName, userIds); + } + } + + private void onPackageAdded(@NonNull final String packageName, + @NonNull final int[] userIds) { + try { + traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName); + for (final int userId : userIds) { + synchronized (mLock) { + var packageState = mPackageManager.onPackageAdded(packageName, userId); + if (packageState != null && !mPackageManager.isInstantApp(packageName, + userId)) { + try { + updateTargetPackagesLocked( + mImpl.onPackageAdded(packageName, userId)); + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageAdded internal error", e); } } } - } finally { - traceEnd(TRACE_TAG_RRO); } + } finally { + traceEnd(TRACE_TAG_RRO); } + } - private void onPackageChanged(@NonNull final String packageName, - @NonNull final int[] userIds) { - try { - traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName); - for (int userId : userIds) { - synchronized (mLock) { - var packageState = mPackageManager.onPackageUpdated(packageName, userId); - if (packageState != null && !mPackageManager.isInstantApp(packageName, - userId)) { - try { - updateTargetPackagesLocked( - mImpl.onPackageChanged(packageName, userId)); - } catch (OperationFailedException e) { - Slog.e(TAG, "onPackageChanged internal error", e); - } + private void onPackageChanged(@NonNull final String packageName, + @NonNull final int[] userIds) { + try { + traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName); + for (int userId : userIds) { + synchronized (mLock) { + var packageState = mPackageManager.onPackageUpdated(packageName, userId); + if (packageState != null && !mPackageManager.isInstantApp(packageName, + userId)) { + try { + updateTargetPackagesLocked( + mImpl.onPackageChanged(packageName, userId)); + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageChanged internal error", e); } } } - } finally { - traceEnd(TRACE_TAG_RRO); } + } finally { + traceEnd(TRACE_TAG_RRO); } + } - private void onPackageReplacing(@NonNull final String packageName, - boolean systemUpdateUninstall, @NonNull final int[] userIds) { - try { - traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName); - for (int userId : userIds) { - synchronized (mLock) { - var packageState = mPackageManager.onPackageUpdated(packageName, userId); - if (packageState != null && !mPackageManager.isInstantApp(packageName, - userId)) { - try { - updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName, - systemUpdateUninstall, userId)); - } catch (OperationFailedException e) { - Slog.e(TAG, "onPackageReplacing internal error", e); - } + private void onPackageReplacing(@NonNull final String packageName, + boolean systemUpdateUninstall, @NonNull final int[] userIds) { + try { + traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName); + for (int userId : userIds) { + synchronized (mLock) { + var packageState = mPackageManager.onPackageUpdated(packageName, userId); + if (packageState != null && !mPackageManager.isInstantApp(packageName, + userId)) { + try { + updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName, + systemUpdateUninstall, userId)); + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageReplacing internal error", e); } } } - } finally { - traceEnd(TRACE_TAG_RRO); } + } finally { + traceEnd(TRACE_TAG_RRO); } + } - private void onPackageReplaced(@NonNull final String packageName, - @NonNull final int[] userIds) { - try { - traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName); - for (int userId : userIds) { - synchronized (mLock) { - var packageState = mPackageManager.onPackageUpdated(packageName, userId); - if (packageState != null && !mPackageManager.isInstantApp(packageName, - userId)) { - try { - updateTargetPackagesLocked( - mImpl.onPackageReplaced(packageName, userId)); - } catch (OperationFailedException e) { - Slog.e(TAG, "onPackageReplaced internal error", e); - } + private void onPackageReplaced(@NonNull final String packageName, + @NonNull final int[] userIds) { + try { + traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName); + for (int userId : userIds) { + synchronized (mLock) { + var packageState = mPackageManager.onPackageUpdated(packageName, userId); + if (packageState != null && !mPackageManager.isInstantApp(packageName, + userId)) { + try { + updateTargetPackagesLocked( + mImpl.onPackageReplaced(packageName, userId)); + } catch (OperationFailedException e) { + Slog.e(TAG, "onPackageReplaced internal error", e); } } } - } finally { - traceEnd(TRACE_TAG_RRO); } + } finally { + traceEnd(TRACE_TAG_RRO); } + } - private void onPackageRemoved(@NonNull final String packageName, - @NonNull final int[] userIds) { - try { - traceBegin(TRACE_TAG_RRO, "OMS#onPackageRemoved " + packageName); - for (int userId : userIds) { - synchronized (mLock) { - mPackageManager.onPackageRemoved(packageName, userId); - updateTargetPackagesLocked(mImpl.onPackageRemoved(packageName, userId)); - } + private void onPackageRemoved(@NonNull final String packageName, + @NonNull final int[] userIds) { + try { + traceBegin(TRACE_TAG_RRO, "OMS#onPackageRemoved " + packageName); + for (int userId : userIds) { + synchronized (mLock) { + mPackageManager.onPackageRemoved(packageName, userId); + updateTargetPackagesLocked(mImpl.onPackageRemoved(packageName, userId)); } - } finally { - traceEnd(TRACE_TAG_RRO); } + } finally { + traceEnd(TRACE_TAG_RRO); } } @@ -684,7 +682,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { try { mImpl.setEnabledExclusive( - overlay, false /* withinCategory */, realUserId) + overlay, false /* withinCategory */, realUserId) .ifPresent( OverlayManagerService.this::updateTargetPackagesLocked); return true; diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index 9a69d77c61f0..e367609e89b6 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -17,9 +17,14 @@ package com.android.server.pm; import static android.os.PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED; +import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED; import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.os.Process.SYSTEM_UID; import static android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED; + +import static com.android.server.pm.PackageManagerService.DEBUG_BACKUP; import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; +import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY; import static com.android.server.pm.PackageManagerService.PACKAGE_SCHEME; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.PackageManagerService.TAG; @@ -28,6 +33,7 @@ import android.Manifest; import android.annotation.AppIdInt; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.BroadcastOptions; @@ -38,12 +44,18 @@ import android.content.IIntentReceiver; import android.content.Intent; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.os.PowerExemptionManager; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; import android.provider.DeviceConfig; +import android.stats.storage.StorageEnums; import android.util.IntArray; import android.util.Log; import android.util.Pair; @@ -51,10 +63,15 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FrameworkStatsLog; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.PackageUserStateInternal; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.function.BiFunction; -import java.util.function.Supplier; /** * Helper class to send broadcasts for various situations. @@ -70,14 +87,20 @@ public final class BroadcastHelper { private final UserManagerInternal mUmInternal; private final ActivityManagerInternal mAmInternal; private final Context mContext; + private final Handler mHandler; + private final PackageMonitorCallbackHelper mPackageMonitorCallbackHelper; + private final AppsFilterSnapshot mAppsFilter; BroadcastHelper(PackageManagerServiceInjector injector) { mUmInternal = injector.getUserManagerInternal(); mAmInternal = injector.getActivityManagerInternal(); mContext = injector.getContext(); + mHandler = injector.getHandler(); + mPackageMonitorCallbackHelper = injector.getPackageMonitorCallbackHelper(); + mAppsFilter = injector.getAppsFilter(); } - public void sendPackageBroadcast(final String action, final String pkg, final Bundle extras, + void sendPackageBroadcast(final String action, final String pkg, final Bundle extras, final int flags, final String targetPkg, final IIntentReceiver finishedReceiver, final int[] userIds, int[] instantUserIds, @Nullable SparseArray<int[]> broadcastAllowList, @@ -114,9 +137,16 @@ public final class BroadcastHelper { * the system and applications allowed to see instant applications to receive package * lifecycle events for instant applications. */ - public void doSendBroadcast(String action, String pkg, Bundle extras, - int flags, String targetPkg, IIntentReceiver finishedReceiver, - int[] userIds, boolean isInstantApp, @Nullable SparseArray<int[]> broadcastAllowList, + private void doSendBroadcast( + @NonNull String action, + @Nullable String pkg, + @Nullable Bundle extras, + int flags, + @Nullable String targetPkg, + @Nullable IIntentReceiver finishedReceiver, + @NonNull int[] userIds, + boolean isInstantApp, + @Nullable SparseArray<int[]> broadcastAllowList, @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver, @Nullable Bundle bOptions) { for (int userId : userIds) { @@ -166,9 +196,11 @@ public final class BroadcastHelper { } } - public void sendResourcesChangedBroadcast(@NonNull Supplier<Computer> snapshotComputer, - boolean mediaStatus, boolean replacing, @NonNull String[] pkgNames, - @NonNull int[] uids) { + void sendResourcesChangedBroadcast(@NonNull Computer snapshot, + boolean mediaStatus, + boolean replacing, + @NonNull String[] pkgNames, + @NonNull int[] uids) { if (ArrayUtils.isEmpty(pkgNames) || ArrayUtils.isEmpty(uids)) { return; } @@ -184,7 +216,7 @@ public final class BroadcastHelper { null /* targetPkg */, null /* finishedReceiver */, null /* userIds */, null /* instantUserIds */, null /* broadcastAllowList */, (callingUid, intentExtras) -> filterExtrasChangedPackageList( - snapshotComputer.get(), callingUid, intentExtras), + snapshot, callingUid, intentExtras), null /* bOptions */); } @@ -193,8 +225,9 @@ public final class BroadcastHelper { * automatically without needing an explicit launch. * Send it a LOCKED_BOOT_COMPLETED/BOOT_COMPLETED if it would ordinarily have gotten ones. */ - public void sendBootCompletedBroadcastToSystemApp( - String packageName, boolean includeStopped, int userId) { + private void sendBootCompletedBroadcastToSystemApp(@NonNull String packageName, + boolean includeStopped, + int userId) { // If user is not running, the app didn't miss any broadcast if (!mUmInternal.isUserRunning(userId)) { return; @@ -229,7 +262,7 @@ public final class BroadcastHelper { } } - public @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions( + private @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions( @PowerExemptionManager.ReasonCode int reasonCode) { long duration = 10_000; if (mAmInternal != null) { @@ -242,9 +275,14 @@ public final class BroadcastHelper { return bOptions; } - public void sendPackageChangedBroadcast(String packageName, boolean dontKillApp, - ArrayList<String> componentNames, int packageUid, String reason, - int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList) { + private void sendPackageChangedBroadcast(@NonNull String packageName, + boolean dontKillApp, + @NonNull ArrayList<String> componentNames, + int packageUid, + @Nullable String reason, + @Nullable int[] userIds, + @Nullable int[] instantUserIds, + @Nullable SparseArray<int[]> broadcastAllowList) { if (DEBUG_INSTALL) { Log.v(TAG, "Sending package changed: package=" + packageName + " components=" + componentNames); @@ -269,7 +307,7 @@ public final class BroadcastHelper { null /* bOptions */); } - public static void sendDeviceCustomizationReadyBroadcast() { + static void sendDeviceCustomizationReadyBroadcast() { final Intent intent = new Intent(Intent.ACTION_DEVICE_CUSTOMIZATION_READY); intent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); final IActivityManager am = ActivityManager.getService(); @@ -285,15 +323,23 @@ public final class BroadcastHelper { } } - public void sendSessionCommitBroadcast(PackageInstaller.SessionInfo sessionInfo, int userId, - int launcherUid, @Nullable ComponentName launcherComponent, - @Nullable String appPredictionServicePackage) { + void sendSessionCommitBroadcast(@NonNull Computer snapshot, + @NonNull PackageInstaller.SessionInfo sessionInfo, + int userId, + @Nullable String appPredictionServicePackage) { + UserManagerService ums = UserManagerService.getInstance(); + if (ums == null || sessionInfo.isStaged()) { + return; + } + final UserInfo parent = ums.getProfileParent(userId); + final int launcherUserId = (parent != null) ? parent.id : userId; + final ComponentName launcherComponent = snapshot.getDefaultHomeActivity(launcherUserId); if (launcherComponent != null) { Intent launcherIntent = new Intent(PackageInstaller.ACTION_SESSION_COMMITTED) .putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo) .putExtra(Intent.EXTRA_USER, UserHandle.of(userId)) .setPackage(launcherComponent.getPackageName()); - mContext.sendBroadcastAsUser(launcherIntent, UserHandle.of(launcherUid)); + mContext.sendBroadcastAsUser(launcherIntent, UserHandle.of(launcherUserId)); } // TODO(b/122900055) Change/Remove this and replace with new permission role. if (appPredictionServicePackage != null) { @@ -301,30 +347,278 @@ public final class BroadcastHelper { .putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo) .putExtra(Intent.EXTRA_USER, UserHandle.of(userId)) .setPackage(appPredictionServicePackage); - mContext.sendBroadcastAsUser(predictorIntent, UserHandle.of(launcherUid)); + mContext.sendBroadcastAsUser(predictorIntent, UserHandle.of(launcherUserId)); } } - public void sendPreferredActivityChangedBroadcast(int userId) { - final IActivityManager am = ActivityManager.getService(); - if (am == null) { - return; + void sendPreferredActivityChangedBroadcast(int userId) { + mHandler.post(() -> { + final IActivityManager am = ActivityManager.getService(); + if (am == null) { + return; + } + + final Intent intent = new Intent(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + try { + am.broadcastIntentWithFeature(null, null, intent, null, null, + 0, null, null, null, null, null, android.app.AppOpsManager.OP_NONE, + null, false, false, userId); + } catch (RemoteException e) { + } + }); + } + + void sendPostInstallBroadcasts(@NonNull Computer snapshot, + @NonNull InstallRequest request, + @NonNull String packageName, + @NonNull String requiredPermissionControllerPackage, + @NonNull String[] requiredVerifierPackages, + @NonNull String requiredInstallerPackage, + @NonNull PackageSender packageSender, + boolean isLaunchedForRestore, + boolean isKillApp, + boolean isUpdate, + boolean isArchived) { + // Send the removed broadcasts + if (request.getRemovedInfo() != null) { + if (request.getRemovedInfo().mIsExternal) { + if (DEBUG_INSTALL) { + Slog.i(TAG, "upgrading pkg " + request.getRemovedInfo().mRemovedPackage + + " is ASEC-hosted -> UNAVAILABLE"); + } + final String[] pkgNames = new String[]{ + request.getRemovedInfo().mRemovedPackage}; + final int[] uids = new int[]{request.getRemovedInfo().mUid}; + notifyResourcesChanged( + false /* mediaStatus */, true /* replacing */, pkgNames, uids); + sendResourcesChangedBroadcast( + snapshot, false /* mediaStatus */, true /* replacing */, pkgNames, uids); + } + sendPackageRemovedBroadcasts( + request.getRemovedInfo(), packageSender, isKillApp, false /*removedBySystem*/, + false /*isArchived*/); } - final Intent intent = new Intent(Intent.ACTION_PREFERRED_ACTIVITY_CHANGED); - intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - try { - am.broadcastIntentWithFeature(null, null, intent, null, null, - 0, null, null, null, null, null, android.app.AppOpsManager.OP_NONE, - null, false, false, userId); - } catch (RemoteException e) { + final int[] firstUserIds = request.getFirstTimeBroadcastUserIds(); + final int[] firstInstantUserIds = request.getFirstTimeBroadcastInstantUserIds(); + final int[] updateUserIds = request.getUpdateBroadcastUserIds(); + final int[] instantUserIds = request.getUpdateBroadcastInstantUserIds(); + + final String installerPackageName = + request.getInstallerPackageName() != null + ? request.getInstallerPackageName() + : request.getRemovedInfo() != null + ? request.getRemovedInfo().mInstallerPackageName + : null; + + Bundle extras = new Bundle(); + extras.putInt(Intent.EXTRA_UID, request.getAppId()); + if (isUpdate) { + extras.putBoolean(Intent.EXTRA_REPLACING, true); + } + if (isArchived) { + extras.putBoolean(Intent.EXTRA_ARCHIVAL, true); + } + extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, request.getDataLoaderType()); + + final String staticSharedLibraryName = request.getPkg().getStaticSharedLibraryName(); + // If a package is a static shared library, then only the installer of the package + // should get the broadcast. + if (installerPackageName != null && staticSharedLibraryName != null) { + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName, + extras, 0 /*flags*/, + installerPackageName, null /*finishedReceiver*/, + request.getNewUsers(), null /* instantUserIds*/, + null /* broadcastAllowList */, null); + } + + // Send installed broadcasts if the package is not a static shared lib. + if (staticSharedLibraryName == null) { + // Send PACKAGE_ADDED broadcast for users that see the package for the first time + // sendPackageAddedForNewUsers also deals with system apps + final int appId = UserHandle.getAppId(request.getAppId()); + final boolean isSystem = request.isInstallSystem(); + final boolean isVirtualPreload = + ((request.getInstallFlags() & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0); + sendPackageAddedForNewUsers(snapshot, packageName, + isSystem || isVirtualPreload, + isVirtualPreload /*startReceiver*/, appId, + firstUserIds, firstInstantUserIds, isArchived, request.getDataLoaderType()); + + // Send PACKAGE_ADDED broadcast for users that don't see + // the package for the first time + + // Send to all running apps. + final SparseArray<int[]> newBroadcastAllowList = + mAppsFilter.getVisibilityAllowList(snapshot, + snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID), + updateUserIds, snapshot.getPackageStates()); + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName, + extras, 0 /*flags*/, + null /*targetPackage*/, null /*finishedReceiver*/, + updateUserIds, instantUserIds, newBroadcastAllowList, null); + // Send to the installer, even if it's not running. + if (installerPackageName != null) { + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName, + extras, 0 /*flags*/, + installerPackageName, null /*finishedReceiver*/, + updateUserIds, instantUserIds, null /* broadcastAllowList */, null); + } + // Send to PermissionController for all update users, even if it may not be running + // for some users + if (isPrivacySafetyLabelChangeNotificationsEnabled(mContext)) { + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName, + extras, 0 /*flags*/, + requiredPermissionControllerPackage, null /*finishedReceiver*/, + updateUserIds, instantUserIds, null /* broadcastAllowList */, null); + } + // Notify required verifier(s) that are not the installer of record for the package. + for (String verifierPackageName : requiredVerifierPackages) { + if (verifierPackageName != null && !verifierPackageName.equals( + installerPackageName)) { + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, + packageName, + extras, 0 /*flags*/, + verifierPackageName, null /*finishedReceiver*/, + updateUserIds, instantUserIds, null /* broadcastAllowList */, + null); + } + } + // If package installer is defined, notify package installer about new + // app installed + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, packageName, + extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/, + requiredInstallerPackage, null /*finishedReceiver*/, + firstUserIds, instantUserIds, null /* broadcastAllowList */, null); + + // Send replaced for users that don't see the package for the first time + if (isUpdate) { + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED, + packageName, extras, 0 /*flags*/, + null /*targetPackage*/, null /*finishedReceiver*/, + updateUserIds, instantUserIds, + request.getRemovedInfo().mBroadcastAllowList, null); + if (installerPackageName != null) { + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED, packageName, + extras, 0 /*flags*/, + installerPackageName, null /*finishedReceiver*/, + updateUserIds, instantUserIds, null /*broadcastAllowList*/, + null); + } + for (String verifierPackageName : requiredVerifierPackages) { + if (verifierPackageName != null && !verifierPackageName.equals( + installerPackageName)) { + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED, + packageName, extras, 0 /*flags*/, verifierPackageName, + null /*finishedReceiver*/, updateUserIds, instantUserIds, + null /*broadcastAllowList*/, null); + } + } + sendPackageBroadcastAndNotify(Intent.ACTION_MY_PACKAGE_REPLACED, + null /*package*/, null /*extras*/, 0 /*flags*/, + packageName /*targetPackage*/, + null /*finishedReceiver*/, updateUserIds, instantUserIds, + null /*broadcastAllowList*/, + getTemporaryAppAllowlistBroadcastOptions( + REASON_PACKAGE_REPLACED).toBundle()); + } else if (isLaunchedForRestore && !request.isInstallSystem()) { + // First-install and we did a restore, so we're responsible for the + // first-launch broadcast. + if (DEBUG_BACKUP) { + Slog.i(TAG, "Post-restore of " + packageName + + " sending FIRST_LAUNCH in " + Arrays.toString(firstUserIds)); + } + sendFirstLaunchBroadcast(packageName, installerPackageName, + firstUserIds, firstInstantUserIds); + } + + // Send broadcast package appeared if external for all users + if (request.getPkg().isExternalStorage()) { + if (!isUpdate) { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + VolumeInfo volume = + storage.findVolumeByUuid( + StorageManager.convert( + request.getPkg().getVolumeUuid()).toString()); + int packageExternalStorageType = + PackageManagerServiceUtils.getPackageExternalStorageType(volume, + /* isExternalStorage */ true); + // If the package was installed externally, log it. + if (packageExternalStorageType != StorageEnums.UNKNOWN) { + FrameworkStatsLog.write( + FrameworkStatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED, + packageExternalStorageType, packageName); + } + } + if (DEBUG_INSTALL) { + Slog.i(TAG, "upgrading pkg " + packageName + " is external"); + } + if (!isArchived) { + final String[] pkgNames = new String[]{packageName}; + final int[] uids = new int[]{request.getPkg().getUid()}; + sendResourcesChangedBroadcast(snapshot, + true /* mediaStatus */, true /* replacing */, pkgNames, uids); + notifyResourcesChanged(true /* mediaStatus */, + true /* replacing */, pkgNames, uids); + } + } + } else { // if static shared lib + final ArrayList<AndroidPackage> libraryConsumers = request.getLibraryConsumers(); + if (!ArrayUtils.isEmpty(libraryConsumers)) { + // No need to kill consumers if it's installation of new version static shared lib. + final boolean dontKillApp = !isUpdate; + for (int i = 0; i < libraryConsumers.size(); i++) { + AndroidPackage pkg = libraryConsumers.get(i); + // send broadcast that all consumers of the static shared library have changed + sendPackageChangedBroadcast(snapshot, pkg.getPackageName(), + dontKillApp, + new ArrayList<>(Collections.singletonList(pkg.getPackageName())), + pkg.getUid(), null); + } + } } } - public void sendPackageAddedForNewUsers(String packageName, @AppIdInt int appId, int[] userIds, - int[] instantUserIds, boolean isArchived, int dataLoaderType, - SparseArray<int[]> broadcastAllowlist) { + private void sendPackageAddedForNewUsers(@NonNull Computer snapshot, + @NonNull String packageName, + boolean sendBootCompleted, + boolean includeStopped, + @AppIdInt int appId, + int[] userIds, + int[] instantUserIds, + boolean isArchived, + int dataLoaderType) { + if (ArrayUtils.isEmpty(userIds) && ArrayUtils.isEmpty(instantUserIds)) { + return; + } + SparseArray<int[]> broadcastAllowList = mAppsFilter.getVisibilityAllowList(snapshot, + snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID), + userIds, snapshot.getPackageStates()); + mHandler.post( + () -> sendPackageAddedForNewUsers(packageName, appId, userIds, + instantUserIds, isArchived, dataLoaderType, broadcastAllowList)); + mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(packageName, appId, userIds, + instantUserIds, isArchived, dataLoaderType, broadcastAllowList, mHandler); + if (sendBootCompleted && !ArrayUtils.isEmpty(userIds)) { + mHandler.post(() -> { + for (int userId : userIds) { + sendBootCompletedBroadcastToSystemApp( + packageName, includeStopped, userId); + } + } + ); + } + } + + private void sendPackageAddedForNewUsers(@NonNull String packageName, + @AppIdInt int appId, + int[] userIds, + int[] instantUserIds, + boolean isArchived, + int dataLoaderType, + @NonNull SparseArray<int[]> broadcastAllowlist) { Bundle extras = new Bundle(1); // Set to UID of the first user, EXTRA_UID is automatically updated in sendPackageBroadcast final int uid = UserHandle.getUid( @@ -349,7 +643,30 @@ public final class BroadcastHelper { } } - public void sendFirstLaunchBroadcast(String pkgName, String installerPkg, + void sendPackageAddedForUser(@NonNull Computer snapshot, + @NonNull String packageName, + @NonNull PackageStateInternal packageState, + int userId, + boolean isArchived, + int dataLoaderType, + @Nullable String appPredictionServicePackage) { + final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); + final boolean isSystem = packageState.isSystem(); + final boolean isInstantApp = userState.isInstantApp(); + final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId }; + final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY; + sendPackageAddedForNewUsers(snapshot, packageName, isSystem /*sendBootCompleted*/, + false /*startReceiver*/, packageState.getAppId(), userIds, instantUserIds, + isArchived, dataLoaderType); + + // Send a session commit broadcast + final PackageInstaller.SessionInfo info = new PackageInstaller.SessionInfo(); + info.installReason = userState.getInstallReason(); + info.appPackageName = packageName; + sendSessionCommitBroadcast(snapshot, info, userId, appPredictionServicePackage); + } + + void sendFirstLaunchBroadcast(String pkgName, String installerPkg, int[] userIds, int[] instantUserIds) { sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0, installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */, @@ -366,7 +683,7 @@ public final class BroadcastHelper { * access all the packages in the extras. */ @Nullable - public static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid, + private static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid, @NonNull Bundle extras) { if (UserHandle.isCore(callingUid)) { // see all @@ -392,7 +709,7 @@ public final class BroadcastHelper { } /** Returns whether the Safety Label Change notification, a privacy feature, is enabled. */ - public static boolean isPrivacySafetyLabelChangeNotificationsEnabled(Context context) { + private static boolean isPrivacySafetyLabelChangeNotificationsEnabled(Context context) { PackageManager packageManager = context.getPackageManager(); return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, true) @@ -424,4 +741,323 @@ public final class BroadcastHelper { pkgList.size() > 0 ? pkgList.toArray(new String[pkgList.size()]) : null, uidList != null && uidList.size() > 0 ? uidList.toArray() : null); } + + void sendApplicationHiddenForUser(@NonNull String packageName, + @NonNull PackageStateInternal packageState, + int userId, + @NonNull PackageSender packageSender) { + final PackageRemovedInfo info = new PackageRemovedInfo(); + info.mRemovedPackage = packageName; + info.mInstallerPackageName = packageState.getInstallSource().mInstallerPackageName; + info.mRemovedUsers = new int[] {userId}; + info.mBroadcastUsers = new int[] {userId}; + info.mUid = UserHandle.getUid(userId, packageState.getAppId()); + info.mRemovedPackageVersionCode = packageState.getVersionCode(); + sendPackageRemovedBroadcasts(info, packageSender, true /*killApp*/, + false /*removedBySystem*/, false /*isArchived*/); + } + + void sendPackageChangedBroadcast(@NonNull Computer snapshot, + @NonNull String packageName, + boolean dontKillApp, + @NonNull ArrayList<String> componentNames, + int packageUid, + @NonNull String reason) { + PackageStateInternal setting = snapshot.getPackageStateInternal(packageName, + Process.SYSTEM_UID); + if (setting == null) { + return; + } + final int userId = UserHandle.getUserId(packageUid); + final boolean isInstantApp = + snapshot.isInstantAppInternal(packageName, userId, Process.SYSTEM_UID); + final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId }; + final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY; + final SparseArray<int[]> broadcastAllowList = + isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds); + mHandler.post(() -> sendPackageChangedBroadcast( + packageName, dontKillApp, componentNames, packageUid, reason, userIds, + instantUserIds, broadcastAllowList)); + mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames, + packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler); + } + + private void sendPackageBroadcastAndNotify(@NonNull String action, + @NonNull String pkg, + @NonNull Bundle extras, + int flags, + @Nullable String targetPkg, + @Nullable IIntentReceiver finishedReceiver, + @NonNull int[] userIds, + @NonNull int[] instantUserIds, + @Nullable SparseArray<int[]> broadcastAllowList, + @Nullable Bundle bOptions) { + mHandler.post(() -> sendPackageBroadcast(action, pkg, extras, flags, + targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList, + null /* filterExtrasForReceiver */, bOptions)); + if (targetPkg == null) { + // For some broadcast action, e.g. ACTION_PACKAGE_ADDED, this method will be called + // many times to different targets, e.g. installer app, permission controller, other + // registered apps. We should filter it to avoid calling back many times for the same + // action. When the targetPkg is set, it sends the broadcast to specific app, e.g. + // installer app or null for registered apps. The callback only need to send back to the + // registered apps so we check the null condition here. + notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList); + } + } + + void sendSystemPackageUpdatedBroadcasts(@NonNull PackageRemovedInfo packageRemovedInfo) { + if (!packageRemovedInfo.mIsRemovedPackageSystemUpdate) { + return; + } + + final String removedPackage = packageRemovedInfo.mRemovedPackage; + final int removedAppId = packageRemovedInfo.mRemovedAppId; + final int uid = packageRemovedInfo.mUid; + final String installerPackageName = packageRemovedInfo.mInstallerPackageName; + final SparseArray<int[]> broadcastAllowList = packageRemovedInfo.mBroadcastAllowList; + + Bundle extras = new Bundle(2); + extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid); + extras.putBoolean(Intent.EXTRA_REPLACING, true); + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, removedPackage, extras, + 0, null /*targetPackage*/, null, null, null, broadcastAllowList, null); + + if (installerPackageName != null) { + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, + removedPackage, extras, 0 /*flags*/, + installerPackageName, null, null, null, null /* broadcastAllowList */, + null); + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED, + removedPackage, extras, 0 /*flags*/, + installerPackageName, null, null, null, null /* broadcastAllowList */, + null); + } + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REPLACED, removedPackage, + extras, 0, null /*targetPackage*/, null, null, null, broadcastAllowList, null); + sendPackageBroadcastAndNotify(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0, + removedPackage, null, null, null, null /* broadcastAllowList */, + getTemporaryBroadcastOptionsForSystemPackageUpdate(REASON_PACKAGE_REPLACED) + .toBundle()); + } + + @SuppressLint("AndroidFrameworkRequiresPermission") + private @NonNull BroadcastOptions getTemporaryBroadcastOptionsForSystemPackageUpdate( + @PowerExemptionManager.ReasonCode int reasonCode) { + long duration = 10_000; + if (mAmInternal != null) { + duration = mAmInternal.getBootTimeTempAllowListDuration(); + } + final BroadcastOptions bOptions = BroadcastOptions.makeBasic(); + bOptions.setTemporaryAppAllowlist(duration, + TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + reasonCode, ""); + return bOptions; + } + + + void sendPackageRemovedBroadcasts( + @NonNull PackageRemovedInfo packageRemovedInfo, + @NonNull PackageSender packageSender, + boolean killApp, + boolean removedBySystem, + boolean isArchived) { + final String removedPackage = packageRemovedInfo.mRemovedPackage; + final int removedAppId = packageRemovedInfo.mRemovedAppId; + final int uid = packageRemovedInfo.mUid; + final String installerPackageName = packageRemovedInfo.mInstallerPackageName; + final int[] broadcastUserIds = packageRemovedInfo.mBroadcastUsers; + final int[] instantUserIds = packageRemovedInfo.mInstantUserIds; + final SparseArray<int[]> broadcastAllowList = packageRemovedInfo.mBroadcastAllowList; + final boolean dataRemoved = packageRemovedInfo.mDataRemoved; + final boolean isUpdate = packageRemovedInfo.mIsUpdate; + final boolean isRemovedPackageSystemUpdate = + packageRemovedInfo.mIsRemovedPackageSystemUpdate; + final boolean isRemovedForAllUsers = packageRemovedInfo.mRemovedForAllUsers; + final boolean isStaticSharedLib = packageRemovedInfo.mIsStaticSharedLib; + + Bundle extras = new Bundle(); + final int removedUid = removedAppId >= 0 ? removedAppId : uid; + extras.putInt(Intent.EXTRA_UID, removedUid); + extras.putBoolean(Intent.EXTRA_DATA_REMOVED, dataRemoved); + extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, isRemovedPackageSystemUpdate); + extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp); + extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem); + final boolean isReplace = isUpdate || isRemovedPackageSystemUpdate; + if (isReplace || isArchived) { + extras.putBoolean(Intent.EXTRA_REPLACING, true); + } + if (isArchived) { + extras.putBoolean(Intent.EXTRA_ARCHIVAL, true); + } + extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, isRemovedForAllUsers); + + // Send PACKAGE_REMOVED broadcast to the respective installer. + if (removedPackage != null && installerPackageName != null) { + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REMOVED, + removedPackage, extras, 0 /*flags*/, + installerPackageName, null, broadcastUserIds, instantUserIds, null, null); + } + if (isStaticSharedLib) { + // When uninstalling static shared libraries, only the package's installer needs to be + // sent a PACKAGE_REMOVED broadcast. There are no other intended recipients. + return; + } + if (removedPackage != null) { + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REMOVED, + removedPackage, extras, 0, null /*targetPackage*/, null, + broadcastUserIds, instantUserIds, broadcastAllowList, null); + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_REMOVED_INTERNAL, + removedPackage, extras, 0 /*flags*/, PLATFORM_PACKAGE_NAME, + null /*finishedReceiver*/, broadcastUserIds, instantUserIds, + broadcastAllowList, null /*bOptions*/); + if (dataRemoved && !isRemovedPackageSystemUpdate) { + sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_FULLY_REMOVED, + removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null, + null, broadcastUserIds, instantUserIds, broadcastAllowList, null); + packageSender.notifyPackageRemoved(removedPackage, removedUid); + } + } + if (removedAppId >= 0) { + // If a system app's updates are uninstalled the UID is not actually removed. Some + // services need to know the package name affected. + if (isReplace) { + extras.putString(Intent.EXTRA_PACKAGE_NAME, removedPackage); + } + + sendPackageBroadcastAndNotify(Intent.ACTION_UID_REMOVED, + null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, + null, null, broadcastUserIds, instantUserIds, broadcastAllowList, null); + } + } + + /** + * Send broadcast intents for packages suspension changes. + * + * @param intent The action name of the suspension intent. + * @param pkgList The names of packages which have suspension changes. + * @param uidList The uids of packages which have suspension changes. + * @param userId The user where packages reside. + */ + void sendPackagesSuspendedOrUnsuspendedForUser(@NonNull Computer snapshot, + @NonNull String intent, + @NonNull String[] pkgList, + @NonNull int[] uidList, + boolean quarantined, + int userId) { + final Bundle extras = new Bundle(3); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList); + if (quarantined) { + extras.putBoolean(Intent.EXTRA_QUARANTINED, true); + } + final int flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND; + final Bundle options = new BroadcastOptions() + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) + .toBundle(); + mHandler.post(() -> sendPackageBroadcast(intent, null /* pkg */, + extras, flags, null /* targetPkg */, null /* finishedReceiver */, + new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */, + (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList( + snapshot, callingUid, intentExtras), + options)); + notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId}, + null /* instantUserIds */, null /* broadcastAllowList */); + } + + void sendMyPackageSuspendedOrUnsuspended(@NonNull Computer snapshot, + @NonNull String[] affectedPackages, + boolean suspended, + int userId) { + final String action = suspended + ? Intent.ACTION_MY_PACKAGE_SUSPENDED + : Intent.ACTION_MY_PACKAGE_UNSUSPENDED; + mHandler.post(() -> { + final IActivityManager am = ActivityManager.getService(); + if (am == null) { + Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ " + + (suspended ? "" : "UN") + "SUSPENDED broadcasts"); + return; + } + final int[] targetUserIds = new int[] {userId}; + for (String packageName : affectedPackages) { + final Bundle appExtras = suspended + ? SuspendPackageHelper.getSuspendedPackageAppExtras( + snapshot, packageName, userId, SYSTEM_UID) + : null; + final Bundle intentExtras; + if (appExtras != null) { + intentExtras = new Bundle(1); + intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras); + } else { + intentExtras = null; + } + doSendBroadcast(action, null, intentExtras, + Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null, + targetUserIds, false, null, null, null); + } + }); + } + + /** + * Send broadcast intents for packages distracting changes. + * + * @param pkgList The names of packages which have suspension changes. + * @param uidList The uids of packages which have suspension changes. + * @param userId The user where packages reside. + */ + void sendDistractingPackagesChanged(@NonNull Computer snapshot, + @NonNull String[] pkgList, + @NonNull int[] uidList, + int userId, + int distractionFlags) { + final Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList); + extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags); + + mHandler.post(() -> sendPackageBroadcast( + Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */, + extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */, + null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */, + null /* broadcastAllowList */, + (callingUid, intentExtras) -> filterExtrasChangedPackageList( + snapshot, callingUid, intentExtras), + null /* bOptions */)); + } + + void sendResourcesChangedBroadcastAndNotify(@NonNull Computer snapshot, + boolean mediaStatus, + boolean replacing, + @NonNull ArrayList<AndroidPackage> packages) { + final int size = packages.size(); + final String[] packageNames = new String[size]; + final int[] packageUids = new int[size]; + for (int i = 0; i < size; i++) { + final AndroidPackage pkg = packages.get(i); + packageNames[i] = pkg.getPackageName(); + packageUids[i] = pkg.getUid(); + } + sendResourcesChangedBroadcast(snapshot, mediaStatus, + replacing, packageNames, packageUids); + notifyResourcesChanged(mediaStatus, replacing, packageNames, packageUids); + } + + private void notifyPackageMonitor(@NonNull String action, + @NonNull String pkg, + @Nullable Bundle extras, + @NonNull int[] userIds, + @NonNull int[] instantUserIds, + @Nullable SparseArray<int[]> broadcastAllowList) { + mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds, + instantUserIds, broadcastAllowList, mHandler); + } + + private void notifyResourcesChanged(boolean mediaStatus, + boolean replacing, + @NonNull String[] pkgNames, + @NonNull int[] uids) { + mPackageMonitorCallbackHelper.notifyResourcesChanged(mediaStatus, replacing, pkgNames, + uids, mHandler); + } } diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 6baa889d61ae..5d2944e17943 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -580,7 +580,7 @@ public class ComputerEngine implements Computer { list.add(ri); PackageManagerServiceUtils.applyEnforceIntentFilterMatching( mInjector.getCompatibility(), mComponentResolver, - list, false, intent, resolvedType, filterCallingUid); + list, false, intent, resolvedType, flags, filterCallingUid); } } } else { @@ -610,7 +610,7 @@ public class ComputerEngine implements Computer { // We also have to ensure all components match the original intent PackageManagerServiceUtils.applyEnforceIntentFilterMatching( mInjector.getCompatibility(), mComponentResolver, - list, false, originalIntent, resolvedType, filterCallingUid); + list, false, originalIntent, resolvedType, flags, filterCallingUid); } return skipPostResolution ? list : applyPostResolutionFilter( @@ -699,7 +699,7 @@ public class ComputerEngine implements Computer { list.add(ri); PackageManagerServiceUtils.applyEnforceIntentFilterMatching( mInjector.getCompatibility(), mComponentResolver, - list, false, intent, resolvedType, callingUid); + list, false, intent, resolvedType, flags, callingUid); } } } else { @@ -711,7 +711,7 @@ public class ComputerEngine implements Computer { // We also have to ensure all components match the original intent PackageManagerServiceUtils.applyEnforceIntentFilterMatching( mInjector.getCompatibility(), mComponentResolver, - list, false, originalIntent, resolvedType, callingUid); + list, false, originalIntent, resolvedType, flags, callingUid); } return list; diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 83f90a1c1b3c..8e767e74fc9b 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -63,7 +63,6 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; -import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.ArchiveState; import com.android.server.pm.pkg.PackageStateInternal; @@ -87,19 +86,16 @@ final class DeletePackageHelper { private final PackageManagerService mPm; private final UserManagerInternal mUserManagerInternal; - private final PermissionManagerServiceInternal mPermissionManager; private final RemovePackageHelper mRemovePackageHelper; + private final BroadcastHelper mBroadcastHelper; // TODO(b/198166813): remove PMS dependency - DeletePackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper) { + DeletePackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper, + BroadcastHelper broadcastHelper) { mPm = pm; mUserManagerInternal = mPm.mInjector.getUserManagerInternal(); - mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal(); mRemovePackageHelper = removePackageHelper; - } - - DeletePackageHelper(PackageManagerService pm) { - this(pm, new RemovePackageHelper(pm)); + mBroadcastHelper = broadcastHelper; } /** @@ -121,7 +117,7 @@ final class DeletePackageHelper { */ public int deletePackageX(String packageName, long versionCode, int userId, int deleteFlags, boolean removedBySystem) { - final PackageRemovedInfo info = new PackageRemovedInfo(mPm); + final PackageRemovedInfo info = new PackageRemovedInfo(); final boolean res; final int removeUser = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0 @@ -251,8 +247,9 @@ final class DeletePackageHelper { if (res) { final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0; final boolean isArchived = (deleteFlags & PackageManager.DELETE_ARCHIVE) != 0; - info.sendPackageRemovedBroadcasts(killApp, removedBySystem, isArchived); - info.sendSystemPackageUpdatedBroadcasts(); + mBroadcastHelper.sendPackageRemovedBroadcasts(info, mPm, killApp, + removedBySystem, isArchived); + mBroadcastHelper.sendSystemPackageUpdatedBroadcasts(info); PackageMetrics.onUninstallSucceeded(info, deleteFlags, removeUser); } @@ -314,7 +311,7 @@ final class DeletePackageHelper { Slog.i(TAG, "Enabling system stub after removal; pkg: " + stubPkg.getPackageName()); } - new InstallPackageHelper(mPm).enableCompressedPackage(stubPkg, stubPs); + mPm.enableCompressedPackage(stubPkg, stubPs); } else if (DEBUG_COMPRESSION) { Slog.i(TAG, "System stub disabled for all users, leaving uncompressed " + "after removal; pkg: " + stubPkg.getPackageName()); @@ -491,8 +488,7 @@ final class DeletePackageHelper { // When an updated system application is deleted we delete the existing resources // as well and fall back to existing code in system partition deleteInstalledSystemPackage(action, allUserHandles, writeSettings); - new InstallPackageHelper(mPm).restoreDisabledSystemPackageLIF( - action, allUserHandles, writeSettings); + mPm.restoreDisabledSystemPackageLIF(action, allUserHandles, writeSettings); } else { if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.getPackageName()); if (ps.isIncremental()) { diff --git a/services/core/java/com/android/server/pm/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java index 8ebb6eaccd1a..c5ec73b4e2b8 100644 --- a/services/core/java/com/android/server/pm/DistractingPackageHelper.java +++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java @@ -19,10 +19,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.RESTRICTION_NONE; import android.annotation.NonNull; -import android.content.Intent; import android.content.pm.PackageManager.DistractionRestriction; -import android.os.Bundle; -import android.os.Handler; import android.os.UserHandle; import android.util.ArraySet; import android.util.IntArray; @@ -42,17 +39,16 @@ public final class DistractingPackageHelper { // TODO(b/198166813): remove PMS dependency private final PackageManagerService mPm; - private final PackageManagerServiceInjector mInjector; private final BroadcastHelper mBroadcastHelper; private final SuspendPackageHelper mSuspendPackageHelper; /** * Constructor for {@link PackageManagerService}. */ - DistractingPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector, - BroadcastHelper broadcastHelper, SuspendPackageHelper suspendPackageHelper) { + DistractingPackageHelper(PackageManagerService pm, + BroadcastHelper broadcastHelper, + SuspendPackageHelper suspendPackageHelper) { mPm = pm; - mInjector = injector; mBroadcastHelper = broadcastHelper; mSuspendPackageHelper = suspendPackageHelper; } @@ -127,8 +123,8 @@ public final class DistractingPackageHelper { if (!changedPackagesList.isEmpty()) { final String[] changedPackages = changedPackagesList.toArray( new String[changedPackagesList.size()]); - sendDistractingPackagesChanged(changedPackages, changedUids.toArray(), userId, - restrictionFlags); + mBroadcastHelper.sendDistractingPackagesChanged(mPm.snapshotComputer(), + changedPackages, changedUids.toArray(), userId, restrictionFlags); mPm.scheduleWritePackageRestrictions(userId); } return unactionedPackages.toArray(new String[0]); @@ -202,34 +198,9 @@ public final class DistractingPackageHelper { if (!changedPackages.isEmpty()) { final String[] packageArray = changedPackages.toArray( new String[changedPackages.size()]); - sendDistractingPackagesChanged(packageArray, changedUids.toArray(), userId, - RESTRICTION_NONE); + mBroadcastHelper.sendDistractingPackagesChanged(mPm.snapshotComputer(), + packageArray, changedUids.toArray(), userId, RESTRICTION_NONE); mPm.scheduleWritePackageRestrictions(userId); } } - - /** - * Send broadcast intents for packages distracting changes. - * - * @param pkgList The names of packages which have suspension changes. - * @param uidList The uids of packages which have suspension changes. - * @param userId The user where packages reside. - */ - void sendDistractingPackagesChanged(@NonNull String[] pkgList, int[] uidList, int userId, - int distractionFlags) { - final Bundle extras = new Bundle(); - extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList); - extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList); - extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags); - - final Handler handler = mInjector.getHandler(); - handler.post(() -> mBroadcastHelper.sendPackageBroadcast( - Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */, - extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */, - null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */, - null /* broadcastAllowList */, - (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList( - mPm.snapshotComputer(), callingUid, intentExtras), - null /* bOptions */)); - } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 486b1ad3c9ec..8f71a9be4fe5 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -38,7 +38,6 @@ import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN; import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile; -import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.os.incremental.IncrementalManager.isIncrementalPath; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; @@ -50,7 +49,6 @@ import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet; import static com.android.server.pm.InstructionSets.getPreferredInstructionSet; import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME; -import static com.android.server.pm.PackageManagerService.DEBUG_BACKUP; import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION; import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING; @@ -130,7 +128,6 @@ import android.content.pm.parsing.result.ParseTypeImpl; import android.net.Uri; import android.os.Binder; import android.os.Build; -import android.os.Bundle; import android.os.Environment; import android.os.FileUtils; import android.os.Message; @@ -143,9 +140,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.incremental.IncrementalManager; import android.os.incremental.IncrementalStorage; -import android.os.storage.StorageManager; -import android.os.storage.VolumeInfo; -import android.stats.storage.StorageEnums; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; @@ -163,7 +157,6 @@ import com.android.internal.content.F2fsUtils; import com.android.internal.security.VerityUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; -import com.android.internal.util.FrameworkStatsLog; import com.android.server.EventLogTags; import com.android.server.LocalManagerRegistry; import com.android.server.SystemConfig; @@ -220,6 +213,7 @@ final class InstallPackageHelper { private final AppDataHelper mAppDataHelper; private final BroadcastHelper mBroadcastHelper; private final RemovePackageHelper mRemovePackageHelper; + private final DeletePackageHelper mDeletePackageHelper; private final IncrementalManager mIncrementalManager; private final ApexManager mApexManager; private final DexManager mDexManager; @@ -233,12 +227,17 @@ final class InstallPackageHelper { private final UpdateOwnershipHelper mUpdateOwnershipHelper; // TODO(b/198166813): remove PMS dependency - InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) { + InstallPackageHelper(PackageManagerService pm, + AppDataHelper appDataHelper, + RemovePackageHelper removePackageHelper, + DeletePackageHelper deletePackageHelper, + BroadcastHelper broadcastHelper) { mPm = pm; mInjector = pm.mInjector; mAppDataHelper = appDataHelper; - mBroadcastHelper = new BroadcastHelper(pm.mInjector); - mRemovePackageHelper = new RemovePackageHelper(pm); + mBroadcastHelper = broadcastHelper; + mRemovePackageHelper = removePackageHelper; + mDeletePackageHelper = deletePackageHelper; mIncrementalManager = pm.mInjector.getIncrementalManager(); mApexManager = pm.mInjector.getApexManager(); mDexManager = pm.mInjector.getDexManager(); @@ -251,10 +250,6 @@ final class InstallPackageHelper { mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper(); } - InstallPackageHelper(PackageManagerService pm) { - this(pm, new AppDataHelper(pm)); - } - /** * Commits the package scan and modifies system state. * <p><em>WARNING:</em> The method may throw an exception in the middle @@ -263,7 +258,7 @@ final class InstallPackageHelper { * possible and the system is not left in an inconsistent state. */ @GuardedBy("mPm.mLock") - public AndroidPackage commitReconciledScanResultLocked( + private AndroidPackage commitReconciledScanResultLocked( @NonNull ReconciledPackage reconciledPkg, int[] allUsers) { final InstallRequest request = reconciledPkg.mInstallRequest; // TODO(b/135203078): Move this even further away @@ -731,8 +726,9 @@ final class InstallPackageHelper { } // TODO(b/278553670) Store archive state for the user. boolean isArchived = (pkgSetting.getPkg() == null); - mPm.sendPackageAddedForUser(mPm.snapshotComputer(), packageName, pkgSetting, userId, - isArchived, DataLoaderType.NONE); + mBroadcastHelper.sendPackageAddedForUser(mPm.snapshotComputer(), packageName, + pkgSetting, userId, isArchived, DataLoaderType.NONE, + mPm.mAppPredictionServicePackage); synchronized (mPm.mLock) { mPm.updateSequenceNumberLP(pkgSetting, new int[]{ userId }); } @@ -998,8 +994,7 @@ final class InstallPackageHelper { return; } final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0; - final boolean isSdkLibrary = packageToScan.isSdkLibrary(); - if (!isApex && !isSdkLibrary) { + if (!isApex) { createdAppId.put(packageName, optimisticallyRegisterAppId(request)); } else { request.getScannedPackageSetting().setAppId(Process.INVALID_UID); @@ -1746,7 +1741,7 @@ final class InstallPackageHelper { } // Update what is removed - PackageRemovedInfo removedInfo = new PackageRemovedInfo(mPm); + PackageRemovedInfo removedInfo = new PackageRemovedInfo(); removedInfo.mUid = ps.getAppId(); removedInfo.mRemovedPackage = ps.getPackageName(); removedInfo.mInstallerPackageName = @@ -2075,8 +2070,6 @@ final class InstallPackageHelper { final InstallRequest installRequest = reconciledPkg.mInstallRequest; final ParsedPackage parsedPackage = installRequest.getParsedPackage(); final String packageName = parsedPackage.getPackageName(); - final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm); - final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm); installRequest.onCommitStarted(); if (installRequest.isInstallReplace()) { @@ -2098,7 +2091,7 @@ final class InstallPackageHelper { allUsers, mPm.mSettings.getPackagesLocked()); if (installRequest.isInstallSystem()) { // Remove existing system package - removePackageHelper.removePackage(oldPackage, true); + mRemovePackageHelper.removePackage(oldPackage, true); if (!disableSystemPackageLPw(oldPackage)) { // We didn't need to disable the .apk as a current system package, // which means we are replacing another update that is already @@ -2114,7 +2107,7 @@ final class InstallPackageHelper { } else { try { // Settings will be written during the call to updateSettingsLI(). - deletePackageHelper.executeDeletePackage( + mDeletePackageHelper.executeDeletePackage( reconciledPkg.mDeletePackageAction, packageName, true, allUsers, false); } catch (SystemDeleteException e) { @@ -2201,12 +2194,19 @@ final class InstallPackageHelper { final String installerPackageName = installRequest.getInstallerPackageName(); if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath()); + final int userId = installRequest.getUserId(); + if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT + && !mPm.mUserManager.exists(userId)) { + installRequest.setError(PackageManagerException.ofInternalError( + "User " + userId + " doesn't exist or has been removed", + PackageManagerException.INTERNAL_ERROR_MISSING_USER)); + return; + } synchronized (mPm.mLock) { // For system-bundled packages, we assume that installing an upgraded version // of the package implies that the user actually wants to run that new code, // so we enable the package. final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName); - final int userId = installRequest.getUserId(); if (ps != null) { if (ps.isSystem()) { if (DEBUG_INSTALL) { @@ -2797,24 +2797,21 @@ final class InstallPackageHelper { final Computer snapshot = mPm.snapshotComputer(); // Send broadcasts for (int i = 0; i < numBroadcasts; i++) { - mPm.sendPackageChangedBroadcast(snapshot, packages[i], true /* dontKillApp */, - components[i], uids[i], null /* reason */); + mBroadcastHelper.sendPackageChangedBroadcast(snapshot, packages[i], + true /* dontKillApp */, components[i], uids[i], null /* reason */); } } void handlePackagePostInstall(InstallRequest request, boolean launchedForRestore) { final boolean killApp = (request.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) == 0; - final boolean virtualPreload = - ((request.getInstallFlags() & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0); - final String installerPackage = request.getInstallerPackageName(); - final int dataLoaderType = request.getDataLoaderType(); final boolean succeeded = request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED; final boolean update = request.isUpdate(); final boolean archived = request.isArchived(); final String packageName = request.getName(); + final Computer snapshot = mPm.snapshotComputer(); final PackageStateInternal pkgSetting = - succeeded ? mPm.snapshotComputer().getPackageStateInternal(packageName) : null; + succeeded ? snapshot.getPackageStateInternal(packageName) : null; final boolean removedBeforeUpdate = (pkgSetting == null) || (pkgSetting.isSystem() && !pkgSetting.getPath().getPath().equals( request.getPkg().getPath())); @@ -2835,207 +2832,21 @@ final class InstallPackageHelper { // Clear the uid cache after we installed a new package. mPm.mPerUidReadTimeoutsCache = null; - // Send the removed broadcasts - if (request.getRemovedInfo() != null) { - if (request.getRemovedInfo().mIsExternal) { - if (DEBUG_INSTALL) { - Slog.i(TAG, "upgrading pkg " + request.getRemovedInfo().mRemovedPackage - + " is ASEC-hosted -> UNAVAILABLE"); - } - final String[] pkgNames = new String[]{ - request.getRemovedInfo().mRemovedPackage}; - final int[] uids = new int[]{request.getRemovedInfo().mUid}; - mPm.notifyResourcesChanged(false /* mediaStatus */, - true /* replacing */, pkgNames, uids); - mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, - false /* mediaStatus */, true /* replacing */, pkgNames, uids); - } - request.getRemovedInfo().sendPackageRemovedBroadcasts( - killApp, false /*removedBySystem*/, false /*isArchived*/); - } - - final String installerPackageName = - request.getInstallerPackageName() != null - ? request.getInstallerPackageName() - : request.getRemovedInfo() != null - ? request.getRemovedInfo().mInstallerPackageName - : null; - mPm.notifyInstantAppPackageInstalled(request.getPkg().getPackageName(), request.getNewUsers()); request.populateBroadcastUsers(); final int[] firstUserIds = request.getFirstTimeBroadcastUserIds(); - final int[] firstInstantUserIds = request.getFirstTimeBroadcastInstantUserIds(); - final int[] updateUserIds = request.getUpdateBroadcastUserIds(); - final int[] instantUserIds = request.getUpdateBroadcastInstantUserIds(); - - Bundle extras = new Bundle(); - extras.putInt(Intent.EXTRA_UID, request.getAppId()); - if (update) { - extras.putBoolean(Intent.EXTRA_REPLACING, true); - } - if (archived) { - extras.putBoolean(Intent.EXTRA_ARCHIVAL, true); - } - extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType); - - // If a package is a static shared library, then only the installer of the package - // should get the broadcast. - if (installerPackageName != null - && request.getPkg().getStaticSharedLibraryName() != null) { - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, 0 /*flags*/, - installerPackageName, null /*finishedReceiver*/, - request.getNewUsers(), null /* instantUserIds*/, - null /* broadcastAllowList */, null); - } - // Send installed broadcasts if the package is not a static shared lib. if (request.getPkg().getStaticSharedLibraryName() == null) { mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath()); + } - // Send PACKAGE_ADDED broadcast for users that see the package for the first time - // sendPackageAddedForNewUsers also deals with system apps - int appId = UserHandle.getAppId(request.getAppId()); - boolean isSystem = request.isInstallSystem(); - mPm.sendPackageAddedForNewUsers(mPm.snapshotComputer(), packageName, - isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId, - firstUserIds, firstInstantUserIds, archived, dataLoaderType); + mBroadcastHelper.sendPostInstallBroadcasts(mPm.snapshotComputer(), request, packageName, + mPm.mRequiredPermissionControllerPackage, mPm.mRequiredVerifierPackages, + mPm.mRequiredInstallerPackage, + /* packageSender= */ mPm, launchedForRestore, killApp, update, archived); - // Send PACKAGE_ADDED broadcast for users that don't see - // the package for the first time - - // Send to all running apps. - final SparseArray<int[]> newBroadcastAllowList; - synchronized (mPm.mLock) { - final Computer snapshot = mPm.snapshotComputer(); - newBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(snapshot, - snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID), - updateUserIds, mPm.mSettings.getPackagesLocked()); - } - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, 0 /*flags*/, - null /*targetPackage*/, null /*finishedReceiver*/, - updateUserIds, instantUserIds, newBroadcastAllowList, null); - // Send to the installer, even if it's not running. - if (installerPackageName != null) { - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, 0 /*flags*/, - installerPackageName, null /*finishedReceiver*/, - updateUserIds, instantUserIds, null /* broadcastAllowList */, null); - } - // Send to PermissionController for all update users, even if it may not be running - // for some users - if (BroadcastHelper.isPrivacySafetyLabelChangeNotificationsEnabled(mContext)) { - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, 0 /*flags*/, - mPm.mRequiredPermissionControllerPackage, null /*finishedReceiver*/, - updateUserIds, instantUserIds, null /* broadcastAllowList */, null); - } - // Notify required verifier(s) that are not the installer of record for the package. - for (String verifierPackageName : mPm.mRequiredVerifierPackages) { - if (verifierPackageName != null && !verifierPackageName.equals( - installerPackageName)) { - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, 0 /*flags*/, - verifierPackageName, null /*finishedReceiver*/, - updateUserIds, instantUserIds, null /* broadcastAllowList */, - null); - } - } - // If package installer is defined, notify package installer about new - // app installed - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, - extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND /*flags*/, - mPm.mRequiredInstallerPackage, null /*finishedReceiver*/, - firstUserIds, instantUserIds, null /* broadcastAllowList */, null); - - // Send replaced for users that don't see the package for the first time - if (update) { - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - packageName, extras, 0 /*flags*/, - null /*targetPackage*/, null /*finishedReceiver*/, - updateUserIds, instantUserIds, - request.getRemovedInfo().mBroadcastAllowList, null); - if (installerPackageName != null) { - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, - extras, 0 /*flags*/, - installerPackageName, null /*finishedReceiver*/, - updateUserIds, instantUserIds, null /*broadcastAllowList*/, - null); - } - for (String verifierPackageName : mPm.mRequiredVerifierPackages) { - if (verifierPackageName != null && !verifierPackageName.equals( - installerPackageName)) { - mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - packageName, extras, 0 /*flags*/, verifierPackageName, - null /*finishedReceiver*/, updateUserIds, instantUserIds, - null /*broadcastAllowList*/, null); - } - } - mPm.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, - null /*package*/, null /*extras*/, 0 /*flags*/, - packageName /*targetPackage*/, - null /*finishedReceiver*/, updateUserIds, instantUserIds, - null /*broadcastAllowList*/, - mBroadcastHelper.getTemporaryAppAllowlistBroadcastOptions( - REASON_PACKAGE_REPLACED).toBundle()); - } else if (launchedForRestore && !request.isInstallSystem()) { - // First-install and we did a restore, so we're responsible for the - // first-launch broadcast. - if (DEBUG_BACKUP) { - Slog.i(TAG, "Post-restore of " + packageName - + " sending FIRST_LAUNCH in " + Arrays.toString(firstUserIds)); - } - mBroadcastHelper.sendFirstLaunchBroadcast(packageName, installerPackage, - firstUserIds, firstInstantUserIds); - } - - // Send broadcast package appeared if external for all users - if (request.getPkg().isExternalStorage()) { - if (!update) { - final StorageManager storageManager = - mInjector.getSystemService(StorageManager.class); - VolumeInfo volume = - storageManager.findVolumeByUuid( - StorageManager.convert( - request.getPkg().getVolumeUuid()).toString()); - int packageExternalStorageType = - PackageManagerServiceUtils.getPackageExternalStorageType(volume, - request.getPkg().isExternalStorage()); - // If the package was installed externally, log it. - if (packageExternalStorageType != StorageEnums.UNKNOWN) { - FrameworkStatsLog.write( - FrameworkStatsLog.APP_INSTALL_ON_EXTERNAL_STORAGE_REPORTED, - packageExternalStorageType, packageName); - } - } - if (DEBUG_INSTALL) { - Slog.i(TAG, "upgrading pkg " + request.getPkg() + " is external"); - } - if (!archived) { - final String[] pkgNames = new String[]{packageName}; - final int[] uids = new int[]{request.getPkg().getUid()}; - mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, - true /* mediaStatus */, true /* replacing */, pkgNames, uids); - mPm.notifyResourcesChanged(true /* mediaStatus */, true /* replacing */, - pkgNames, uids); - } - } - } else if (!ArrayUtils.isEmpty(request.getLibraryConsumers())) { // if static shared lib - // No need to kill consumers if it's installation of new version static shared lib. - final Computer snapshot = mPm.snapshotComputer(); - final boolean dontKillApp = !update - && request.getPkg().getStaticSharedLibraryName() != null; - for (int i = 0; i < request.getLibraryConsumers().size(); i++) { - AndroidPackage pkg = request.getLibraryConsumers().get(i); - // send broadcast that all consumers of the static shared library have changed - mPm.sendPackageChangedBroadcast(snapshot, pkg.getPackageName(), dontKillApp, - new ArrayList<>(Collections.singletonList(pkg.getPackageName())), - pkg.getUid(), null); - } - } // Work that needs to happen on first install within each user if (firstUserIds.length > 0) { @@ -3075,7 +2886,6 @@ final class InstallPackageHelper { } if (!archived) { - final Computer snapshot = mPm.snapshotComputer(); // Notify DexManager that the package was installed for new users. // The updated users should already be indexed and the package code paths // should not change. @@ -3091,7 +2901,7 @@ final class InstallPackageHelper { } } else { // Now send PACKAGE_REMOVED + EXTRA_REPLACING broadcast. - final PackageRemovedInfo info = new PackageRemovedInfo(mPm); + final PackageRemovedInfo info = new PackageRemovedInfo(); info.mRemovedPackage = packageName; info.mInstallerPackageName = request.getInstallerPackageName(); info.mRemovedUsers = firstUserIds; @@ -3100,8 +2910,8 @@ final class InstallPackageHelper { info.mRemovedPackageVersionCode = request.getPkg().getLongVersionCode(); info.mRemovedForAllUsers = true; - info.sendPackageRemovedBroadcasts(false /*killApp*/, - false /*removedBySystem*/, true /*isArchived*/); + mBroadcastHelper.sendPackageRemovedBroadcasts(info, mPm, + false /*killApp*/, false /*removedBySystem*/, true /*isArchived*/); } } @@ -3292,15 +3102,14 @@ final class InstallPackageHelper { synchronized (mPm.mLock) { mPm.mSettings.disableSystemPackageLPw(stubPkg.getPackageName(), true /*replaced*/); } - final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm); - removePackageHelper.removePackage(stubPkg, true /*chatty*/); + mRemovePackageHelper.removePackage(stubPkg, true /*chatty*/); try { return initPackageTracedLI(scanFile, parseFlags, scanFlags); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(), e); // Remove the failed install - removePackageHelper.removeCodePath(scanFile); + mRemovePackageHelper.removeCodePath(scanFile); throw e; } } @@ -3343,7 +3152,7 @@ final class InstallPackageHelper { if (!dstCodePath.exists()) { return null; } - new RemovePackageHelper(mPm).removeCodePath(dstCodePath); + mRemovePackageHelper.removeCodePath(dstCodePath); return null; } @@ -4299,8 +4108,8 @@ final class InstallPackageHelper { parsedPackage.getPackageName(), UserHandle.USER_ALL, "scanPackageInternalLI", ApplicationExitInfo.REASON_OTHER, null /* request */)) { - DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm); - deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true, + mDeletePackageHelper.deletePackageLIF( + parsedPackage.getPackageName(), null, true, mPm.mUserManager.getUserIds(), 0, null, false); } } else if (newPkgVersionGreater || newSharedUserSetting) { diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java index fe6a8a1778a2..ca8dc2973404 100644 --- a/services/core/java/com/android/server/pm/InstallingSession.java +++ b/services/core/java/com/android/server/pm/InstallingSession.java @@ -94,8 +94,6 @@ class InstallingSession { private final UserHandle mUser; @NonNull final PackageManagerService mPm; - final InstallPackageHelper mInstallPackageHelper; - final RemovePackageHelper mRemovePackageHelper; final boolean mIsInherit; final int mSessionId; final int mRequireUserAction; @@ -108,8 +106,6 @@ class InstallingSession { PackageLite packageLite, PackageManagerService pm) { mPm = pm; mUser = user; - mInstallPackageHelper = new InstallPackageHelper(mPm); - mRemovePackageHelper = new RemovePackageHelper(mPm); mOriginInfo = originInfo; mMoveInfo = moveInfo; mObserver = observer; @@ -142,8 +138,6 @@ class InstallingSession { PackageLite packageLite, PackageManagerService pm) { mPm = pm; mUser = user; - mInstallPackageHelper = new InstallPackageHelper(mPm); - mRemovePackageHelper = new RemovePackageHelper(mPm); mOriginInfo = OriginInfo.fromStagedFile(stagedDir); mMoveInfo = null; mInstallReason = fixUpInstallReason( @@ -242,7 +236,7 @@ class InstallingSession { // state can change within this delay and hence we need to re-verify certain conditions. boolean isStaged = (mInstallFlags & INSTALL_STAGED) != 0; if (isStaged) { - Pair<Integer, String> ret = mInstallPackageHelper.verifyReplacingVersionCode( + Pair<Integer, String> ret = mPm.verifyReplacingVersionCode( pkgLite, mRequiredInstalledVersionCode, mInstallFlags); mRet = ret.first; if (mRet != INSTALL_SUCCEEDED) { @@ -540,39 +534,39 @@ class InstallingSession { } } } else { - mInstallPackageHelper.installPackagesTraced(installRequests); + mPm.installPackagesTraced(installRequests); for (InstallRequest request : installRequests) { doPostInstall(request); } } for (InstallRequest request : installRequests) { - mInstallPackageHelper.restoreAndPostInstall(request); + mPm.restoreAndPostInstall(request); } } private void doPostInstall(InstallRequest request) { if (mMoveInfo != null) { if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) { - mRemovePackageHelper.cleanUpForMoveInstall(mMoveInfo.mFromUuid, + mPm.cleanUpForMoveInstall(mMoveInfo.mFromUuid, mMoveInfo.mPackageName, mMoveInfo.mFromCodePath); } else { - mRemovePackageHelper.cleanUpForMoveInstall(mMoveInfo.mToUuid, + mPm.cleanUpForMoveInstall(mMoveInfo.mToUuid, mMoveInfo.mPackageName, mMoveInfo.mFromCodePath); } } else { if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) { - mRemovePackageHelper.removeCodePath(request.getCodeFile()); + mPm.removeCodePath(request.getCodeFile()); } } } private void cleanUpForFailedInstall(InstallRequest request) { if (request.isInstallMove()) { - mRemovePackageHelper.cleanUpForMoveInstall(request.getMoveToUuid(), + mPm.cleanUpForMoveInstall(request.getMoveToUuid(), request.getMovePackageName(), request.getMoveFromCodePath()); } else { - mRemovePackageHelper.removeCodePath(request.getCodeFile()); + mPm.removeCodePath(request.getCodeFile()); } } diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java index b4ca477ddfec..4ecbd154cab5 100644 --- a/services/core/java/com/android/server/pm/PackageHandler.java +++ b/services/core/java/com/android/server/pm/PackageHandler.java @@ -60,14 +60,10 @@ import java.io.IOException; */ final class PackageHandler extends Handler { private final PackageManagerService mPm; - private final InstallPackageHelper mInstallPackageHelper; - private final RemovePackageHelper mRemovePackageHelper; PackageHandler(Looper looper, PackageManagerService pm) { super(looper); mPm = pm; - mInstallPackageHelper = new InstallPackageHelper(mPm); - mRemovePackageHelper = new RemovePackageHelper(mPm); } @Override @@ -82,7 +78,7 @@ final class PackageHandler extends Handler { void doHandleMessage(Message msg) { switch (msg.what) { case SEND_PENDING_BROADCAST: { - mInstallPackageHelper.sendPendingBroadcasts(); + mPm.sendPendingBroadcasts(); break; } case POST_INSTALL: { @@ -96,7 +92,7 @@ final class PackageHandler extends Handler { request.onInstallCompleted(); request.runPostInstallRunnable(); if (!request.isInstallExistingForUser()) { - mInstallPackageHelper.handlePackagePostInstall(request, didRestore); + mPm.handlePackagePostInstall(request, didRestore); } else if (DEBUG_INSTALL) { // No post-install when we run restore from installExistingPackageForUser Slog.i(TAG, "Nothing to do for post-install token " + msg.arg1); @@ -107,7 +103,7 @@ final class PackageHandler extends Handler { case DEFERRED_NO_KILL_POST_DELETE: { InstallArgs args = (InstallArgs) msg.obj; if (args != null) { - mRemovePackageHelper.cleanUpResources(args.mCodeFile, args.mInstructionSets); + mPm.cleanUpResources(args.mCodeFile, args.mInstructionSets); } } break; case DEFERRED_NO_KILL_INSTALL_OBSERVER: diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 95b565d235cb..1bb20b4769f5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -18,7 +18,9 @@ package com.android.server.pm; import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO; import static android.os.Process.INVALID_UID; + import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME; + import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -407,11 +409,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } private void removeStagingDirs(ArraySet<File> stagingDirsToRemove) { - final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm); // Clean up orphaned staging directories for (File stage : stagingDirsToRemove) { Slog.w(TAG, "Deleting orphan stage " + stage); - removePackageHelper.removeCodePath(stage); + mPm.removeCodePath(stage); } } @@ -1320,9 +1321,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements @Override public void installExistingPackage(String packageName, int installFlags, int installReason, IntentSender statusReceiver, int userId, List<String> allowListedPermissions) { - final InstallPackageHelper installPackageHelper = new InstallPackageHelper(mPm); - var result = installPackageHelper.installExistingPackageAsUser(packageName, userId, + var result = mPm.installExistingPackageAsUser(packageName, userId, installFlags, installReason, allowListedPermissions, statusReceiver); int returnCode = result.first; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 54a2e3adf713..d0e5f96f8d0f 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2449,14 +2449,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } private void onSystemDataLoaderUnrecoverable() { - final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm); final String packageName = getPackageName(); if (TextUtils.isEmpty(packageName)) { // The package has not been installed. return; } mHandler.post(() -> { - if (deletePackageHelper.deletePackageX(packageName, + if (mPm.deletePackageX(packageName, PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/) != PackageManager.DELETE_SUCCEEDED) { diff --git a/services/core/java/com/android/server/pm/PackageManagerException.java b/services/core/java/com/android/server/pm/PackageManagerException.java index dea66591569f..d69737aef98f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerException.java +++ b/services/core/java/com/android/server/pm/PackageManagerException.java @@ -63,6 +63,7 @@ public class PackageManagerException extends Exception { public static final int INTERNAL_ERROR_STATIC_SHARED_LIB_OVERLAY_TARGETS = -35; public static final int INTERNAL_ERROR_APEX_NOT_DIRECTORY = -36; public static final int INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE = -37; + public static final int INTERNAL_ERROR_MISSING_USER = -38; @IntDef(prefix = { "INTERNAL_ERROR_" }, value = { INTERNAL_ERROR_NATIVE_LIBRARY_COPY, @@ -101,7 +102,8 @@ public class PackageManagerException extends Exception { INTERNAL_ERROR_STATIC_SHARED_LIB_PROTECTED_BROADCAST, INTERNAL_ERROR_STATIC_SHARED_LIB_OVERLAY_TARGETS, INTERNAL_ERROR_APEX_NOT_DIRECTORY, - INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE + INTERNAL_ERROR_APEX_MORE_THAN_ONE_FILE, + INTERNAL_ERROR_MISSING_USER }) @Retention(RetentionPolicy.SOURCE) public @interface InternalErrorCode {} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 33cb85c038a0..ddc8369738de 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -70,7 +70,6 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; -import android.content.IIntentReceiver; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; @@ -98,6 +97,7 @@ import android.content.pm.InstantAppInfo; import android.content.pm.InstantAppRequest; import android.content.pm.ModuleInfo; import android.content.pm.PackageInfo; +import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ComponentEnabledSetting; @@ -115,6 +115,7 @@ import android.content.pm.TestUtilityService; import android.content.pm.UserInfo; import android.content.pm.UserPackage; import android.content.pm.VerifierDeviceIdentity; +import android.content.pm.VerifierInfo; import android.content.pm.VersionedPackage; import android.content.pm.overlay.OverlayPaths; import android.content.pm.parsing.PackageLite; @@ -985,7 +986,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService private final DeletePackageHelper mDeletePackageHelper; private final InitAppsHelper mInitAppsHelper; private final AppDataHelper mAppDataHelper; - private final InstallPackageHelper mInstallPackageHelper; + @NonNull private final InstallPackageHelper mInstallPackageHelper; private final PreferredActivityHelper mPreferredActivityHelper; private final ResolveIntentHelper mResolveIntentHelper; private final DexOptHelper mDexOptHelper; @@ -1715,7 +1716,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService (i, pm) -> new CrossProfileIntentFilterHelper(i.getSettings(), i.getUserManagerService(), i.getLock(), i.getUserManagerInternal(), context), - (i, pm) -> new UpdateOwnershipHelper()); + (i, pm) -> new UpdateOwnershipHelper(), + (i, pm) -> new PackageMonitorCallbackHelper()); if (Build.VERSION.SDK_INT <= 0) { Slog.w(TAG, "**** ro.build.version.sdk not set!"); @@ -2067,17 +2069,19 @@ public class PackageManagerService implements PackageSender, TestUtilityService mDomainVerificationManager.setConnection(mDomainVerificationConnection); mBroadcastHelper = new BroadcastHelper(mInjector); - mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper(mInjector); + mPackageMonitorCallbackHelper = injector.getPackageMonitorCallbackHelper(); mAppDataHelper = new AppDataHelper(this); - mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper); - mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper); - mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper); + mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper, mBroadcastHelper); + mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper, + mBroadcastHelper); + mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper, mRemovePackageHelper, + mDeletePackageHelper, mBroadcastHelper); mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager, mInjector.getUserManagerInternal(), mDeletePackageHelper); mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper); - mPreferredActivityHelper = new PreferredActivityHelper(this); + mPreferredActivityHelper = new PreferredActivityHelper(this, mBroadcastHelper); mResolveIntentHelper = new ResolveIntentHelper(mContext, mPreferredActivityHelper, injector.getCompatibility(), mUserManager, mDomainVerificationManager, mUserNeedsBadging, () -> mResolveInfo, () -> mInstantAppInstallerActivity, @@ -2085,7 +2089,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService mDexOptHelper = new DexOptHelper(this); mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mUserManager, mBroadcastHelper, mProtectedPackages); - mDistractingPackageHelper = new DistractingPackageHelper(this, mInjector, mBroadcastHelper, + mDistractingPackageHelper = new DistractingPackageHelper(this, mBroadcastHelper, mSuspendPackageHelper); mStorageEventHelper = new StorageEventHelper(this, mDeletePackageHelper, mRemovePackageHelper); @@ -3078,38 +3082,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Override - public void sendPackageBroadcast(final String action, final String pkg, final Bundle extras, - final int flags, final String targetPkg, final IIntentReceiver finishedReceiver, - final int[] userIds, int[] instantUserIds, - @Nullable SparseArray<int[]> broadcastAllowList, - @Nullable Bundle bOptions) { - mHandler.post(() -> mBroadcastHelper.sendPackageBroadcast(action, pkg, extras, flags, - targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList, - null /* filterExtrasForReceiver */, bOptions)); - if (targetPkg == null) { - // For some broadcast action, e.g. ACTION_PACKAGE_ADDED, this method will be called - // many times to different targets, e.g. installer app, permission controller, other - // registered apps. We should filter it to avoid calling back many times for the same - // action. When the targetPkg is set, it sends the broadcast to specific app, e.g. - // installer app or null for registered apps. The callback only need to send back to the - // registered apps so we check the null condition here. - notifyPackageMonitor(action, pkg, extras, userIds, instantUserIds, broadcastAllowList); - } - } - - void notifyPackageMonitor(String action, String pkg, Bundle extras, int[] userIds, - int[] instantUserIds, SparseArray<int[]> broadcastAllowList) { - mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds, - instantUserIds, broadcastAllowList); - } - - void notifyResourcesChanged(boolean mediaStatus, boolean replacing, - @NonNull String[] pkgNames, @NonNull int[] uids) { - mPackageMonitorCallbackHelper.notifyResourcesChanged(mediaStatus, replacing, pkgNames, - uids); - } - - @Override public void notifyPackageAdded(String packageName, int uid) { mPackageObserverHelper.notifyAdded(packageName, uid); } @@ -3125,64 +3097,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService UserPackage.removeFromCache(UserHandle.getUserId(uid), packageName); } - void sendPackageAddedForUser(@NonNull Computer snapshot, String packageName, - @NonNull PackageStateInternal packageState, int userId, boolean isArchived, - int dataLoaderType) { - final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId); - final boolean isSystem = packageState.isSystem(); - final boolean isInstantApp = userState.isInstantApp(); - final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId }; - final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY; - sendPackageAddedForNewUsers(snapshot, packageName, isSystem /*sendBootCompleted*/, - false /*startReceiver*/, packageState.getAppId(), userIds, instantUserIds, - isArchived, dataLoaderType); - - // Send a session commit broadcast - final PackageInstaller.SessionInfo info = new PackageInstaller.SessionInfo(); - info.installReason = userState.getInstallReason(); - info.appPackageName = packageName; - sendSessionCommitBroadcast(info, userId); - } - - @Override - public void sendPackageAddedForNewUsers(@NonNull Computer snapshot, String packageName, - boolean sendBootCompleted, boolean includeStopped, @AppIdInt int appId, int[] userIds, - int[] instantUserIds, boolean isArchived, int dataLoaderType) { - if (ArrayUtils.isEmpty(userIds) && ArrayUtils.isEmpty(instantUserIds)) { - return; - } - SparseArray<int[]> broadcastAllowList = mAppsFilter.getVisibilityAllowList(snapshot, - snapshot.getPackageStateInternal(packageName, Process.SYSTEM_UID), - userIds, snapshot.getPackageStates()); - mHandler.post( - () -> mBroadcastHelper.sendPackageAddedForNewUsers(packageName, appId, userIds, - instantUserIds, isArchived, dataLoaderType, broadcastAllowList)); - mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(packageName, appId, userIds, - instantUserIds, isArchived, dataLoaderType, broadcastAllowList); - if (sendBootCompleted && !ArrayUtils.isEmpty(userIds)) { - mHandler.post(() -> { - for (int userId : userIds) { - mBroadcastHelper.sendBootCompletedBroadcastToSystemApp( - packageName, includeStopped, userId); - } - } - ); - } - } - - private void sendApplicationHiddenForUser(String packageName, PackageStateInternal packageState, - int userId) { - final PackageRemovedInfo info = new PackageRemovedInfo(this); - info.mRemovedPackage = packageName; - info.mInstallerPackageName = packageState.getInstallSource().mInstallerPackageName; - info.mRemovedUsers = new int[] {userId}; - info.mBroadcastUsers = new int[] {userId}; - info.mUid = UserHandle.getUid(userId, packageState.getAppId()); - info.mRemovedPackageVersionCode = packageState.getVersionCode(); - info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/, - false /*isArchived*/); - } - boolean isUserRestricted(int userId, String restrictionKey) { Bundle restrictions = mUserManager.getUserRestrictions(userId); if (restrictions.getBoolean(restrictionKey, false)) { @@ -3505,11 +3419,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } - void postPreferredActivityChangedBroadcast(int userId) { - mHandler.post(() -> mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId)); - } - - /** This method takes a specific user id as well as UserHandle.USER_ALL. */ @GuardedBy("mLock") void clearPackagePreferredActivitiesLPw(String packageName, @@ -3609,17 +3518,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService } public void sendSessionCommitBroadcast(PackageInstaller.SessionInfo sessionInfo, int userId) { - UserManagerService ums = UserManagerService.getInstance(); - if (ums == null || sessionInfo.isStaged()) { - return; - } - final UserInfo parent = ums.getProfileParent(userId); - final int launcherUid = (parent != null) ? parent.id : userId; - // TODO: Should this snapshot be moved further up? - final ComponentName launcherComponent = snapshotComputer() - .getDefaultHomeActivity(launcherUid); - mBroadcastHelper.sendSessionCommitBroadcast(sessionInfo, userId, launcherUid, - launcherComponent, mAppPredictionServicePackage); + mBroadcastHelper.sendSessionCommitBroadcast(snapshotComputer(), sessionInfo, userId, + mAppPredictionServicePackage); } private @Nullable String getSetupWizardPackageNameImpl(@NonNull Computer computer) { @@ -3988,7 +3888,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService if (isSystemStub && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) { - if (!mInstallPackageHelper.enableCompressedPackage(deletedPkg, pkgSetting)) { + if (!enableCompressedPackage(deletedPkg, pkgSetting)) { Slog.w(TAG, "Failed setApplicationEnabledSetting: failed to enable " + "commpressed package " + setting.getPackageName()); updateAllowed[i] = false; @@ -4070,8 +3970,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService final ArrayList<String> components = sendNowBroadcasts.valueAt(i); final int packageUid = UserHandle.getUid( userId, pkgSettings.get(packageName).getAppId()); - sendPackageChangedBroadcast(newSnapshot, packageName, false /* dontKillApp */, - components, packageUid, null /* reason */); + mBroadcastHelper.sendPackageChangedBroadcast(newSnapshot, packageName, + false /* dontKillApp */, components, packageUid, null /* reason */); } } finally { Binder.restoreCallingIdentity(callingId); @@ -4147,27 +4047,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } - void sendPackageChangedBroadcast(@NonNull Computer snapshot, String packageName, - boolean dontKillApp, ArrayList<String> componentNames, int packageUid, String reason) { - PackageStateInternal setting = snapshot.getPackageStateInternal(packageName, - Process.SYSTEM_UID); - if (setting == null) { - return; - } - final int userId = UserHandle.getUserId(packageUid); - final boolean isInstantApp = - snapshot.isInstantAppInternal(packageName, userId, Process.SYSTEM_UID); - final int[] userIds = isInstantApp ? EMPTY_INT_ARRAY : new int[] { userId }; - final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY; - final SparseArray<int[]> broadcastAllowList = - isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds); - mHandler.post(() -> mBroadcastHelper.sendPackageChangedBroadcast( - packageName, dontKillApp, componentNames, packageUid, reason, userIds, - instantUserIds, broadcastAllowList)); - mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames, - packageUid, reason, userIds, instantUserIds, broadcastAllowList); - } - /** * Used by SystemServer */ @@ -4312,7 +4191,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService if (pkg == null) { return; } - sendPackageChangedBroadcast(snapshot, pkg.getPackageName(), + mBroadcastHelper.sendPackageChangedBroadcast(snapshot, pkg.getPackageName(), true /* dontKillApp */, new ArrayList<>(Collections.singletonList(pkg.getPackageName())), pkg.getUid(), @@ -5294,7 +5173,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService throw new SecurityException("Calling package " + packageName + " does not belong to calling uid " + callingUid); } - return mSuspendPackageHelper + return SuspendPackageHelper .getSuspendedPackageAppExtras(snapshot, packageName, userId, callingUid); } @@ -5857,10 +5736,14 @@ public class PackageManagerService implements PackageSender, TestUtilityService if (hidden) { killApplication(packageName, newPackageState.getAppId(), userId, "hiding pkg", ApplicationExitInfo.REASON_OTHER); - sendApplicationHiddenForUser(packageName, newPackageState, userId); + mBroadcastHelper.sendApplicationHiddenForUser( + packageName, newPackageState, userId, + /* packageSender= */ PackageManagerService.this); } else { - sendPackageAddedForUser(newSnapshot, packageName, newPackageState, userId, - false /* isArchived */, DataLoaderType.NONE); + mBroadcastHelper.sendPackageAddedForUser( + newSnapshot, packageName, newPackageState, userId, + false /* isArchived */, DataLoaderType.NONE, + mAppPredictionServicePackage); } scheduleWritePackageRestrictions(userId); @@ -7929,4 +7812,75 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } } + + void removeCodePath(@Nullable File codePath) { + mRemovePackageHelper.removeCodePath(codePath); + } + + void cleanUpResources(@Nullable File codeFile, @Nullable String[] instructionSets) { + mRemovePackageHelper.cleanUpResources(codeFile, instructionSets); + } + + void cleanUpForMoveInstall(String volumeUuid, String packageName, String fromCodePath) { + mRemovePackageHelper.cleanUpForMoveInstall(volumeUuid, packageName, fromCodePath); + } + + void sendPendingBroadcasts() { + mInstallPackageHelper.sendPendingBroadcasts(); + } + + void handlePackagePostInstall(@NonNull InstallRequest request, boolean launchedForRestore) { + mInstallPackageHelper.handlePackagePostInstall(request, launchedForRestore); + } + + Pair<Integer, IntentSender> installExistingPackageAsUser( + @Nullable String packageName, + @UserIdInt int userId, @PackageManager.InstallFlags int installFlags, + @PackageManager.InstallReason int installReason, + @Nullable List<String> allowlistedRestrictedPermissions, + @Nullable IntentSender intentSender) { + return mInstallPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags, + installReason, allowlistedRestrictedPermissions, intentSender); + } + AndroidPackage initPackageTracedLI(File scanFile, final int parseFlags, int scanFlags) + throws PackageManagerException { + return mInstallPackageHelper.initPackageTracedLI(scanFile, parseFlags, scanFlags); + } + + void restoreDisabledSystemPackageLIF(@NonNull DeletePackageAction action, + @NonNull int[] allUserHandles, + boolean writeSettings) throws SystemDeleteException { + mInstallPackageHelper.restoreDisabledSystemPackageLIF( + action, allUserHandles, writeSettings); + } + boolean enableCompressedPackage(@NonNull AndroidPackage stubPkg, + @NonNull PackageSetting stubPs) { + return mInstallPackageHelper.enableCompressedPackage(stubPkg, stubPs); + } + + void installPackagesTraced(List<InstallRequest> requests) { + mInstallPackageHelper.installPackagesTraced(requests); + } + + void restoreAndPostInstall(InstallRequest request) { + mInstallPackageHelper.restoreAndPostInstall(request); + } + + Pair<Integer, String> verifyReplacingVersionCode(@NonNull PackageInfoLite pkgLite, + long requiredInstalledVersionCode, + int installFlags) { + return mInstallPackageHelper.verifyReplacingVersionCode( + pkgLite, requiredInstalledVersionCode, installFlags); + } + + int getUidForVerifier(VerifierInfo verifierInfo) { + return mInstallPackageHelper.getUidForVerifier(verifierInfo); + } + + int deletePackageX(String packageName, long versionCode, int userId, int deleteFlags, + boolean removedBySystem) { + return mDeletePackageHelper.deletePackageX(packageName, + PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM, + PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/); + } } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java index 0c2e082e0a86..5b770aab19ca 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java @@ -146,6 +146,7 @@ public class PackageManagerServiceInjector { private final Singleton<SharedLibrariesImpl> mSharedLibrariesProducer; private final Singleton<CrossProfileIntentFilterHelper> mCrossProfileIntentFilterHelperProducer; private final Singleton<UpdateOwnershipHelper> mUpdateOwnershipHelperProducer; + private final Singleton<PackageMonitorCallbackHelper> mPackageMonitorCallbackHelper; PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock, Installer installer, Object installLock, PackageAbiHelper abiHelper, @@ -186,7 +187,8 @@ public class PackageManagerServiceInjector { Producer<IBackupManager> iBackupManager, Producer<SharedLibrariesImpl> sharedLibrariesProducer, Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer, - Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer) { + Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer, + Producer<PackageMonitorCallbackHelper> packageMonitorCallbackHelper) { mContext = context; mLock = lock; mInstaller = installer; @@ -242,6 +244,7 @@ public class PackageManagerServiceInjector { mCrossProfileIntentFilterHelperProducer = new Singleton<>( crossProfileIntentFilterHelperProducer); mUpdateOwnershipHelperProducer = new Singleton<>(updateOwnershipHelperProducer); + mPackageMonitorCallbackHelper = new Singleton<>(packageMonitorCallbackHelper); } /** @@ -431,6 +434,10 @@ public class PackageManagerServiceInjector { return mUpdateOwnershipHelperProducer.get(this, mPackageManager); } + public PackageMonitorCallbackHelper getPackageMonitorCallbackHelper() { + return mPackageMonitorCallbackHelper.get(this, mPackageManager); + } + /** Provides an abstraction to static access to system state. */ public interface SystemWrapper { diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 38f241d40d98..8e91f42d5a2a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -44,6 +44,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; +import android.compat.annotation.Overridable; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -69,6 +70,7 @@ import android.os.Environment; import android.os.FileUtils; import android.os.Process; import android.os.SystemProperties; +import android.os.UserHandle; import android.os.incremental.IncrementalManager; import android.os.incremental.IncrementalStorage; import android.os.incremental.V4Signature; @@ -180,8 +182,9 @@ public class PackageManagerServiceUtils { public @interface SharedUserIdJoinType {} /** - * Components of apps targeting Android T and above will stop receiving intents from - * external callers that do not match its declared intent filters. + * Intents sent from apps targeting Android V and above will stop resolving to components with + * non matching intent filters, even when explicitly setting a component name, unless the + * target components are in the same app as the calling app. * * When an app registers an exported component in its manifest and adds an <intent-filter>, * the component can be started by any intent - even those that do not match the intent filter. @@ -189,8 +192,9 @@ public class PackageManagerServiceUtils { * Without checking the intent when the component is started, in some circumstances this can * allow 3P apps to trigger internal-only functionality. */ + @Overridable @ChangeId - @Disabled /* Revert enforcement: b/274147456 */ + @Disabled /* Enforcement reverted in T: b/274147456 */ private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188; /** @@ -1187,19 +1191,27 @@ public class PackageManagerServiceUtils { public static void applyEnforceIntentFilterMatching( PlatformCompat compat, ComponentResolverApi resolver, List<ResolveInfo> resolveInfos, boolean isReceiver, - Intent intent, String resolvedType, int filterCallingUid) { + Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, + int filterCallingUid) { if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return; + // Do not enforce filter matching when the caller is system or root + if (ActivityManager.canAccessUnexportedComponents(filterCallingUid)) return; + final Printer logPrinter = DEBUG_INTENT_MATCHING ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM) : null; + final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0; + + final boolean enforce = compat.isChangeEnabledByUidInternal( + ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, filterCallingUid); + for (int i = resolveInfos.size() - 1; i >= 0; --i) { final ComponentInfo info = resolveInfos.get(i).getComponentInfo(); - // Do not enforce filter matching when the caller is system, root, or the same app - if (ActivityManager.checkComponentPermission(null, filterCallingUid, - info.applicationInfo.uid, false) == PackageManager.PERMISSION_GRANTED) { + // Skip filter matching when the caller is targeting the same app + if (UserHandle.isSameApp(filterCallingUid, info.applicationInfo.uid)) { continue; } @@ -1221,14 +1233,11 @@ public class PackageManagerServiceUtils { continue; } - // Only enforce filter matching if target app's target SDK >= T - final boolean enforce = compat.isChangeEnabledInternal( - ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo); - boolean match = false; for (int j = 0, size = comp.getIntents().size(); j < size; ++j) { IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter(); - if (IntentResolver.intentMatchesFilter(intentFilter, intent, resolvedType)) { + if (IntentResolver.intentMatchesFilter( + intentFilter, intent, resolvedType, defaultOnly)) { match = true; break; } diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java index bb3bf5360edc..b8c2b8616ea6 100644 --- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java +++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java @@ -52,12 +52,6 @@ class PackageMonitorCallbackHelper { private final Object mLock = new Object(); final IActivityManager mActivityManager = ActivityManager.getService(); - final Handler mHandler; - - PackageMonitorCallbackHelper(PackageManagerServiceInjector injector) { - mHandler = injector.getHandler(); - } - @NonNull @GuardedBy("mLock") private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>(); @@ -100,7 +94,8 @@ class PackageMonitorCallbackHelper { public void notifyPackageAddedForNewUsers(String packageName, @AppIdInt int appId, @NonNull int[] userIds, @NonNull int[] instantUserIds, - boolean isArchived, int dataLoaderType, SparseArray<int[]> broadcastAllowList) { + boolean isArchived, int dataLoaderType, SparseArray<int[]> broadcastAllowList, + @NonNull Handler handler) { Bundle extras = new Bundle(2); // Set to UID of the first user, EXTRA_UID is automatically updated in sendPackageBroadcast final int uid = UserHandle.getUid( @@ -111,11 +106,11 @@ class PackageMonitorCallbackHelper { } extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType); notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras , - userIds /* userIds */, instantUserIds, broadcastAllowList); + userIds /* userIds */, instantUserIds, broadcastAllowList, handler); } public void notifyResourcesChanged(boolean mediaStatus, boolean replacing, - @NonNull String[] pkgNames, @NonNull int[] uids) { + @NonNull String[] pkgNames, @NonNull int[] uids, @NonNull Handler handler) { Bundle extras = new Bundle(); extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgNames); extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids); @@ -125,12 +120,12 @@ class PackageMonitorCallbackHelper { String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */, - null /* instantUserIds */, null /* broadcastAllowList */); + null /* instantUserIds */, null /* broadcastAllowList */, handler); } public void notifyPackageChanged(String packageName, boolean dontKillApp, ArrayList<String> componentNames, int packageUid, String reason, int[] userIds, - int[] instantUserIds, SparseArray<int[]> broadcastAllowList) { + int[] instantUserIds, SparseArray<int[]> broadcastAllowList, Handler handler) { Bundle extras = new Bundle(4); extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0)); String[] nameList = new String[componentNames.size()]; @@ -142,11 +137,12 @@ class PackageMonitorCallbackHelper { extras.putString(Intent.EXTRA_REASON, reason); } notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds, - instantUserIds, broadcastAllowList); + instantUserIds, broadcastAllowList, handler); } public void notifyPackageMonitor(String action, String pkg, Bundle extras, - int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList) { + int[] userIds, int[] instantUserIds, SparseArray<int[]> broadcastAllowList, + Handler handler) { if (!isAllowedCallbackAction(action)) { return; } @@ -160,9 +156,10 @@ class PackageMonitorCallbackHelper { } if (ArrayUtils.isEmpty(instantUserIds)) { - doNotifyCallbacks(action, pkg, extras, resolvedUserIds, broadcastAllowList); + doNotifyCallbacks( + action, pkg, extras, resolvedUserIds, broadcastAllowList, handler); } else { - doNotifyCallbacks(action, pkg, extras, instantUserIds, broadcastAllowList); + doNotifyCallbacks(action, pkg, extras, instantUserIds, broadcastAllowList, handler); } } catch (RemoteException e) { // do nothing @@ -181,7 +178,7 @@ class PackageMonitorCallbackHelper { } private void doNotifyCallbacks(String action, String pkg, Bundle extras, int[] userIds, - SparseArray<int[]> broadcastAllowList) { + SparseArray<int[]> broadcastAllowList, Handler handler) { RemoteCallbackList<IRemoteCallback> callbacks; synchronized (mLock) { callbacks = mCallbacks; @@ -202,7 +199,7 @@ class PackageMonitorCallbackHelper { final int[] allowUids = broadcastAllowList != null ? broadcastAllowList.get(userId) : new int[]{}; - mHandler.post(() -> callbacks.broadcast((callback, user) -> { + handler.post(() -> callbacks.broadcast((callback, user) -> { RegisterUser registerUser = (RegisterUser) user; if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId() != userId)) { diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java index 9f02542a31e4..7ee1772adead 100644 --- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java +++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java @@ -16,24 +16,12 @@ package com.android.server.pm; -import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED; -import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; -import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; - -import android.annotation.NonNull; -import android.app.ActivityManagerInternal; -import android.app.BroadcastOptions; -import android.content.Intent; -import android.os.Bundle; -import android.os.PowerExemptionManager; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.util.ArrayUtils; -import com.android.server.LocalServices; final class PackageRemovedInfo { - final PackageSender mPackageSender; String mRemovedPackage; String mInstallerPackageName; int mUid = -1; @@ -58,116 +46,6 @@ final class PackageRemovedInfo { InstallArgs mArgs = null; private static final int[] EMPTY_INT_ARRAY = new int[0]; - PackageRemovedInfo(PackageSender packageSender) { - mPackageSender = packageSender; - } - - void sendPackageRemovedBroadcasts(boolean killApp, boolean removedBySystem, - boolean isArchived) { - sendPackageRemovedBroadcastInternal(killApp, removedBySystem, isArchived); - } - - void sendSystemPackageUpdatedBroadcasts() { - if (mIsRemovedPackageSystemUpdate) { - sendSystemPackageUpdatedBroadcastsInternal(); - } - } - - private void sendSystemPackageUpdatedBroadcastsInternal() { - Bundle extras = new Bundle(2); - extras.putInt(Intent.EXTRA_UID, mRemovedAppId >= 0 ? mRemovedAppId : mUid); - extras.putBoolean(Intent.EXTRA_REPLACING, true); - mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, mRemovedPackage, extras, - 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null); - if (mInstallerPackageName != null) { - mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, - mRemovedPackage, extras, 0 /*flags*/, - mInstallerPackageName, null, null, null, null /* broadcastAllowList */, - null); - mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - mRemovedPackage, extras, 0 /*flags*/, - mInstallerPackageName, null, null, null, null /* broadcastAllowList */, - null); - } - mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, mRemovedPackage, - extras, 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null); - mPackageSender.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0, - mRemovedPackage, null, null, null, null /* broadcastAllowList */, - getTemporaryAppAllowlistBroadcastOptions(REASON_PACKAGE_REPLACED).toBundle()); - } - - private static @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions( - @PowerExemptionManager.ReasonCode int reasonCode) { - long duration = 10_000; - final ActivityManagerInternal amInternal = - LocalServices.getService(ActivityManagerInternal.class); - if (amInternal != null) { - duration = amInternal.getBootTimeTempAllowListDuration(); - } - final BroadcastOptions bOptions = BroadcastOptions.makeBasic(); - bOptions.setTemporaryAppAllowlist(duration, - TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, - reasonCode, ""); - return bOptions; - } - - private void sendPackageRemovedBroadcastInternal(boolean killApp, boolean removedBySystem, - boolean isArchived) { - Bundle extras = new Bundle(); - final int removedUid = mRemovedAppId >= 0 ? mRemovedAppId : mUid; - extras.putInt(Intent.EXTRA_UID, removedUid); - extras.putBoolean(Intent.EXTRA_DATA_REMOVED, mDataRemoved); - extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, mIsRemovedPackageSystemUpdate); - extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp); - extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem); - final boolean isReplace = mIsUpdate || mIsRemovedPackageSystemUpdate; - if (isReplace || isArchived) { - extras.putBoolean(Intent.EXTRA_REPLACING, true); - } - if (isArchived) { - extras.putBoolean(Intent.EXTRA_ARCHIVAL, true); - } - extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, mRemovedForAllUsers); - - // Send PACKAGE_REMOVED broadcast to the respective installer. - if (mRemovedPackage != null && mInstallerPackageName != null) { - mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, - mRemovedPackage, extras, 0 /*flags*/, - mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null); - } - if (mIsStaticSharedLib) { - // When uninstalling static shared libraries, only the package's installer needs to be - // sent a PACKAGE_REMOVED broadcast. There are no other intended recipients. - return; - } - if (mRemovedPackage != null) { - mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, - mRemovedPackage, extras, 0, null /*targetPackage*/, null, - mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null); - mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED_INTERNAL, - mRemovedPackage, extras, 0 /*flags*/, PLATFORM_PACKAGE_NAME, - null /*finishedReceiver*/, mBroadcastUsers, mInstantUserIds, - mBroadcastAllowList, null /*bOptions*/); - if (mDataRemoved && !mIsRemovedPackageSystemUpdate) { - mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, - mRemovedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null, - null, mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null); - mPackageSender.notifyPackageRemoved(mRemovedPackage, removedUid); - } - } - if (mRemovedAppId >= 0) { - // If a system app's updates are uninstalled the UID is not actually removed. Some - // services need to know the package name affected. - if (isReplace) { - extras.putString(Intent.EXTRA_PACKAGE_NAME, mRemovedPackage); - } - - mPackageSender.sendPackageBroadcast(Intent.ACTION_UID_REMOVED, - null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, - null, null, mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null); - } - } - public void populateBroadcastUsers(PackageSetting deletedPackageSetting) { if (mRemovedUsers == null) { mBroadcastUsers = null; diff --git a/services/core/java/com/android/server/pm/PackageSender.java b/services/core/java/com/android/server/pm/PackageSender.java index 82e1d5f389c5..db83f593d5ee 100644 --- a/services/core/java/com/android/server/pm/PackageSender.java +++ b/services/core/java/com/android/server/pm/PackageSender.java @@ -16,24 +16,7 @@ package com.android.server.pm; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.IIntentReceiver; -import android.os.Bundle; -import android.util.SparseArray; - interface PackageSender { - /** - * @param userIds User IDs where the action occurred on a full application - * @param instantUserIds User IDs where the action occurred on an instant application - */ - void sendPackageBroadcast(String action, String pkg, - Bundle extras, int flags, String targetPkg, - IIntentReceiver finishedReceiver, int[] userIds, int[] instantUserIds, - @Nullable SparseArray<int[]> broadcastAllowList, @Nullable Bundle bOptions); - void sendPackageAddedForNewUsers(@NonNull Computer snapshot, String packageName, - boolean sendBootCompleted, boolean includeStopped, int appId, int[] userIds, - int[] instantUserIds, boolean isArchived, int dataLoaderType); void notifyPackageAdded(String packageName, int uid); void notifyPackageChanged(String packageName, int uid); void notifyPackageRemoved(String packageName, int uid); diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java index 571aab4f6969..41d2aeb9b168 100644 --- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java +++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java @@ -69,10 +69,12 @@ final class PreferredActivityHelper { private static final String TAG_DEFAULT_APPS = "da"; private final PackageManagerService mPm; + private final BroadcastHelper mBroadcastHelper; // TODO(b/198166813): remove PMS dependency - PreferredActivityHelper(PackageManagerService pm) { + PreferredActivityHelper(PackageManagerService pm, BroadcastHelper broadcastHelper) { mPm = pm; + mBroadcastHelper = broadcastHelper; } private ResolveInfo findPreferredActivityNotLocked(@NonNull Computer snapshot, Intent intent, @@ -120,7 +122,7 @@ final class PreferredActivityHelper { } if (changedUsers.size() > 0) { updateDefaultHomeNotLocked(mPm.snapshotComputer(), changedUsers); - mPm.postPreferredActivityChangedBroadcast(userId); + mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId); mPm.scheduleWritePackageRestrictions(userId); } } @@ -167,7 +169,7 @@ final class PreferredActivityHelper { return mPm.setActiveLauncherPackage(packageName, userId, successful -> { if (successful) { - mPm.postPreferredActivityChangedBroadcast(userId); + mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId); } }); } @@ -215,7 +217,7 @@ final class PreferredActivityHelper { } // Re-snapshot after mLock if (!(isHomeFilter(filter) && updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId))) { - mPm.postPreferredActivityChangedBroadcast(userId); + mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId); } } @@ -411,7 +413,7 @@ final class PreferredActivityHelper { if (isHomeFilter(filter)) { updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId); } - mPm.postPreferredActivityChangedBroadcast(userId); + mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId); } public void clearPackagePersistentPreferredActivities(String packageName, int userId) { @@ -426,7 +428,7 @@ final class PreferredActivityHelper { } if (changed) { updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId); - mPm.postPreferredActivityChangedBroadcast(userId); + mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId); mPm.scheduleWritePackageRestrictions(userId); } } @@ -443,7 +445,7 @@ final class PreferredActivityHelper { } if (changed) { updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId); - mPm.postPreferredActivityChangedBroadcast(userId); + mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId); mPm.scheduleWritePackageRestrictions(userId); } } @@ -616,7 +618,7 @@ final class PreferredActivityHelper { mPm.clearPackagePreferredActivitiesLPw(null, changedUsers, userId); } if (changedUsers.size() > 0) { - mPm.postPreferredActivityChangedBroadcast(userId); + mBroadcastHelper.sendPreferredActivityChangedBroadcast(userId); } synchronized (mPm.mLock) { mPm.mSettings.applyDefaultPreferredAppsLPw(userId); diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index d989c90a0597..b055a3ffd688 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -22,6 +22,7 @@ import static android.os.incremental.IncrementalManager.isIncrementalPath; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; import static android.os.storage.StorageManager.FLAG_STORAGE_DE; import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL; + import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL; import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE; @@ -69,9 +70,11 @@ final class RemovePackageHelper { private final PermissionManagerServiceInternal mPermissionManager; private final SharedLibrariesImpl mSharedLibraries; private final AppDataHelper mAppDataHelper; + private final BroadcastHelper mBroadcastHelper; // TODO(b/198166813): remove PMS dependency - RemovePackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) { + RemovePackageHelper(PackageManagerService pm, AppDataHelper appDataHelper, + BroadcastHelper broadcastHelper) { mPm = pm; mIncrementalManager = mPm.mInjector.getIncrementalManager(); mInstaller = mPm.mInjector.getInstaller(); @@ -79,10 +82,7 @@ final class RemovePackageHelper { mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal(); mSharedLibraries = mPm.mInjector.getSharedLibrariesImpl(); mAppDataHelper = appDataHelper; - } - - RemovePackageHelper(PackageManagerService pm) { - this(pm, new AppDataHelper(pm)); + mBroadcastHelper = broadcastHelper; } public void removeCodePath(File codePath) { @@ -265,7 +265,8 @@ final class RemovePackageHelper { final List<AndroidPackage> sharedUserPkgs = sus != null ? sus.getPackages() : Collections.emptyList(); - final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm); + final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm, + mBroadcastHelper); final int[] userIds = (userId == UserHandle.USER_ALL) ? mUserManagerInternal.getUserIds() : new int[] {userId}; for (int nextUserId : userIds) { @@ -395,13 +396,13 @@ final class RemovePackageHelper { } if (changedUsers.size() > 0) { final PreferredActivityHelper preferredActivityHelper = - new PreferredActivityHelper(mPm); + new PreferredActivityHelper(mPm, mBroadcastHelper); preferredActivityHelper.updateDefaultHomeNotLocked(mPm.snapshotComputer(), changedUsers); - mPm.postPreferredActivityChangedBroadcast(UserHandle.USER_ALL); + mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL); } } else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate - && outInfo.mRemovedUsers != null) { + && outInfo.mRemovedUsers != null && !outInfo.mIsExternal) { // For non-system uninstalls with DELETE_KEEP_DATA, set the installed state to false // for affected users. This does not apply to app updates where the old apk is replaced // but the old data remains. diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java index 160b2aa05c18..da14397b9c92 100644 --- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java +++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java @@ -459,7 +459,7 @@ final class ResolveIntentHelper { list.add(ri); PackageManagerServiceUtils.applyEnforceIntentFilterMatching( mPlatformCompat, componentResolver, list, true, intent, - resolvedType, filterCallingUid); + resolvedType, flags, filterCallingUid); } } } else { @@ -485,7 +485,7 @@ final class ResolveIntentHelper { // We also have to ensure all components match the original intent PackageManagerServiceUtils.applyEnforceIntentFilterMatching( mPlatformCompat, componentResolver, - list, true, originalIntent, resolvedType, filterCallingUid); + list, true, originalIntent, resolvedType, flags, filterCallingUid); } return computer.applyPostResolutionFilter(list, instantAppPkgName, false, queryingUid, diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 0ea45c466ca7..e993d9e5b724 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -372,6 +372,7 @@ class ShortcutPackage extends ShortcutPackageItem { // Extract Icon and update the icon res ID and the bitmap path. s.saveIconAndFixUpShortcutLocked(this, newShortcut); s.fixUpShortcutResourceNamesAndValues(newShortcut); + ensureShortcutCountBeforePush(); saveShortcut(newShortcut); } @@ -426,7 +427,6 @@ class ShortcutPackage extends ShortcutPackageItem { @NonNull List<ShortcutInfo> changedShortcuts) { Preconditions.checkArgument(newShortcut.isEnabled(), "pushDynamicShortcuts() cannot publish disabled shortcuts"); - ensureShortcutCountBeforePush(); newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC); diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index db5b9b199b85..c725cdca9d76 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -147,7 +147,6 @@ public final class StorageEventHelper extends StorageEventListener { final Settings.VersionInfo ver; final List<? extends PackageStateInternal> packages; - final InstallPackageHelper installPackageHelper = new InstallPackageHelper(mPm); synchronized (mPm.mLock) { ver = mPm.mSettings.findOrCreateVersion(volumeUuid); packages = mPm.mSettings.getVolumePackagesLPr(volumeUuid); @@ -160,7 +159,7 @@ public final class StorageEventHelper extends StorageEventListener { synchronized (mPm.mInstallLock) { final AndroidPackage pkg; try { - pkg = installPackageHelper.initPackageTracedLI( + pkg = mPm.initPackageTracedLI( ps.getPath(), parseFlags, SCAN_INITIAL); loaded.add(pkg); @@ -228,7 +227,8 @@ public final class StorageEventHelper extends StorageEventListener { } if (DEBUG_INSTALL) Slog.d(TAG, "Loaded packages " + loaded); - sendResourcesChangedBroadcast(true /* mediaStatus */, false /* replacing */, loaded); + mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm.snapshotComputer(), + true /* mediaStatus */, false /* replacing */, loaded); synchronized (mLoadedVolumes) { mLoadedVolumes.add(vol.getId()); } @@ -256,7 +256,7 @@ public final class StorageEventHelper extends StorageEventListener { final AndroidPackage pkg = ps.getPkg(); final int deleteFlags = PackageManager.DELETE_KEEP_DATA; - final PackageRemovedInfo outInfo = new PackageRemovedInfo(mPm); + final PackageRemovedInfo outInfo = new PackageRemovedInfo(); try (PackageFreezer freezer = mPm.freezePackageForDelete(ps.getPackageName(), UserHandle.USER_ALL, deleteFlags, @@ -280,7 +280,8 @@ public final class StorageEventHelper extends StorageEventListener { } if (DEBUG_INSTALL) Slog.d(TAG, "Unloaded packages " + unloaded); - sendResourcesChangedBroadcast(false /* mediaStatus */, false /* replacing */, unloaded); + mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm.snapshotComputer(), + false /* mediaStatus */, false /* replacing */, unloaded); synchronized (mLoadedVolumes) { mLoadedVolumes.remove(vol.getId()); } @@ -295,21 +296,6 @@ public final class StorageEventHelper extends StorageEventListener { } } - private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing, - ArrayList<AndroidPackage> packages) { - final int size = packages.size(); - final String[] packageNames = new String[size]; - final int[] packageUids = new int[size]; - for (int i = 0; i < size; i++) { - final AndroidPackage pkg = packages.get(i); - packageNames[i] = pkg.getPackageName(); - packageUids[i] = pkg.getUid(); - } - mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, mediaStatus, - replacing, packageNames, packageUids); - mPm.notifyResourcesChanged(mediaStatus, replacing, packageNames, packageUids); - } - /** * Examine all apps present on given mounted volume, and destroy apps that * aren't expected, either due to uninstallation or reinstallation on diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index ddb045df4eaf..29d99a73a034 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -26,17 +26,13 @@ import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.ActivityManager; import android.app.AppOpsManager; -import android.app.BroadcastOptions; -import android.app.IActivityManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.SuspendDialogInfo; import android.os.Binder; import android.os.Bundle; -import android.os.Handler; import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; @@ -47,7 +43,6 @@ import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.server.LocalServices; @@ -207,19 +202,22 @@ public final class SuspendPackageHelper { } }); + final Computer newSnapshot = mPm.snapshotComputer(); if (!notifyPackagesList.isEmpty()) { final String[] changedPackages = notifyPackagesList.toArray(new String[0]); - sendPackagesSuspendedForUser( + mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot, suspended ? Intent.ACTION_PACKAGES_SUSPENDED : Intent.ACTION_PACKAGES_UNSUSPENDED, changedPackages, notifyUids.toArray(), quarantined, userId); - sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId); + mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, changedPackages, + suspended, userId); mPm.scheduleWritePackageRestrictions(userId); } // Send the suspension changed broadcast to ensure suspension state is not stale. if (!changedPackagesList.isEmpty()) { - sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, + mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot, + Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, changedPackagesList.toArray(new String[0]), changedUids.toArray(), quarantined, userId); } @@ -269,8 +267,10 @@ public final class SuspendPackageHelper { * @return The app extras of the suspended package. */ @Nullable - Bundle getSuspendedPackageAppExtras(@NonNull Computer snapshot, @NonNull String packageName, - int userId, int callingUid) { + static Bundle getSuspendedPackageAppExtras(@NonNull Computer snapshot, + @NonNull String packageName, + int userId, + int callingUid) { final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName, callingUid); if (ps == null) { return null; @@ -299,7 +299,7 @@ public final class SuspendPackageHelper { * suspensions will be removed. * @param userId The user for which the changes are taking place. */ - void removeSuspensionsBySuspendingPackage(@NonNull Computer computer, + void removeSuspensionsBySuspendingPackage(@NonNull Computer snapshot, @NonNull String[] packagesToChange, @NonNull Predicate<String> suspendingPackagePredicate, int userId) { final List<String> unsuspendedPackages = new ArrayList<>(); @@ -307,7 +307,7 @@ public final class SuspendPackageHelper { final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>(); for (String packageName : packagesToChange) { final PackageStateInternal packageState = - computer.getPackageStateInternal(packageName); + snapshot.getPackageStateInternal(packageName); final PackageUserStateInternal packageUserState = packageState == null ? null : packageState.getUserStateOrDefault(userId); if (packageUserState == null || !packageUserState.isSuspended()) { @@ -350,11 +350,14 @@ public final class SuspendPackageHelper { }); mPm.scheduleWritePackageRestrictions(userId); + final Computer newSnapshot = mPm.snapshotComputer(); if (!unsuspendedPackages.isEmpty()) { final String[] packageArray = unsuspendedPackages.toArray( new String[unsuspendedPackages.size()]); - sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId); - sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED, + mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, packageArray, + false, userId); + mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot, + Intent.ACTION_PACKAGES_UNSUSPENDED, packageArray, unsuspendedUids.toArray(), false, userId); } } @@ -610,38 +613,6 @@ public final class SuspendPackageHelper { } /** - * Send broadcast intents for packages suspension changes. - * - * @param intent The action name of the suspension intent. - * @param pkgList The names of packages which have suspension changes. - * @param uidList The uids of packages which have suspension changes. - * @param userId The user where packages reside. - */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - void sendPackagesSuspendedForUser(@NonNull String intent, @NonNull String[] pkgList, - @NonNull int[] uidList, boolean quarantined, int userId) { - final Handler handler = mInjector.getHandler(); - final Bundle extras = new Bundle(3); - extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList); - extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList); - if (quarantined) { - extras.putBoolean(Intent.EXTRA_QUARANTINED, true); - } - final int flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND; - final Bundle options = new BroadcastOptions() - .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) - .toBundle(); - handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */, - extras, flags, null /* targetPkg */, null /* finishedReceiver */, - new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */, - (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList( - mPm.snapshotComputer(), callingUid, intentExtras), - options)); - mPm.notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId}, - null /* instantUserIds */, null /* broadcastAllowList */); - } - - /** * Suspends packages on behalf of an admin. * * @return array of packages that are unsuspendable, either because admin is not allowed to @@ -756,37 +727,4 @@ public final class SuspendPackageHelper { } return false; } - - private void sendMyPackageSuspendedOrUnsuspended(String[] affectedPackages, boolean suspended, - int userId) { - final Handler handler = mInjector.getHandler(); - final String action = suspended - ? Intent.ACTION_MY_PACKAGE_SUSPENDED - : Intent.ACTION_MY_PACKAGE_UNSUSPENDED; - handler.post(() -> { - final IActivityManager am = ActivityManager.getService(); - if (am == null) { - Slog.wtf(TAG, "IActivityManager null. Cannot send MY_PACKAGE_ " - + (suspended ? "" : "UN") + "SUSPENDED broadcasts"); - return; - } - final int[] targetUserIds = new int[] {userId}; - final Computer snapshot = mPm.snapshotComputer(); - for (String packageName : affectedPackages) { - final Bundle appExtras = suspended - ? getSuspendedPackageAppExtras(snapshot, packageName, userId, SYSTEM_UID) - : null; - final Bundle intentExtras; - if (appExtras != null) { - intentExtras = new Bundle(1); - intentExtras.putBundle(Intent.EXTRA_SUSPENDED_PACKAGE_EXTRAS, appExtras); - } else { - intentExtras = null; - } - mBroadcastHelper.doSendBroadcast(action, null, intentExtras, - Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null, - targetUserIds, false, null, null, null); - } - }); - } } diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index 04d1da61f3cc..0fd7a373c4aa 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -55,6 +55,7 @@ }, { "name": "GtsContentTestCases", + "keywords": ["internal"], "options": [ { "include-filter": "com.google.android.content.gts" @@ -145,6 +146,19 @@ "include-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest" } ] + }, + { + "file_patterns": [ + "(/|^)InstallPackageHelper\\.java", + "services/core/java/com/android/server/pm/parsing/.*", + "services/core/java/com/android/server/pm/pkg/parsing/.*" + ], + "name": "SdkSandboxManagerServiceUnitTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] } ], "imports": [ diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index 8c73ce8b6b95..c6435aeaba4d 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -139,7 +139,6 @@ final class VerifyingSession { private final UserHandle mUser; @NonNull private final PackageManagerService mPm; - private final InstallPackageHelper mInstallPackageHelper; VerifyingSession(UserHandle user, File stagedDir, IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, InstallSource installSource, @@ -147,7 +146,6 @@ final class VerifyingSession { boolean userActionRequired, PackageManagerService pm) { mPm = pm; mUser = user; - mInstallPackageHelper = new InstallPackageHelper(mPm); mOriginInfo = OriginInfo.fromStagedFile(stagedDir); mObserver = observer; mInstallFlags = sessionParams.installFlags; @@ -181,7 +179,7 @@ final class VerifyingSession { PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext, mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags, mPackageAbiOverride); - Pair<Integer, String> ret = mInstallPackageHelper.verifyReplacingVersionCode( + Pair<Integer, String> ret = mPm.verifyReplacingVersionCode( pkgLite, mRequiredInstalledVersionCode, mInstallFlags); setReturnCode(ret.first, ret.second); if (mRet != INSTALL_SUCCEEDED) { @@ -729,7 +727,7 @@ final class VerifyingSession { continue; } - final int verifierUid = mInstallPackageHelper.getUidForVerifier(verifierInfo); + final int verifierUid = mPm.getUidForVerifier(verifierInfo); if (verifierUid == -1) { continue; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index dc75a98c5bdc..097656cac7f7 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1983,7 +1983,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void run() { if (mPendingHomeKeyEvent != null) { handleShortPressOnHome(mPendingHomeKeyEvent); - mPendingHomeKeyEvent.recycle(); mPendingHomeKeyEvent = null; } } @@ -2028,7 +2027,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_PIP_MENU || mPictureInPictureVisible) { mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); // just in case - mPendingHomeKeyEvent = KeyEvent.obtain(event); + mPendingHomeKeyEvent = event; mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable, ViewConfiguration.getDoubleTapTimeout()); return true; @@ -2036,11 +2035,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // Post to main thread to avoid blocking input pipeline. - final KeyEvent shortPressEvent = KeyEvent.obtain(event); - mHandler.post(() -> { - handleShortPressOnHome(shortPressEvent); - shortPressEvent.recycle(); - }); + mHandler.post(() -> handleShortPressOnHome(event)); return true; } @@ -2067,14 +2062,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (repeatCount == 0) { mHomePressed = true; if (mPendingHomeKeyEvent != null) { - mPendingHomeKeyEvent.recycle(); mPendingHomeKeyEvent = null; mHandler.removeCallbacks(mHomeDoubleTapTimeoutRunnable); - final KeyEvent doublePressEvent = KeyEvent.obtain(event); - mHandler.post(() -> { - handleDoubleTapOnHome(doublePressEvent); - doublePressEvent.recycle(); - }); + mHandler.post(() -> handleDoubleTapOnHome(event)); // TODO(multi-display): Remove display id check once we support recents on // multi-display } else if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI @@ -2084,11 +2074,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { if (!keyguardOn) { // Post to main thread to avoid blocking input pipeline. - final KeyEvent longPressEvent = KeyEvent.obtain(event); - mHandler.post(() -> { - handleLongPressOnHome(longPressEvent); - longPressEvent.recycle(); - }); + mHandler.post(() -> handleLongPressOnHome(event)); } } return true; diff --git a/services/core/java/com/android/server/policy/TEST_MAPPING b/services/core/java/com/android/server/policy/TEST_MAPPING index 819a82c24bfe..338b479f1ad1 100644 --- a/services/core/java/com/android/server/policy/TEST_MAPPING +++ b/services/core/java/com/android/server/policy/TEST_MAPPING @@ -32,7 +32,7 @@ "name": "CtsPermissionPolicyTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest" @@ -49,7 +49,7 @@ "name": "CtsPermissionTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "include-filter": "android.permission.cts.SplitPermissionTest" diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 4a4214f7af83..dfbcbae650cd 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -3347,6 +3347,8 @@ public final class PowerManagerService extends SystemService } else { startDreaming = false; } + Slog.i(TAG, "handleSandman powerGroup=" + groupId + " startDreaming=" + startDreaming + + " wakefulness=" + wakefulnessToString(wakefulness)); } // Start dreaming if needed. @@ -3381,19 +3383,23 @@ public final class PowerManagerService extends SystemService if (startDreaming && isDreaming) { mDreamsBatteryLevelDrain = 0; if (wakefulness == WAKEFULNESS_DOZING) { - Slog.i(TAG, "Dozing..."); + Slog.i(TAG, "Dozing powerGroup " + groupId); } else { - Slog.i(TAG, "Dreaming..."); + Slog.i(TAG, "Dreaming powerGroup " + groupId); } } // If preconditions changed, wait for the next iteration to determine // whether the dream should continue (or be restarted). final PowerGroup powerGroup = mPowerGroups.get(groupId); + final int newWakefulness = powerGroup.getWakefulnessLocked(); if (powerGroup.isSandmanSummonedLocked() - || powerGroup.getWakefulnessLocked() != wakefulness) { + || newWakefulness != wakefulness) { return; // wait for next cycle } + Slog.i(TAG, "handleSandman powerGroup=" + groupId + " isDreaming=" + isDreaming + + " wakefulness=" + newWakefulness); + // Determine whether the dream should continue. long now = mClock.uptimeMillis(); diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING index 19086a184068..05a0e85d790f 100644 --- a/services/core/java/com/android/server/power/TEST_MAPPING +++ b/services/core/java/com/android/server/power/TEST_MAPPING @@ -3,16 +3,14 @@ { "name": "CtsBatterySavingTestCases", "options": [ - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.LargeTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"} + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "androidx.test.filters.LargeTest"} ] }, { "name": "FrameworksMockingServicesTests", "options": [ {"include-filter": "com.android.server.power"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] }, @@ -20,7 +18,6 @@ "name": "PowerServiceTests", "options": [ {"include-filter": "com.android.server.power"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] diff --git a/services/core/java/com/android/server/security/rkp/OWNERS b/services/core/java/com/android/server/security/rkp/OWNERS index 348f94048311..ea6dc727c7b2 100644 --- a/services/core/java/com/android/server/security/rkp/OWNERS +++ b/services/core/java/com/android/server/security/rkp/OWNERS @@ -1 +1 @@ -file:platform/frameworks/base:master:/core/java/android/security/rkp/OWNERS +file:platform/frameworks/base:main:/core/java/android/security/rkp/OWNERS diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java index 96f4a01f7f3a..c2666f63d7a6 100644 --- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java +++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java @@ -297,7 +297,8 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn return; } - for (ClientState clientState : mClients.values()) { + + for (ClientState clientState : mClients.values().toArray(new ClientState[0])) { tryRespondWithError( clientState.mDelegatingListener.mRemoteListener, SpeechRecognizer.ERROR_SERVER_DISCONNECTED); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 40e9c1305f01..3fd832376d2b 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -955,6 +955,17 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } } + public void setTiles(String tiles) { + enforceStatusBarOrShell(); + + if (mBar != null) { + try { + mBar.setQsTiles(tiles.split(",")); + } catch (RemoteException ex) { + } + } + } + public void clickTile(ComponentName component) { enforceStatusBarOrShell(); @@ -2104,7 +2115,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D IStatusBar bar = mBar; if (bar != null) { try { - bar.requestAddTile(componentName, appName, label, icon, proxyCallback); + bar.requestAddTile(callingUid, componentName, appName, label, icon, proxyCallback); return; } catch (RemoteException e) { Slog.e(TAG, "requestAddTile", e); diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java index 11a4976d945f..d6bf02fcdc47 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java +++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java @@ -61,6 +61,8 @@ public class StatusBarShellCommand extends ShellCommand { return runAddTile(); case "remove-tile": return runRemoveTile(); + case "set-tiles": + return runSetTiles(); case "click-tile": return runClickTile(); case "check-support": @@ -105,6 +107,11 @@ public class StatusBarShellCommand extends ShellCommand { return 0; } + private int runSetTiles() throws RemoteException { + mInterface.setTiles(getNextArgRequired()); + return 0; + } + private int runClickTile() throws RemoteException { mInterface.clickTile(ComponentName.unflattenFromString(getNextArgRequired())); return 0; @@ -242,6 +249,9 @@ public class StatusBarShellCommand extends ShellCommand { pw.println(" remove-tile COMPONENT"); pw.println(" Remove a TileService of the specified component"); pw.println(""); + pw.println(" set-tiles LIST-OF-TILES"); + pw.println(" Sets the list of tiles as the current Quick Settings tiles"); + pw.println(""); pw.println(" click-tile COMPONENT"); pw.println(" Click on a TileService of the specified component"); pw.println(""); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a0c78702fec5..ed10346d8495 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -580,7 +580,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A IBinder mRequestedLaunchingTaskFragmentToken; // Tracking splash screen status from previous activity - boolean mAllowIconSplashScreen = true; + boolean mSplashScreenStyleSolidColor = false; boolean mPauseSchedulePendingForPip = false; @@ -2408,7 +2408,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @VisibleForTesting boolean addStartingWindow(String pkg, int resolvedTheme, ActivityRecord from, boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, - boolean activityCreated, boolean allowIcon, boolean activityAllDrawn) { + boolean activityCreated, boolean isSimple, + boolean activityAllDrawn) { // If the display is frozen, we won't do anything until the actual window is // displayed so there is no reason to put in the starting window. if (!okToDisplay()) { @@ -2443,8 +2444,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int typeParameter = StartingSurfaceController .makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning, - allowTaskSnapshot, activityCreated, allowIcon, useLegacy, - activityAllDrawn, type, packageName, mUserId); + allowTaskSnapshot, activityCreated, isSimple, useLegacy, activityAllDrawn, + type, packageName, mUserId); if (type == STARTING_WINDOW_TYPE_SNAPSHOT) { if (isActivityTypeHome()) { @@ -5365,7 +5366,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Finish should only ever commit visibility=false, so we can check full containment // rather than just direct membership. inFinishingTransition = mTransitionController.inFinishingTransition(this); - if (!inFinishingTransition && !mDisplayContent.isSleeping()) { + if (!inFinishingTransition && (visible || !mDisplayContent.isSleeping())) { Slog.e(TAG, "setVisibility=" + visible + " while transition is not collecting or finishing " + this + " caller=" + Debug.getCallers(8)); @@ -6746,7 +6747,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void onFirstWindowDrawn(WindowState win) { firstWindowDrawn = true; // stop tracking - mAllowIconSplashScreen = false; + mSplashScreenStyleSolidColor = true; if (mStartingWindow != null) { ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Finish starting %s" @@ -6795,7 +6796,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void onStartingWindowDrawn() { boolean wasTaskVisible = false; if (task != null) { - mAllowIconSplashScreen = false; + mSplashScreenStyleSolidColor = true; wasTaskVisible = !setTaskHasBeenVisible(); } @@ -7320,32 +7321,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** - * Checks whether an icon splash screen can be used in the starting window based on the - * preference in the {@code options} and this activity's theme, giving higher priority to the - * {@code options}'s preference. - * - * When no preference is specified, a default behaviour is defined: - * - if the activity is started from the home or shell app, an icon can be used - * - if the activity is started from SystemUI, an icon should not be used - * - if there is a launching activity, use its preference - * - if none of the above is met, only use an icon when the activity is started for the first - * time from a System app - * - * The returned value is sent to WmShell, which will make the final decision on what splash - * screen type will be used. - * - * @return true if an icon can be used in the splash screen - * false when an icon should not be used in the splash screen + * @return true if a solid color splash screen must be used + * false when an icon splash screen can be used, but the final decision for whether to + * use an icon or solid color splash screen will be made by WmShell. */ - private boolean canUseIconSplashScreen(ActivityRecord sourceRecord, + private boolean shouldUseSolidColorSplashScreen(ActivityRecord sourceRecord, boolean startActivity, ActivityOptions options, int resolvedTheme) { if (sourceRecord == null && !startActivity) { - // Shouldn't use an icon if this activity is not top activity. This could happen when - // adding a splash screen window to the warm start activity which is re-create because - // top is finishing. + // Use simple style if this activity is not top activity. This could happen when adding + // a splash screen window to the warm start activity which is re-create because top is + // finishing. final ActivityRecord above = task.getActivityAbove(this); if (above != null) { - return false; + return true; } } @@ -7353,33 +7341,32 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int optionsStyle = options != null ? options.getSplashScreenStyle() : SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED; if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR) { - return false; + return true; } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON || isIconStylePreferred(resolvedTheme)) { - return true; + return false; } // Choose the default behavior when neither the ActivityRecord nor the activity theme have // specified a splash screen style. if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME || launchedFromUid == Process.SHELL_UID) { - return true; - } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) { return false; + } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) { + return true; } else { - // Need to check sourceRecord in case this activity is launched from a service or a - // trampoline activity. + // Need to check sourceRecord in case this activity is launched from a service. if (sourceRecord == null) { sourceRecord = searchCandidateLaunchingActivity(); } if (sourceRecord != null) { - return sourceRecord.mAllowIconSplashScreen; + return sourceRecord.mSplashScreenStyleSolidColor; } // Use an icon if the activity was launched from System for the first start. - // Otherwise, can't use an icon splash screen. - return mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEM && startActivity; + // Otherwise, must use solid color splash screen. + return mLaunchSourceType != LAUNCH_SOURCE_TYPE_SYSTEM || !startActivity; } } @@ -7443,7 +7430,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme, splashScreenTheme); - mAllowIconSplashScreen = canUseIconSplashScreen(sourceRecord, startActivity, + mSplashScreenStyleSolidColor = shouldUseSolidColorSplashScreen(sourceRecord, startActivity, startOptions, resolvedTheme); final boolean activityCreated = @@ -7455,7 +7442,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean scheduled = addStartingWindow(packageName, resolvedTheme, prev, newTask || newSingleActivity, taskSwitch, processRunning, - allowTaskSnapshot(), activityCreated, mAllowIconSplashScreen, allDrawn); + allowTaskSnapshot(), activityCreated, mSplashScreenStyleSolidColor, allDrawn); if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) { Slog.d(TAG, "Scheduled starting window for " + this); } diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index f33ecaa90531..10405ec7cd88 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -64,6 +64,7 @@ import android.graphics.Bitmap; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; +import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.text.TextUtils; @@ -503,6 +504,16 @@ class RecentTasks { Slog.i(TAG, "Loading recents for user " + userId + " into memory."); List<Task> tasks = mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks); + + // Tasks are ordered from most recent to least recent. Update the last active time to be + // in sync with task recency when device reboots, so the most recent task has the + // highest last active time + long currentElapsedTime = SystemClock.elapsedRealtime(); + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + task.lastActiveTime = currentElapsedTime - i; + } + mTasks.addAll(tasks); cleanupLocked(userId); mUsersWithRecentsLoaded.put(userId, true); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2a3391807a2c..ea5c9c2715b1 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2234,9 +2234,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mService.getTaskChangeNotificationController().notifyActivityUnpinned(); } mWindowManager.mPolicy.setPipVisibilityLw(inPip); - mWmService.mTransactionFactory.get() - .setTrustedOverlay(task.getSurfaceControl(), inPip) - .apply(); + if (task.getSurfaceControl() != null) { + mWmService.mTransactionFactory.get() + .setTrustedOverlay(task.getSurfaceControl(), inPip) + .apply(); + } } void executeAppTransitionForAllDisplay() { diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index a0517bebca6d..a55c232990cf 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -19,12 +19,12 @@ package com.android.server.wm; import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED; import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN; import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN; -import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_ICON; import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT; import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN; import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK; import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING; import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH; +import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN; import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SNAPSHOT; import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SPLASH_SCREEN; @@ -102,7 +102,7 @@ public class StartingSurfaceController { static int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, - boolean allowIcon, boolean useLegacy, boolean activityDrawn, int startingWindowType, + boolean isSolidColor, boolean useLegacy, boolean activityDrawn, int startingWindowType, String packageName, int userId) { int parameter = 0; if (newTask) { @@ -120,8 +120,8 @@ public class StartingSurfaceController { if (activityCreated || startingWindowType == STARTING_WINDOW_TYPE_SNAPSHOT) { parameter |= TYPE_PARAMETER_ACTIVITY_CREATED; } - if (allowIcon) { - parameter |= TYPE_PARAMETER_ALLOW_ICON; + if (isSolidColor) { + parameter |= TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN; } if (useLegacy) { parameter |= TYPE_PARAMETER_LEGACY_SPLASH_SCREEN; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 261fc2eb26e0..83856153a709 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1162,11 +1162,17 @@ class Task extends TaskFragment { forAllActivities(oldParentTask::cleanUpActivityReferences); } - if (oldParent.inPinnedWindowingMode() - && (newParent == null || !newParent.inPinnedWindowingMode())) { - // Notify if a task from the root pinned task is being removed - // (or moved depending on the mode). - mRootWindowContainer.notifyActivityPipModeChanged(this, null); + if (newParent == null || !newParent.inPinnedWindowingMode()) { + if (oldParent.inPinnedWindowingMode()) { + // Notify if a task from the root pinned task is being removed + // (or moved depending on the mode). + mRootWindowContainer.notifyActivityPipModeChanged(this, null); + } else if (inPinnedWindowingMode()) { + // The task in pinned mode is removed, even though the old parent was not pinned + // The task was most likely force killed or crashed + Slog.e(TAG, "Pinned task is removed t=" + this); + mRootWindowContainer.notifyActivityPipModeChanged(this, null); + } } } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 5d95bc77edcb..50bc825d8bea 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -255,6 +255,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { boolean mClearedForReorderActivityToFront; /** + * Whether the TaskFragment surface is managed by a system {@link TaskFragmentOrganizer}. + */ + boolean mIsSurfaceManagedBySystemOrganizer = false; + + /** * When we are in the process of pausing an activity, before starting the * next one, this variable holds the activity that is currently being paused. * @@ -449,13 +454,21 @@ class TaskFragment extends WindowContainer<WindowContainer> { void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid, @NonNull String processName) { + setTaskFragmentOrganizer(organizer, uid, processName, + false /* isSurfaceManagedBySystemOrganizer */); + } + + void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid, + @NonNull String processName, boolean isSurfaceManagedBySystemOrganizer) { mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder()); mTaskFragmentOrganizerUid = uid; mTaskFragmentOrganizerProcessName = processName; + mIsSurfaceManagedBySystemOrganizer = isSurfaceManagedBySystemOrganizer; } void onTaskFragmentOrganizerRemoved() { mTaskFragmentOrganizer = null; + mIsSurfaceManagedBySystemOrganizer = false; } /** Whether this TaskFragment is organized by the given {@code organizer}. */ @@ -2396,6 +2409,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (mDelayOrganizedTaskFragmentSurfaceUpdate || mTaskFragmentOrganizer == null) { return; } + if (mIsSurfaceManagedBySystemOrganizer) { + return; + } if (mTransitionController.isShellTransitionsEnabled() && !mTransitionController.isCollecting(this)) { // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index ea722b61be6f..04164c20a372 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -26,6 +26,7 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_I import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; +import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; @@ -50,12 +51,15 @@ import android.window.ITaskFragmentOrganizer; import android.window.ITaskFragmentOrganizerController; import android.window.TaskFragmentInfo; import android.window.TaskFragmentOperation; +import android.window.TaskFragmentOrganizerToken; import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentTransaction; import android.window.WindowContainerTransaction; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; +import com.android.window.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -133,6 +137,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr new WeakHashMap<>(); /** + * Whether this {@link android.window.TaskFragmentOrganizer} is a system organizer. If true, + * the {@link android.view.SurfaceControl} of the {@link TaskFragment} is provided to the + * client in the {@link TYPE_TASK_FRAGMENT_APPEARED} event. + */ + private final boolean mIsSystemOrganizer; + + /** * {@link RemoteAnimationDefinition} for embedded activities transition animation that is * organized by this organizer. */ @@ -147,10 +158,12 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr */ private final ArrayMap<IBinder, Integer> mDeferredTransitions = new ArrayMap<>(); - TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer, int pid, int uid) { + TaskFragmentOrganizerState(@NonNull ITaskFragmentOrganizer organizer, int pid, int uid, + boolean isSystemOrganizer) { mOrganizer = organizer; mOrganizerPid = pid; mOrganizerUid = uid; + mIsSystemOrganizer = isSystemOrganizer; try { mOrganizer.asBinder().linkToDeath(this, 0 /*flags*/); } catch (RemoteException e) { @@ -235,11 +248,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr tf.mTaskFragmentAppearedSent = true; mLastSentTaskFragmentInfos.put(tf, info); mTaskFragmentTaskIds.put(tf, taskId); - return new TaskFragmentTransaction.Change( + final TaskFragmentTransaction.Change change = new TaskFragmentTransaction.Change( TYPE_TASK_FRAGMENT_APPEARED) .setTaskFragmentToken(tf.getFragmentToken()) .setTaskFragmentInfo(info) .setTaskId(taskId); + if (mIsSystemOrganizer) { + change.setTaskFragmentSurfaceControl(tf.getSurfaceControl()); + } + return change; } @NonNull @@ -435,8 +452,25 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr : null; } + @VisibleForTesting + void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) { + registerOrganizerInternal(organizer, false /* isSystemOrganizer */); + } + @Override - public void registerOrganizer(@NonNull ITaskFragmentOrganizer organizer) { + public void registerOrganizer( + @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) { + registerOrganizerInternal( + organizer, + Flags.taskFragmentSystemOrganizerFlag() && isSystemOrganizer); + } + + @VisibleForTesting + void registerOrganizerInternal( + @NonNull ITaskFragmentOrganizer organizer, boolean isSystemOrganizer) { + if (isSystemOrganizer) { + enforceTaskPermission("registerSystemOrganizer()"); + } final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); synchronized (mGlobalLock) { @@ -448,7 +482,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr "Replacing existing organizer currently unsupported"); } mTaskFragmentOrganizerState.put(organizer.asBinder(), - new TaskFragmentOrganizerState(organizer, pid, uid)); + new TaskFragmentOrganizerState(organizer, pid, uid, isSystemOrganizer)); mPendingTaskFragmentEvents.put(organizer.asBinder(), new ArrayList<>()); } } @@ -711,6 +745,12 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } + boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) { + final TaskFragmentOrganizerState state = + mTaskFragmentOrganizerState.get(token.asBinder()); + return state != null && state.mIsSystemOrganizer; + } + @Nullable private PendingTaskFragmentEvent getLastPendingParentInfoChangedEvent( @NonNull ITaskFragmentOrganizer organizer, @NonNull Task task) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a6d285a110f9..3db7765963f7 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5241,16 +5241,17 @@ public class WindowManagerService extends IWindowManager.Stub applyForcedPropertiesForDefaultDisplay(); mAnimator.ready(); mDisplayReady = true; - // Reconfigure all displays to make sure that forced properties and - // DisplayWindowSettings are applied. - mRoot.forAllDisplays(DisplayContent::reconfigureDisplayLocked); + mHasWideColorGamutSupport = queryWideColorGamutSupport(); + mHasHdrSupport = queryHdrSupport(); mIsTouchDevice = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TOUCHSCREEN); mIsFakeTouchDevice = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_FAKETOUCH); + // Reconfigure all displays to make sure that the forced properties and + // DisplayWindowSettings are applied. In addition, wide-color/hdr/isTouchDevice also + // affect the Configuration. + mRoot.forAllDisplays(DisplayContent::reconfigureDisplayLocked); } - - mAtmService.updateConfiguration(null /* request to compute config */); } public void systemReady() { @@ -5258,8 +5259,6 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.systemReady(); mRoot.forAllDisplayPolicies(DisplayPolicy::systemReady); mSnapshotController.systemReady(); - mHasWideColorGamutSupport = queryWideColorGamutSupport(); - mHasHdrSupport = queryHdrSupport(); UiThread.getHandler().post(mSettingsObserver::loadSettings); IVrManager vrManager = IVrManager.Stub.asInterface( ServiceManager.getService(Context.VR_SERVICE)); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 6d7e2970e2b1..376cad734866 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -99,6 +99,7 @@ import android.window.IWindowOrganizerController; import android.window.TaskFragmentAnimationParams; import android.window.TaskFragmentCreationParams; import android.window.TaskFragmentOperation; +import android.window.TaskFragmentOrganizerToken; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -1993,8 +1994,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub creationParams.getFragmentToken(), true /* createdByOrganizer */); // Set task fragment organizer immediately, since it might have to be notified about further // actions. - taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(), - ownerActivity.getUid(), ownerActivity.info.processName); + TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer(); + taskFragment.setTaskFragmentOrganizer(organizerToken, + ownerActivity.getUid(), ownerActivity.info.processName, + mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken)); final int position; if (creationParams.getPairedPrimaryFragmentToken() != null) { // When there is a paired primary TaskFragment, we want to place the new TaskFragment diff --git a/services/core/jni/TEST_MAPPING b/services/core/jni/TEST_MAPPING index eb9db702f7f9..7f7eb480a21d 100644 --- a/services/core/jni/TEST_MAPPING +++ b/services/core/jni/TEST_MAPPING @@ -7,7 +7,6 @@ "name": "CtsVibratorTestCases", "options": [ {"exclude-annotation": "androidx.test.filters.LargeTest"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] diff --git a/services/devicepolicy/TEST_MAPPING b/services/devicepolicy/TEST_MAPPING index fccd1ecf23c0..0d5534ba74df 100644 --- a/services/devicepolicy/TEST_MAPPING +++ b/services/devicepolicy/TEST_MAPPING @@ -4,7 +4,7 @@ "name": "CtsDevicePolicyManagerTestCases", "options": [ { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" }, { "exclude-annotation": "androidx.test.filters.LargeTest" diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index d0ead141b58c..25e8475fcf42 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -21,6 +21,7 @@ import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_TARGET_USER_ID import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY; import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY; import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_HARDWARE_LIMITATION; +import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_STORAGE_LIMIT_REACHED; import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED; import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET; import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT; @@ -51,6 +52,7 @@ import android.content.pm.UserProperties; import android.os.Binder; import android.os.Bundle; import android.os.Environment; +import android.os.Parcel; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -63,6 +65,7 @@ import android.util.Xml; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.devicepolicy.flags.FlagUtils; import com.android.server.utils.Slogf; import libcore.io.IoUtils; @@ -117,6 +120,10 @@ final class DevicePolicyEngine { * Map containing the current set of admins in each user with active policies. */ private final SparseArray<Set<EnforcingAdmin>> mEnforcingAdmins; + private final SparseArray<HashMap<EnforcingAdmin, Integer>> mAdminPolicySize; + + //TODO(b/295504706) : Speak to security team to decide what to set Policy_Size_Limit + private static final int POLICY_SIZE_LIMIT = 99999; private final DeviceAdminServiceController mDeviceAdminServiceController; @@ -131,6 +138,7 @@ final class DevicePolicyEngine { mLocalPolicies = new SparseArray<>(); mGlobalPolicies = new HashMap<>(); mEnforcingAdmins = new SparseArray<>(); + mAdminPolicySize = new SparseArray<>(); } /** @@ -139,7 +147,6 @@ final class DevicePolicyEngine { * * <p>If {@code skipEnforcePolicy} is true, it sets the policies in the internal data structure * but doesn't call the enforcing logic. - * */ <V> void setLocalPolicy( @NonNull PolicyDefinition<V> policyDefinition, @@ -152,6 +159,12 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); + if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { + if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value, + policyDefinition, userId)) { + return; + } + } if (policyDefinition.isNonCoexistablePolicy()) { setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState, @@ -236,6 +249,7 @@ final class DevicePolicyEngine { } // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values + /** * Set the policy for the provided {@code policyDefinition} * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}. @@ -250,6 +264,7 @@ final class DevicePolicyEngine { } // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values + /** * Removes any previously set policy for the provided {@code policyDefinition} * (see {@link PolicyDefinition}) and {@code enforcingAdmin}. @@ -267,6 +282,10 @@ final class DevicePolicyEngine { } PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId); + if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { + decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin); + } + if (policyDefinition.isNonCoexistablePolicy()) { setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState, enforcingAdmin, /* value= */ null, userId, /* skipEnforcePolicy= */ false); @@ -392,6 +411,7 @@ final class DevicePolicyEngine { } // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values + /** * Set the policy for the provided {@code policyDefinition} * (see {@link PolicyDefinition}) and {@code enforcingAdmin} to the provided {@code value}. @@ -407,6 +427,13 @@ final class DevicePolicyEngine { Objects.requireNonNull(value); synchronized (mLock) { + PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); + if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { + if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value, + policyDefinition, UserHandle.USER_ALL)) { + return; + } + } // TODO(b/270999567): Move error handling for DISALLOW_CELLULAR_2G into the code // that honors the restriction once there's an API available if (checkFor2gFailure(policyDefinition, enforcingAdmin)) { @@ -416,8 +443,6 @@ final class DevicePolicyEngine { return; } - PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition); - boolean policyChanged = globalPolicyState.addPolicy(enforcingAdmin, value); boolean policyAppliedOnAllUsers = applyGlobalPolicyOnUsersWithLocalPoliciesLocked( policyDefinition, enforcingAdmin, value, skipEnforcePolicy); @@ -434,7 +459,7 @@ final class DevicePolicyEngine { // TODO(b/285532044): remove hack and handle properly if (!policyAppliedGlobally && policyDefinition.getPolicyKey().getIdentifier().equals( - USER_CONTROL_DISABLED_PACKAGES_POLICY)) { + USER_CONTROL_DISABLED_PACKAGES_POLICY)) { PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value; PolicyValue<Set<String>> parsedResolvedValue = (PolicyValue<Set<String>>) globalPolicyState.getCurrentResolvedPolicy(); @@ -459,6 +484,7 @@ final class DevicePolicyEngine { } // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values + /** * Removes any previously set policy for the provided {@code policyDefinition} * (see {@link PolicyDefinition}) and {@code enforcingAdmin}. @@ -472,6 +498,11 @@ final class DevicePolicyEngine { synchronized (mLock) { PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition); + + if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { + decreasePolicySizeForAdmin(policyState, enforcingAdmin); + } + boolean policyChanged = policyState.removePolicy(enforcingAdmin); if (policyChanged) { @@ -687,7 +718,6 @@ final class DevicePolicyEngine { * <p>Note that this will always return at most one item for policies that do not require * additional params (e.g. {@link PolicyDefinition#LOCK_TASK} vs * {@link PolicyDefinition#PERMISSION_GRANT(String, String)}). - * */ @NonNull <V> Set<PolicyKey> getLocalPolicyKeysSetByAdmin( @@ -723,7 +753,6 @@ final class DevicePolicyEngine { * <p>Note that this will always return at most one item for policies that do not require * additional params (e.g. {@link PolicyDefinition#LOCK_TASK} vs * {@link PolicyDefinition#PERMISSION_GRANT(String, String)}). - * */ @NonNull <V> Set<PolicyKey> getLocalPolicyKeysSetByAllAdmins( @@ -964,7 +993,7 @@ final class DevicePolicyEngine { EnforcingAdmin callingAdmin, PolicyDefinition<V> policyDefinition, int userId) { - for (EnforcingAdmin admin: policyState.getPoliciesSetByAdmins().keySet()) { + for (EnforcingAdmin admin : policyState.getPoliciesSetByAdmins().keySet()) { // We're sending a separate broadcast for the calling admin with the result. if (admin.equals(callingAdmin)) { continue; @@ -1152,7 +1181,7 @@ final class DevicePolicyEngine { try { if (packageManager.getPackageInfo(packageName, 0, userId) == null || packageManager.getActivityInfo( - policies.get(admin).getValue(), 0, userId) == null) { + policies.get(admin).getValue(), 0, userId) == null) { Slogf.e(TAG, String.format( "Persistent preferred activity in package %s not found for " + "user %d, removing policy for admin", @@ -1450,6 +1479,97 @@ final class DevicePolicyEngine { return false; } + /** + * Calculate the size of a policy in bytes + */ + + private static <V> int sizeOf(PolicyValue<V> value) { + try { + Parcel parcel = Parcel.obtain(); + parcel.writeParcelable(value, /* flags= */ 0); + + parcel.setDataPosition(0); + + byte[] bytes; + + bytes = parcel.marshall(); + return bytes.length; + } catch (Exception e) { + Log.e(TAG, "Error calculating size of policy: " + e); + return 0; + } + } + + /** + * Checks if the policy already exists and removes the current size to prevent recording the + * same policy twice. + * + * Checks if the new sum of the size of all policies is less than the maximum sum of policies + * size per admin and returns true. + * + * If the policy size limit is reached then send policy result to admin and return false. + */ + + private <V> boolean handleAdminPolicySizeLimit(PolicyState<V> policyState, EnforcingAdmin admin, + PolicyValue<V> value, PolicyDefinition policyDefinition, int userId) { + int currentSize = 0; + if (mAdminPolicySize.contains(admin.getUserId()) + && mAdminPolicySize.get( + admin.getUserId()).containsKey(admin)) { + currentSize = mAdminPolicySize.get(admin.getUserId()).get(admin); + } + if (policyState.getPoliciesSetByAdmins().containsKey(admin)) { + currentSize -= sizeOf(policyState.getPoliciesSetByAdmins().get(admin)); + } + int policySize = sizeOf(value); + if (currentSize + policySize < POLICY_SIZE_LIMIT) { + increasePolicySizeForAdmin(admin, policySize); + return true; + } else { + sendPolicyResultToAdmin( + admin, + policyDefinition, + RESULT_FAILURE_STORAGE_LIMIT_REACHED, + userId); + return false; + } + } + + /** + * Increase the int in mAdminPolicySize representing the size of the sum of all + * active policies for that admin. + */ + + private <V> void increasePolicySizeForAdmin(EnforcingAdmin admin, int policySize) { + if (!mAdminPolicySize.contains(admin.getUserId())) { + mAdminPolicySize.put(admin.getUserId(), new HashMap<>()); + } + if (!mAdminPolicySize.get(admin.getUserId()).containsKey(admin)) { + mAdminPolicySize.get(admin.getUserId()).put(admin, /* size= */ 0); + } + mAdminPolicySize.get(admin.getUserId()).put(admin, + mAdminPolicySize.get(admin.getUserId()).get(admin) + policySize); + } + + /** + * Decrease the int in mAdminPolicySize representing the size of the sum of all + * active policies for that admin. + */ + + private <V> void decreasePolicySizeForAdmin(PolicyState<V> policyState, EnforcingAdmin admin) { + if (policyState.getPoliciesSetByAdmins().containsKey(admin)) { + mAdminPolicySize.get(admin.getUserId()).put(admin, + mAdminPolicySize.get(admin.getUserId()).get(admin) - sizeOf( + policyState.getPoliciesSetByAdmins().get(admin))); + } + if (mAdminPolicySize.get(admin.getUserId()).get(admin) <= 0) { + mAdminPolicySize.get(admin.getUserId()).remove(admin); + } + if (mAdminPolicySize.get(admin.getUserId()).isEmpty()) { + mAdminPolicySize.remove(admin.getUserId()); + } + } + @NonNull private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) { synchronized (mLock) { @@ -1508,11 +1628,13 @@ final class DevicePolicyEngine { clear(); write(); } + private void clear() { synchronized (mLock) { mGlobalPolicies.clear(); mLocalPolicies.clear(); mEnforcingAdmins.clear(); + mAdminPolicySize.clear(); } } @@ -1553,7 +1675,11 @@ final class DevicePolicyEngine { private static final String TAG_POLICY_STATE_ENTRY = "policy-state-entry"; private static final String TAG_POLICY_KEY_ENTRY = "policy-key-entry"; private static final String TAG_ENFORCING_ADMINS_ENTRY = "enforcing-admins-entry"; + private static final String TAG_ENFORCING_ADMIN_AND_SIZE = "enforcing-admin-and-size"; + private static final String TAG_ENFORCING_ADMIN = "enforcing-admin"; + private static final String TAG_POLICY_SUM_SIZE = "policy-sum-size"; private static final String ATTR_USER_ID = "user-id"; + private static final String ATTR_POLICY_SUM_SIZE = "size"; private final File mFile; @@ -1595,6 +1721,7 @@ final class DevicePolicyEngine { writeLocalPoliciesInner(serializer); writeGlobalPoliciesInner(serializer); writeEnforcingAdminsInner(serializer); + writeEnforcingAdminSizeInner(serializer); } private void writeLocalPoliciesInner(TypedXmlSerializer serializer) throws IOException { @@ -1652,6 +1779,30 @@ final class DevicePolicyEngine { } } + private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer) + throws IOException { + if (FlagUtils.isDevicePolicySizeTrackingEnabled()) { + if (mAdminPolicySize != null) { + for (int i = 0; i < mAdminPolicySize.size(); i++) { + int userId = mAdminPolicySize.keyAt(i); + for (EnforcingAdmin admin : mAdminPolicySize.get( + userId).keySet()) { + serializer.startTag(/* namespace= */ null, + TAG_ENFORCING_ADMIN_AND_SIZE); + serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN); + admin.saveToXml(serializer); + serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN); + serializer.startTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE); + serializer.attributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE, + mAdminPolicySize.get(userId).get(admin)); + serializer.endTag(/* namespace= */ null, TAG_POLICY_SUM_SIZE); + serializer.endTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_AND_SIZE); + } + } + } + } + } + void readFromFileLocked() { if (!mFile.exists()) { Log.d(TAG, "" + mFile + " doesn't exist"); @@ -1689,6 +1840,9 @@ final class DevicePolicyEngine { case TAG_ENFORCING_ADMINS_ENTRY: readEnforcingAdminsInner(parser); break; + case TAG_ENFORCING_ADMIN_AND_SIZE: + readEnforcingAdminAndSizeInner(parser); + break; default: Slogf.wtf(TAG, "Unknown tag " + tag); } @@ -1767,5 +1921,37 @@ final class DevicePolicyEngine { } mEnforcingAdmins.get(admin.getUserId()).add(admin); } + + private void readEnforcingAdminAndSizeInner(TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + EnforcingAdmin admin = null; + int size = 0; + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + String tag = parser.getName(); + switch (tag) { + case TAG_ENFORCING_ADMIN: + admin = EnforcingAdmin.readFromXml(parser); + break; + case TAG_POLICY_SUM_SIZE: + size = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE); + break; + default: + Slogf.wtf(TAG, "Unknown tag " + tag); + } + } + if (admin == null) { + Slogf.wtf(TAG, "Error parsing enforcingAdmins, EnforcingAdmin is null."); + return; + } + if (size <= 0) { + Slogf.wtf(TAG, "Error parsing policy size, size is " + size); + return; + } + if (!mAdminPolicySize.contains(admin.getUserId())) { + mAdminPolicySize.put(admin.getUserId(), new HashMap<>()); + } + mAdminPolicySize.get(admin.getUserId()).put(admin, size); + } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f60493289bfb..6aa135a3eaa9 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -241,7 +241,6 @@ import static android.provider.Telephony.Carriers.ENFORCE_KEY; import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI; import static android.provider.Telephony.Carriers.INVALID_APN_ID; import static android.security.keystore.AttestationUtils.USE_INDIVIDUAL_ATTESTATION; - import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; @@ -23455,7 +23454,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public DevicePolicyState getDevicePolicyState() { Preconditions.checkCallAuthorization( hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); - return mInjector.binderWithCleanCallingIdentity(mDevicePolicyEngine::getDevicePolicyState); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java index 9fe3749755da..7e17ef111cf0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java @@ -16,6 +16,7 @@ package com.android.server.devicepolicy.flags; +import static com.android.server.devicepolicy.flags.Flags.devicePolicySizeTrackingEnabled; import static com.android.server.devicepolicy.flags.Flags.policyEngineMigrationV2Enabled; import android.os.Binder; @@ -28,4 +29,10 @@ public final class FlagUtils { return policyEngineMigrationV2Enabled(); }); } + + public static boolean isDevicePolicySizeTrackingEnabled() { + return Binder.withCleanCallingIdentity(() -> { + return devicePolicySizeTrackingEnabled(); + }); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig index 00702a9c26fa..0dde496e7285 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig +++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig @@ -5,4 +5,10 @@ flag { namespace: "enterprise" description: "V2 of the policy engine migrations for Android V" bug: "289520697" +} +flag { + name: "device_policy_size_tracking_enabled" + namespace: "enterprise" + description: "Add feature to track the total policy size and have a max threshold." + bug: "281543351" }
\ No newline at end of file diff --git a/services/foldables/devicestateprovider/OWNERS b/services/foldables/devicestateprovider/OWNERS index b2dcd0c0f3c4..573284419a48 100644 --- a/services/foldables/devicestateprovider/OWNERS +++ b/services/foldables/devicestateprovider/OWNERS @@ -1,6 +1,6 @@ akulian@google.com -kennethford@google.com jiamingliu@google.com kchyn@google.com +kennethford@google.com nickchameyev@google.com nicomazz@google.com
\ No newline at end of file diff --git a/services/foldables/devicestateprovider/TEST_MAPPING b/services/foldables/devicestateprovider/TEST_MAPPING index cd0d851adcc2..47de131803c5 100644 --- a/services/foldables/devicestateprovider/TEST_MAPPING +++ b/services/foldables/devicestateprovider/TEST_MAPPING @@ -1,7 +1,7 @@ { "presubmit": [ { - "name": "foldable-services-tests", + "name": "foldable-device-state-provider-tests", "options": [ { "exclude-annotation": "androidx.test.filters.FlakyTest" diff --git a/services/foldables/devicestateprovider/tests/AndroidTest.xml b/services/foldables/devicestateprovider/tests/AndroidTest.xml index 55e0d9cbaecf..f5fdac75f7cd 100644 --- a/services/foldables/devicestateprovider/tests/AndroidTest.xml +++ b/services/foldables/devicestateprovider/tests/AndroidTest.xml @@ -22,7 +22,7 @@ </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> - <option name="package" value="com.android.foldableslib.tests" /> + <option name="package" value="com.android.foldablesdevicestatelib.tests" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> <option name="hidden-api-checks" value="false" /> </test> diff --git a/services/net/OWNERS b/services/net/OWNERS index 62c5737a2e8e..c24680e9b06a 100644 --- a/services/net/OWNERS +++ b/services/net/OWNERS @@ -1,2 +1,2 @@ set noparent -file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking +file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking diff --git a/services/tests/InputMethodSystemServerTests/TEST_MAPPING b/services/tests/InputMethodSystemServerTests/TEST_MAPPING index cedbfd2b2dde..de9f771a2a36 100644 --- a/services/tests/InputMethodSystemServerTests/TEST_MAPPING +++ b/services/tests/InputMethodSystemServerTests/TEST_MAPPING @@ -4,7 +4,6 @@ "name": "FrameworksInputMethodSystemServerTests", "options": [ {"include-filter": "com.android.server.inputmethod"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] @@ -15,7 +14,6 @@ "name": "FrameworksImeTests", "options": [ {"include-filter": "com.android.inputmethodservice"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java index 4a2bf75b4d2c..5d3eba86a725 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java @@ -17,24 +17,22 @@ package com.android.server.pm; import static com.android.compatibility.common.util.ShellUtils.runShellCommand; + import static com.google.common.truth.Truth.assertWithMessage; + import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; + import static java.lang.reflect.Modifier.isFinal; import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.AppGlobals; -import android.content.IIntentReceiver; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Postsubmit; -import android.util.SparseArray; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -87,18 +85,6 @@ public class PackageManagerServiceTest { @Test public void testPackageRemoval() { class PackageSenderImpl implements PackageSender { - public void sendPackageBroadcast(final String action, final String pkg, - final Bundle extras, final int flags, final String targetPkg, - final IIntentReceiver finishedReceiver, final int[] userIds, - int[] instantUserIds, SparseArray<int[]> broadcastAllowList, - @Nullable Bundle bOptions) { - } - - public void sendPackageAddedForNewUsers(@NonNull Computer snapshot, String packageName, - boolean sendBootComplete, boolean includeStopped, int appId, - int[] userIds, int[] instantUserIds, boolean isArchived, int dataLoaderType) { - } - @Override public void notifyPackageAdded(String packageName, int uid) { } @@ -113,9 +99,8 @@ public class PackageManagerServiceTest { } } - PackageSenderImpl sender = new PackageSenderImpl(); PackageSetting setting = null; - PackageRemovedInfo pri = new PackageRemovedInfo(sender); + PackageRemovedInfo pri = new PackageRemovedInfo(); // Initial conditions: nothing there Assert.assertNull(pri.mRemovedUsers); diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt index 823ce4555ca8..05477197b991 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionResetTest.kt @@ -112,4 +112,4 @@ class AppIdPermissionPolicyPermissionResetTest : BaseAppIdPermissionPolicyTest() @JvmStatic fun data(): Array<Action> = Action.values() } -}
\ No newline at end of file +} diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt index f085bd7fd7f0..c44b2c50258e 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyPermissionStatesTest.kt @@ -43,8 +43,7 @@ class AppIdPermissionPolicyPermissionStatesTest : BaseAppIdPermissionPolicyTest( @Parameterized.Parameter(0) lateinit var action: Action @Before - override fun setUp() { - super.setUp() + fun setUp() { if (action == Action.ON_USER_ADDED) { createUserState(USER_ID_NEW) } @@ -881,4 +880,4 @@ class AppIdPermissionPolicyPermissionStatesTest : BaseAppIdPermissionPolicyTest( @JvmStatic fun data(): Array<Action> = Action.values() } -}
\ No newline at end of file +} diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt new file mode 100644 index 000000000000..e4e336845fca --- /dev/null +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.test + +import android.content.pm.PermissionGroupInfo +import android.content.pm.PermissionInfo +import com.android.server.permission.access.GetStateScope +import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.permission.AppIdPermissionPolicy +import com.android.server.permission.access.permission.Permission +import com.android.server.permission.access.permission.PermissionFlags +import com.android.server.testutils.mock +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import org.junit.Test +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +class AppIdPermissionPolicyTest : BaseAppIdPermissionPolicyTest() { + @Test + fun testOnAppIdRemoved_appIdIsRemoved_permissionFlagsCleared() { + val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0) + val permissionOwnerPackageState = mockPackageState( + APP_ID_0, + mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission)) + ) + val requestingPackageState = mockPackageState( + APP_ID_1, + mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)) + ) + addPackageState(permissionOwnerPackageState) + addPackageState(requestingPackageState) + addPermission(parsedPermission) + setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.INSTALL_GRANTED) + + mutateState { + with(appIdPermissionPolicy) { + onAppIdRemoved(APP_ID_1) + } + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = 0 + assertWithMessage( + "After onAppIdRemoved() is called for appId $APP_ID_1 that requests a permission" + + " owns by appId $APP_ID_0 with existing permission flags. The actual permission" + + " flags $actualFlags should be null" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnStorageVolumeMounted_nonSystemAppAfterNonSystemUpdate_remainsRevoked() { + val permissionOwnerPackageState = mockPackageState(APP_ID_0, mockSimpleAndroidPackage()) + val installedPackageState = mockPackageState( + APP_ID_1, + mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)) + ) + addPackageState(permissionOwnerPackageState) + addPackageState(installedPackageState) + addPermission(defaultPermission) + val oldFlags = PermissionFlags.INSTALL_REVOKED + setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags) + + mutateState { + with(appIdPermissionPolicy) { + onStorageVolumeMounted(null, listOf(installedPackageState.packageName), false) + } + } + + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onStorageVolumeMounted() is called for a non-system app that requests a normal" + + " permission with existing INSTALL_REVOKED flag after a non-system-update" + + " (such as an OTA update), the actual permission flags should remain revoked." + + " The actual permission flags $actualFlags should match the expected flags" + + " $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageRemoved_packageIsRemoved_permissionDefinitionsAndStatesAreUpdated() { + val permissionOwnerPackageState = mockPackageState( + APP_ID_0, + mockAndroidPackage( + PACKAGE_NAME_0, + requestedPermissions = setOf(PERMISSION_NAME_0), + permissions = listOf(defaultPermission) + ) + ) + val requestingPackageState = mockPackageState( + APP_ID_1, + mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)) + ) + addPackageState(permissionOwnerPackageState) + addPackageState(requestingPackageState) + addPermission(defaultPermission) + val oldFlags = PermissionFlags.INSTALL_GRANTED + setPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0, oldFlags) + setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags) + + mutateState { + removePackageState(permissionOwnerPackageState) + with(appIdPermissionPolicy) { + onPackageRemoved(PACKAGE_NAME_0, APP_ID_0) + } + } + + assertWithMessage( + "After onPackageRemoved() is called for a permission owner, the permission" + + " definitions owned by this package should be removed" + ) + .that(getPermission(PERMISSION_NAME_0)) + .isNull() + + val app0ActualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) + val app0ExpectedNewFlags = 0 + assertWithMessage( + "After onPackageRemoved() is called for a permission owner, the permission states of" + + " this app should be trimmed. The actual permission flags $app0ActualFlags should" + + " match the expected flags $app0ExpectedNewFlags" + ) + .that(app0ActualFlags) + .isEqualTo(app0ExpectedNewFlags) + + val app1ActualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val app1ExpectedNewFlags = PermissionFlags.INSTALL_REVOKED + assertWithMessage( + "After onPackageRemoved() is called for a permission owner, the permission states of" + + " the permission requester should remain unchanged. The actual permission flags" + + " $app1ActualFlags should match the expected flags $app1ExpectedNewFlags" + ) + .that(app1ActualFlags) + .isEqualTo(app1ExpectedNewFlags) + } + + @Test + fun testOnPackageInstalled_nonSystemAppIsInstalled_upgradeExemptFlagIsCleared() { + val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.UPGRADE_EXEMPT + testOnPackageInstalled( + oldFlags, + permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED + ) {} + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.SOFT_RESTRICTED + assertWithMessage( + "After onPackageInstalled() is called for a non-system app that requests a runtime" + + " soft restricted permission, UPGRADE_EXEMPT flag should be removed. The actual" + + " permission flags $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageInstalled_systemAppIsInstalled_upgradeExemptFlagIsRetained() { + val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.UPGRADE_EXEMPT + testOnPackageInstalled( + oldFlags, + permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED, + isInstalledPackageSystem = true + ) {} + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageInstalled() is called for a system app that requests a runtime" + + " soft restricted permission, UPGRADE_EXEMPT flag should be retained. The actual" + + " permission flags $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageInstalled_requestedPermissionAlsoRequestedBySystemApp_exemptFlagIsRetained() { + val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.UPGRADE_EXEMPT + testOnPackageInstalled( + oldFlags, + permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED + ) { + val systemAppPackageState = mockPackageState( + APP_ID_1, + mockAndroidPackage(PACKAGE_NAME_2, requestedPermissions = setOf(PERMISSION_NAME_0)), + isSystem = true + ) + addPackageState(systemAppPackageState) + } + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageInstalled() is called for a non-system app that requests a runtime" + + " soft restricted permission, and that permission is also requested by a system" + + " app in the same appId, UPGRADE_EXEMPT flag should be retained. The actual" + + " permission flags $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageInstalled_restrictedPermissionsNotExempt_getsRestrictionFlags() { + val oldFlags = PermissionFlags.RESTRICTION_REVOKED + testOnPackageInstalled( + oldFlags, + permissionInfoFlags = PermissionInfo.FLAG_HARD_RESTRICTED + ) {} + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = oldFlags + assertWithMessage( + "After onPackageInstalled() is called for a non-system app that requests a runtime" + + " hard restricted permission that is not exempted. The actual permission flags" + + " $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + @Test + fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() { + val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.INSTALLER_EXEMPT + testOnPackageInstalled( + oldFlags, + permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED + ) {} + val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) + val expectedNewFlags = PermissionFlags.INSTALLER_EXEMPT + assertWithMessage( + "After onPackageInstalled() is called for a non-system app that requests a runtime" + + " soft restricted permission that is exempted. The actual permission flags" + + " $actualFlags should match the expected flags $expectedNewFlags" + ) + .that(actualFlags) + .isEqualTo(expectedNewFlags) + } + + private fun testOnPackageInstalled( + oldFlags: Int, + permissionInfoFlags: Int = 0, + isInstalledPackageSystem: Boolean = false, + additionalSetup: () -> Unit + ) { + val parsedPermission = mockParsedPermission( + PERMISSION_NAME_0, + PACKAGE_NAME_0, + protectionLevel = PermissionInfo.PROTECTION_DANGEROUS, + flags = permissionInfoFlags + ) + val permissionOwnerPackageState = mockPackageState( + APP_ID_0, + mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission)) + ) + addPackageState(permissionOwnerPackageState) + addPermission(parsedPermission) + + additionalSetup() + + mutateState { + val installedPackageState = mockPackageState( + APP_ID_1, + mockAndroidPackage( + PACKAGE_NAME_1, + requestedPermissions = setOf(PERMISSION_NAME_0), + ), + isSystem = isInstalledPackageSystem, + ) + addPackageState(installedPackageState, newState) + setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, oldFlags, newState) + with(appIdPermissionPolicy) { + onPackageInstalled(installedPackageState, USER_ID_0) + } + } + } + + @Test + fun testOnStateMutated_notEmpty_isCalledForEachListener() { + val mockListener = mock<AppIdPermissionPolicy.OnPermissionFlagsChangedListener> {} + appIdPermissionPolicy.addOnPermissionFlagsChangedListener(mockListener) + + GetStateScope(oldState).apply { + with(appIdPermissionPolicy) { + onStateMutated() + } + } + + verify(mockListener, times(1)).onStateMutated() + } + + @Test + fun testGetPermissionTrees() { + val permissionTrees: IndexedMap<String, Permission> + GetStateScope(oldState).apply { + with(appIdPermissionPolicy) { + permissionTrees = getPermissionTrees() + } + } + + assertThat(oldState.systemState.permissionTrees).isEqualTo(permissionTrees) + } + + @Test + fun testFindPermissionTree() { + val permissionTree = createSimplePermission(isTree = true) + val actualPermissionTree: Permission? + oldState.mutateSystemState().mutatePermissionTrees()[PERMISSION_TREE_NAME] = permissionTree + + GetStateScope(oldState).apply { + with(appIdPermissionPolicy) { + actualPermissionTree = findPermissionTree(PERMISSION_BELONGS_TO_A_TREE) + } + } + + assertThat(actualPermissionTree).isEqualTo(permissionTree) + } + + @Test + fun testAddPermissionTree() { + val permissionTree = createSimplePermission(isTree = true) + + mutateState { + with(appIdPermissionPolicy) { + addPermissionTree(permissionTree) + } + } + + assertThat(newState.systemState.permissionTrees[PERMISSION_TREE_NAME]) + .isEqualTo(permissionTree) + } + + @Test + fun testGetPermissionGroups() { + val permissionGroups: IndexedMap<String, PermissionGroupInfo> + GetStateScope(oldState).apply { + with(appIdPermissionPolicy) { + permissionGroups = getPermissionGroups() + } + } + + assertThat(oldState.systemState.permissionGroups).isEqualTo(permissionGroups) + } + + @Test + fun testGetPermissions() { + val permissions: IndexedMap<String, Permission> + GetStateScope(oldState).apply { + with(appIdPermissionPolicy) { + permissions = getPermissions() + } + } + + assertThat(oldState.systemState.permissions).isEqualTo(permissions) + } + + @Test + fun testAddPermission() { + val permission = createSimplePermission() + + mutateState { + with(appIdPermissionPolicy) { + addPermission(permission) + } + } + + assertThat(newState.systemState.permissions[PERMISSION_NAME_0]).isEqualTo(permission) + } + + @Test + fun testRemovePermission() { + val permission = createSimplePermission() + + mutateState { + with(appIdPermissionPolicy) { + addPermission(permission) + removePermission(permission) + } + } + + assertThat(newState.systemState.permissions[PERMISSION_NAME_0]).isNull() + } + + @Test + fun testGetUidPermissionFlags() { + val uidPermissionFlags: IndexedMap<String, Int>? + GetStateScope(oldState).apply { + with(appIdPermissionPolicy) { + uidPermissionFlags = getUidPermissionFlags(APP_ID_0, USER_ID_0) + } + } + + assertThat(oldState.userStates[USER_ID_0]!!.appIdPermissionFlags[APP_ID_0]) + .isEqualTo(uidPermissionFlags) + } + + @Test + fun testUpdateAndGetPermissionFlags() { + val flags = PermissionFlags.INSTALL_GRANTED + var actualFlags = 0 + mutateState { + with(appIdPermissionPolicy) { + updatePermissionFlags( + APP_ID_0, + USER_ID_0, + PERMISSION_NAME_0, + PermissionFlags.MASK_ALL, + flags + ) + actualFlags = getPermissionFlags(APP_ID_0, USER_ID_0, PERMISSION_NAME_0) + } + } + + assertThat(actualFlags).isEqualTo(flags) + } +} diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt index 7966c5c4d961..ec84bc329674 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt @@ -34,7 +34,6 @@ import com.android.server.permission.access.MutateStateScope import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports import com.android.server.permission.access.permission.AppIdPermissionPolicy import com.android.server.permission.access.permission.Permission -import com.android.server.permission.access.permission.PermissionFlags import com.android.server.permission.access.util.hasBits import com.android.server.pm.parsing.PackageInfoUtils import com.android.server.pm.pkg.AndroidPackage @@ -45,10 +44,8 @@ import com.android.server.pm.pkg.component.ParsedPermissionGroup import com.android.server.testutils.any import com.android.server.testutils.mock import com.android.server.testutils.whenever -import com.google.common.truth.Truth.assertWithMessage import org.junit.Before import org.junit.Rule -import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyLong @@ -56,7 +53,7 @@ import org.mockito.ArgumentMatchers.anyLong * Mocking unit test for AppIdPermissionPolicy. */ @RunWith(AndroidJUnit4::class) -open class BaseAppIdPermissionPolicyTest { +abstract class BaseAppIdPermissionPolicyTest { protected lateinit var oldState: MutableAccessState protected lateinit var newState: MutableAccessState @@ -80,7 +77,7 @@ open class BaseAppIdPermissionPolicyTest { .build() @Before - open fun setUp() { + fun baseSetUp() { oldState = MutableAccessState() createUserState(USER_ID_0) oldState.mutateExternalState().setPackageStates(ArrayMap()) @@ -139,78 +136,6 @@ open class BaseAppIdPermissionPolicyTest { } } - @Test - fun testOnAppIdRemoved_appIdIsRemoved_permissionFlagsCleared() { - val parsedPermission = mockParsedPermission(PERMISSION_NAME_0, PACKAGE_NAME_0) - val permissionOwnerPackageState = mockPackageState( - APP_ID_0, - mockAndroidPackage(PACKAGE_NAME_0, permissions = listOf(parsedPermission)) - ) - val requestingPackageState = mockPackageState( - APP_ID_1, - mockAndroidPackage(PACKAGE_NAME_1, requestedPermissions = setOf(PERMISSION_NAME_0)) - ) - addPackageState(permissionOwnerPackageState) - addPackageState(requestingPackageState) - addPermission(parsedPermission) - setPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0, PermissionFlags.INSTALL_GRANTED) - - mutateState { - with(appIdPermissionPolicy) { - onAppIdRemoved(APP_ID_1) - } - } - - val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0) - val expectedNewFlags = 0 - assertWithMessage( - "After onAppIdRemoved() is called for appId $APP_ID_1 that requests a permission" + - " owns by appId $APP_ID_0 with existing permission flags. The actual permission" + - " flags $actualFlags should be null" - ) - .that(actualFlags) - .isEqualTo(expectedNewFlags) - } - - @Test - fun testOnPackageRemoved_packageIsRemoved_permissionsAreTrimmedAndStatesAreEvaluated() { - // TODO - // shouldn't reuse test cases because it's really different despite it's also for - // trim permission states. It's different because it's package removal - } - - @Test - fun testOnPackageInstalled_nonSystemAppIsInstalled_upgradeExemptFlagIsCleared() { - // TODO - // should be fine for it to be its own test cases and not to re-use - // clearRestrictedPermissionImplicitExemption - } - - @Test - fun testOnPackageInstalled_systemAppIsInstalled_upgradeExemptFlagIsRetained() { - // TODO - } - - @Test - fun testOnPackageInstalled_requestedPermissionAlsoRequestedBySystemApp_exemptFlagIsRetained() { - // TODO - } - - @Test - fun testOnPackageInstalled_restrictedPermissionsNotExempt_getsRestrictionFlags() { - // TODO - } - - @Test - fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() { - // TODO - } - - @Test - fun testOnStateMutated_notEmpty_isCalledForEachListener() { - // TODO - } - /** * Mock an AndroidPackage with PACKAGE_NAME_0, PERMISSION_NAME_0 and PERMISSION_GROUP_NAME_0 */ @@ -221,6 +146,15 @@ open class BaseAppIdPermissionPolicyTest { permissions = listOf(defaultPermissionTree, defaultPermission) ) + protected fun createSimplePermission(isTree: Boolean = false): Permission { + val parsedPermission = if (isTree) { defaultPermissionTree } else { defaultPermission } + val permissionInfo = PackageInfoUtils.generatePermissionInfo( + parsedPermission, + PackageManager.GET_META_DATA.toLong() + )!! + return Permission(permissionInfo, true, Permission.TYPE_MANIFEST, APP_ID_0) + } + protected inline fun mutateState(action: MutateStateScope.() -> Unit) { newState = oldState.toMutable() MutateStateScope(oldState, newState).action() @@ -330,15 +264,26 @@ open class BaseAppIdPermissionPolicyTest { ) { state.mutateExternalState().apply { setPackageStates( - packageStates.toMutableMap().apply { - put(packageState.packageName, packageState) - } + packageStates.toMutableMap().apply { put(packageState.packageName, packageState) } ) mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() } .add(packageState.packageName) } } + protected fun removePackageState( + packageState: PackageState, + state: MutableAccessState = oldState + ) { + state.mutateExternalState().apply { + setPackageStates( + packageStates.toMutableMap().apply { remove(packageState.packageName) } + ) + mutateAppIdPackageNames().mutateOrPut(packageState.appId) { MutableIndexedListSet() } + .remove(packageState.packageName) + } + } + protected fun addDisabledSystemPackageState( packageState: PackageState, state: MutableAccessState = oldState @@ -429,6 +374,7 @@ open class BaseAppIdPermissionPolicyTest { @JvmStatic protected val PERMISSION_NAME_0 = "permissionName0" @JvmStatic protected val PERMISSION_NAME_1 = "permissionName1" @JvmStatic protected val PERMISSION_NAME_2 = "permissionName2" + @JvmStatic protected val PERMISSION_BELONGS_TO_A_TREE = "permissionTree.permission" @JvmStatic protected val PERMISSION_READ_EXTERNAL_STORAGE = Manifest.permission.READ_EXTERNAL_STORAGE @JvmStatic protected val PERMISSION_POST_NOTIFICATIONS = diff --git a/services/tests/RemoteProvisioningServiceTests/OWNERS b/services/tests/RemoteProvisioningServiceTests/OWNERS index 348f94048311..ea6dc727c7b2 100644 --- a/services/tests/RemoteProvisioningServiceTests/OWNERS +++ b/services/tests/RemoteProvisioningServiceTests/OWNERS @@ -1 +1 @@ -file:platform/frameworks/base:master:/core/java/android/security/rkp/OWNERS +file:platform/frameworks/base:main:/core/java/android/security/rkp/OWNERS diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index a23539e37409..2396905aecbf 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -75,6 +75,7 @@ import android.hardware.display.Curve; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.hardware.display.DisplayViewport; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; @@ -87,6 +88,7 @@ import android.media.projection.IMediaProjectionManager; import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.MessageQueue; import android.os.Process; import android.os.RemoteException; @@ -118,11 +120,11 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.sensors.SensorManagerInternal; import com.android.server.wm.WindowManagerInternal; -import com.google.common.truth.Expect; - import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; +import com.google.common.truth.Expect; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -198,9 +200,10 @@ public class DisplayManagerServiceTest { @Override LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, - Handler handler, DisplayAdapter.Listener displayAdapterListener) { + Handler handler, DisplayAdapter.Listener displayAdapterListener, + DisplayManagerFlags flags) { return new LocalDisplayAdapter(syncRoot, context, handler, - displayAdapterListener, new LocalDisplayAdapter.Injector() { + displayAdapterListener, flags, new LocalDisplayAdapter.Injector() { @Override public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { return mSurfaceControlProxy; @@ -244,8 +247,14 @@ public class DisplayManagerServiceTest { @Override LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context, - Handler handler, DisplayAdapter.Listener displayAdapterListener) { - return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener, + Handler handler, DisplayAdapter.Listener displayAdapterListener, + DisplayManagerFlags flags) { + return new LocalDisplayAdapter( + syncRoot, + context, + handler, + displayAdapterListener, + flags, new LocalDisplayAdapter.Injector() { @Override public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { @@ -2445,6 +2454,86 @@ public class DisplayManagerServiceTest { assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED, EVENT_DISPLAY_DISCONNECTED); } + + @Test + public void testRegisterDisplayOffloader_whenEnabled_DisplayHasDisplayOffloadSession() { + when(mMockFlags.isDisplayOffloadEnabled()).thenReturn(true); + // set up DisplayManager + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + // set up display + FakeDisplayDevice displayDevice = + createFakeDisplayDevice(displayManager, new float[]{60f}, Display.DEFAULT_DISPLAY); + initDisplayPowerController(localService); + LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper(); + LogicalDisplay display = + logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true); + int displayId = display.getDisplayIdLocked(); + + // Register DisplayOffloader. + DisplayOffloader mockDisplayOffloader = mock(DisplayOffloader.class); + localService.registerDisplayOffloader(displayId, mockDisplayOffloader); + + assertThat(display.getDisplayOffloadSessionLocked().getDisplayOffloader()).isEqualTo( + mockDisplayOffloader); + } + + @Test + public void testRegisterDisplayOffloader_whenDisabled_DisplayHasNoDisplayOffloadSession() { + when(mMockFlags.isDisplayOffloadEnabled()).thenReturn(false); + // set up DisplayManager + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + // set up display + FakeDisplayDevice displayDevice = + createFakeDisplayDevice(displayManager, new float[]{60f}, Display.DEFAULT_DISPLAY); + initDisplayPowerController(localService); + LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper(); + LogicalDisplay display = + logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true); + int displayId = display.getDisplayIdLocked(); + + // Register DisplayOffloader. + DisplayOffloader mockDisplayOffloader = mock(DisplayOffloader.class); + localService.registerDisplayOffloader(displayId, mockDisplayOffloader); + + assertThat(display.getDisplayOffloadSessionLocked()).isNull(); + } + + private void initDisplayPowerController(DisplayManagerInternal localService) { + localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() { + @Override + public void onStateChanged() { + + } + + @Override + public void onProximityPositive() { + + } + + @Override + public void onProximityNegative() { + + } + + @Override + public void onDisplayStateChange(boolean allInactive, boolean allOff) { + + } + + @Override + public void acquireSuspendBlocker(String id) { + + } + + @Override + public void releaseSuspendBlocker(String id) { + + } + }, new Handler(Looper.getMainLooper()), mSensorManager); + } + private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled) { DisplayManagerService displayManager = new DisplayManagerService(mContext, mShortMockedInjector); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index a56b59a4a481..dca69eb6dd98 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -44,6 +44,7 @@ import android.hardware.Sensor; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.Handler; @@ -123,6 +124,9 @@ public final class DisplayPowerController2Test { private Handler mHandler; private DisplayPowerControllerHolder mHolder; private Sensor mProxSensor; + private DisplayManagerInternal.DisplayOffloader mDisplayOffloader; + private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; + @Mock private DisplayPowerCallbacks mDisplayPowerCallbacksMock; @Mock @@ -1409,6 +1413,111 @@ public final class DisplayPowerController2Test { BRIGHTNESS_RAMP_DECREASE_MAX_IDLE); } + @Test + public void testDozeScreenStateOverride_toSupportedOffloadStateFromDoze_DisplayStateChanges() { + // set up. + int initState = Display.STATE_DOZE; + int supportedTargetState = Display.STATE_DOZE_SUSPEND; + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + doAnswer(invocation -> { + when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); + return null; + }).when(mHolder.displayPowerState).setScreenState(anyInt()); + // init displayoffload session and support offloading. + initDisplayOffloadSession(); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + + // start with DOZE. + when(mHolder.displayPowerState.getScreenState()).thenReturn(initState); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_DOZE; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + mHolder.dpc.overrideDozeScreenState(supportedTargetState); + advanceTime(1); // Run updatePowerState + + verify(mHolder.displayPowerState).setScreenState(supportedTargetState); + } + + @Test + public void testDozeScreenStateOverride_toUnSupportedOffloadStateFromDoze_stateRemains() { + // set up. + int initState = Display.STATE_DOZE; + int unSupportedTargetState = Display.STATE_ON; + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + doAnswer(invocation -> { + when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); + return null; + }).when(mHolder.displayPowerState).setScreenState(anyInt()); + // init displayoffload session and support offloading. + initDisplayOffloadSession(); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + + // start with DOZE. + when(mHolder.displayPowerState.getScreenState()).thenReturn(initState); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_DOZE; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + mHolder.dpc.overrideDozeScreenState(unSupportedTargetState); + advanceTime(1); // Run updatePowerState + + verify(mHolder.displayPowerState, never()).setScreenState(anyInt()); + } + + @Test + public void testDozeScreenStateOverride_toSupportedOffloadStateFromOFF_stateRemains() { + // set up. + int initState = Display.STATE_OFF; + int supportedTargetState = Display.STATE_DOZE_SUSPEND; + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + doAnswer(invocation -> { + when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); + return null; + }).when(mHolder.displayPowerState).setScreenState(anyInt()); + // init displayoffload session and support offloading. + initDisplayOffloadSession(); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + + // start with OFF. + when(mHolder.displayPowerState.getScreenState()).thenReturn(initState); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_OFF; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + mHolder.dpc.overrideDozeScreenState(supportedTargetState); + advanceTime(1); // Run updatePowerState + + verify(mHolder.displayPowerState, never()).setScreenState(anyInt()); + } + + private void initDisplayOffloadSession() { + mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() { + @Override + public boolean startOffload() { + return true; + } + + @Override + public void stopOffload() {} + }); + + mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() { + @Override + public void setDozeStateOverride(int displayState) {} + + @Override + public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() { + return mDisplayOffloader; + } + }; + } + /** * Creates a mock and registers it to {@link LocalServices}. */ @@ -1742,9 +1851,9 @@ public final class DisplayPowerController2Test { BrightnessRangeController getBrightnessRangeController( HighBrightnessModeController hbmController, Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler, - DisplayManagerFlags flags) { + DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) { return new BrightnessRangeController(hbmController, modeChangeCallback, - displayDeviceConfig, mHdrClamper, mFlags); + displayDeviceConfig, mHdrClamper, mFlags, displayToken, info); } @Override diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 05721174b71f..edaa1d5013bf 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -44,6 +44,7 @@ import android.hardware.Sensor; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.Handler; @@ -122,6 +123,8 @@ public final class DisplayPowerControllerTest { private Handler mHandler; private DisplayPowerControllerHolder mHolder; private Sensor mProxSensor; + private DisplayManagerInternal.DisplayOffloader mDisplayOffloader; + private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; @Mock private DisplayPowerCallbacks mDisplayPowerCallbacksMock; @@ -1364,6 +1367,110 @@ public final class DisplayPowerControllerTest { verify(mHolder.animator).setAnimationTimeLimits(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE, BRIGHTNESS_RAMP_DECREASE_MAX_IDLE); } + @Test + public void testDozeScreenStateOverride_toSupportedOffloadStateFromDoze_DisplayStateChanges() { + // set up. + int initState = Display.STATE_DOZE; + int supportedTargetState = Display.STATE_DOZE_SUSPEND; + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + doAnswer(invocation -> { + when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); + return null; + }).when(mHolder.displayPowerState).setScreenState(anyInt()); + // init displayoffload session and support offloading. + initDisplayOffloadSession(); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + + // start with DOZE. + when(mHolder.displayPowerState.getScreenState()).thenReturn(initState); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_DOZE; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + mHolder.dpc.overrideDozeScreenState(supportedTargetState); + advanceTime(1); // Run updatePowerState + + verify(mHolder.displayPowerState).setScreenState(supportedTargetState); + } + + @Test + public void testDozeScreenStateOverride_toUnSupportedOffloadStateFromDoze_stateRemains() { + // set up. + int initState = Display.STATE_DOZE; + int unSupportedTargetState = Display.STATE_ON; + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + doAnswer(invocation -> { + when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); + return null; + }).when(mHolder.displayPowerState).setScreenState(anyInt()); + // init displayoffload session and support offloading. + initDisplayOffloadSession(); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + + // start with DOZE. + when(mHolder.displayPowerState.getScreenState()).thenReturn(initState); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_DOZE; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + mHolder.dpc.overrideDozeScreenState(unSupportedTargetState); + advanceTime(1); // Run updatePowerState + + verify(mHolder.displayPowerState, never()).setScreenState(anyInt()); + } + + @Test + public void testDozeScreenStateOverride_toSupportedOffloadStateFromOFF_stateRemains() { + // set up. + int initState = Display.STATE_OFF; + int supportedTargetState = Display.STATE_DOZE_SUSPEND; + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + doAnswer(invocation -> { + when(mHolder.displayPowerState.getScreenState()).thenReturn(invocation.getArgument(0)); + return null; + }).when(mHolder.displayPowerState).setScreenState(anyInt()); + // init displayoffload session and support offloading. + initDisplayOffloadSession(); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + + // start with OFF. + when(mHolder.displayPowerState.getScreenState()).thenReturn(initState); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_OFF; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + mHolder.dpc.overrideDozeScreenState(supportedTargetState); + advanceTime(1); // Run updatePowerState + + verify(mHolder.displayPowerState, never()).setScreenState(anyInt()); + } + + private void initDisplayOffloadSession() { + mDisplayOffloader = spy(new DisplayManagerInternal.DisplayOffloader() { + @Override + public boolean startOffload() { + return true; + } + + @Override + public void stopOffload() {} + }); + + mDisplayOffloadSession = new DisplayManagerInternal.DisplayOffloadSession() { + @Override + public void setDozeStateOverride(int displayState) {} + + @Override + public DisplayManagerInternal.DisplayOffloader getDisplayOffloader() { + return mDisplayOffloader; + } + }; + } private void advanceTime(long timeMs) { mClock.fastForward(timeMs); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index 6cde5e363657..147e8f22aab6 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -32,12 +32,15 @@ import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Rect; +import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession; +import android.hardware.display.DisplayManagerInternal.DisplayOffloader; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -55,6 +58,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.internal.R; import com.android.server.LocalServices; import com.android.server.display.LocalDisplayAdapter.BacklightAdapter; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; @@ -72,6 +76,7 @@ import org.mockito.quality.Strictness; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -107,8 +112,15 @@ public class LocalDisplayAdapterTest { private LightsManager mMockedLightsManager; @Mock private LogicalLight mMockedBacklight; + @Mock + private DisplayManagerFlags mFlags; + private Handler mHandler; + private DisplayOffloadSession mDisplayOffloadSession; + + private DisplayOffloader mDisplayOffloader; + private TestListener mListener = new TestListener(); private LinkedList<DisplayAddress.Physical> mAddresses = new LinkedList<>(); @@ -120,6 +132,8 @@ public class LocalDisplayAdapterTest { private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f }; private static final int[] BACKLIGHT_RANGE = { 1, 255 }; private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f }; + private static final List<Integer> mDisplayOffloadSupportedStates + = new ArrayList<>(List.of(Display.STATE_DOZE_SUSPEND)); @Before public void setUp() throws Exception { @@ -134,7 +148,7 @@ public class LocalDisplayAdapterTest { mInjector = new Injector(); when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true); mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler, - mListener, mInjector); + mListener, mFlags, mInjector); spyOn(mAdapter); doReturn(mMockedContext).when(mAdapter).getOverlayContext(); @@ -185,6 +199,8 @@ public class LocalDisplayAdapterTest { when(mMockedResources.getIntArray( com.android.internal.R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate)) .thenReturn(new int[]{}); + doReturn(true).when(mFlags).isDisplayOffloadEnabled(); + initDisplayOffloadSession(); } @After @@ -1109,6 +1125,72 @@ public class LocalDisplayAdapterTest { assertThat(info.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP).isEqualTo(0); } + + @Test + public void test_displayStateToSupportedState_DisplayOffloadStart() + throws InterruptedException { + // prepare a display. + FakeDisplay display = new FakeDisplay(PORT_A); + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + DisplayDevice displayDevice = mListener.addedDisplays.get(0); + + for (Integer supportedState : mDisplayOffloadSupportedStates) { + Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked( + supportedState, 0, 0, mDisplayOffloadSession); + changeStateRunnable.run(); + + verify(mDisplayOffloader).startOffload(); + } + } + + @Test + public void test_displayStateToDozeFromDozeSuspend_DisplayOffloadStop() + throws InterruptedException { + // prepare a display. + FakeDisplay display = new FakeDisplay(PORT_A); + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + DisplayDevice displayDevice = mListener.addedDisplays.get(0); + + Runnable changeStateToDozeSuspendRunnable = displayDevice.requestDisplayStateLocked( + Display.STATE_DOZE_SUSPEND, 0, 0, mDisplayOffloadSession); + Runnable changeStateToDozeRunnable = displayDevice.requestDisplayStateLocked( + Display.STATE_DOZE, 0, 0, mDisplayOffloadSession); + changeStateToDozeSuspendRunnable.run(); + changeStateToDozeRunnable.run(); + + verify(mDisplayOffloader).stopOffload(); + } + + private void initDisplayOffloadSession() { + mDisplayOffloader = spy(new DisplayOffloader() { + @Override + public boolean startOffload() { + return true; + } + + @Override + public void stopOffload() {} + }); + + mDisplayOffloadSession = new DisplayOffloadSession() { + @Override + public void setDozeStateOverride(int displayState) {} + + @Override + public DisplayOffloader getDisplayOffloader() { + return mDisplayOffloader; + } + }; + } + private void setupCutoutAndRoundedCorners() { String sampleCutout = "M 507,66\n" + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n" diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java index 37d966d044c5..c63fac9832e9 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java @@ -20,6 +20,12 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.IBinder; import android.os.PowerManager; import androidx.test.filters.SmallTest; @@ -31,6 +37,7 @@ import com.android.server.testutils.TestHandler; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -40,7 +47,20 @@ import java.util.Map; @SmallTest public class HdrClamperTest { - public static final float FLOAT_TOLERANCE = 0.0001f; + private static final float FLOAT_TOLERANCE = 0.0001f; + private static final long SEND_TIME_TOLERANCE = 100; + + private static final HdrBrightnessData TEST_HDR_DATA = new HdrBrightnessData( + Map.of(500f, 0.6f), + /* brightnessIncreaseDebounceMillis= */ 1000, + /* brightnessIncreaseDurationMillis= */ 2000, + /* brightnessDecreaseDebounceMillis= */ 3000, + /* brightnessDecreaseDurationMillis= */4000 + ); + + private static final int WIDTH = 600; + private static final int HEIGHT = 800; + private static final float MIN_HDR_PERCENT = 0.5f; @Rule public MockitoRule mRule = MockitoJUnit.rule(); @@ -48,17 +68,31 @@ public class HdrClamperTest { @Mock private BrightnessClamperController.ClamperChangeListener mMockListener; + @Mock + private IBinder mMockBinder; + + @Mock + private HdrClamper.Injector mMockInjector; + + @Mock + private HdrClamper.HdrLayerInfoListener mMockHdrInfoListener; + OffsettableClock mClock = new OffsettableClock.Stopped(); private final TestHandler mTestHandler = new TestHandler(null, mClock); private HdrClamper mHdrClamper; - + private HdrClamper.HdrListener mHdrChangeListener; @Before public void setUp() { - mHdrClamper = new HdrClamper(mMockListener, mTestHandler); + when(mMockInjector.getHdrListener(any(), any())).thenReturn(mMockHdrInfoListener); + mHdrClamper = new HdrClamper(mMockListener, mTestHandler, mMockInjector); + ArgumentCaptor<HdrClamper.HdrListener> listenerCaptor = ArgumentCaptor.forClass( + HdrClamper.HdrListener.class); + verify(mMockInjector).getHdrListener(listenerCaptor.capture(), eq(mTestHandler)); + mHdrChangeListener = listenerCaptor.getValue(); configureClamper(); } @@ -68,20 +102,24 @@ public class HdrClamperTest { assertFalse(mTestHandler.hasMessagesOrCallbacks()); assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); } @Test - public void testClamper_AmbientLuxChangesBelowLimit() { + public void testClamper_AmbientLuxChangesBelowLimit_MaxDecrease() { mHdrClamper.onAmbientLuxChange(499); assertTrue(mTestHandler.hasMessagesOrCallbacks()); TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek(); - assertEquals(2000, msgInfo.sendTime); + assertSendTime(3000, msgInfo.sendTime); assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); - mClock.fastForward(2000); + mClock.fastForward(3000); mTestHandler.timeAdvance(); assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4 + assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); } @Test @@ -91,33 +129,67 @@ public class HdrClamperTest { assertFalse(mTestHandler.hasMessagesOrCallbacks()); assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); } @Test public void testClamper_AmbientLuxChangesBelowLimit_ThenSlowlyAboveLimit() { mHdrClamper.onAmbientLuxChange(499); - mClock.fastForward(2000); + mClock.fastForward(3000); mTestHandler.timeAdvance(); mHdrClamper.onAmbientLuxChange(500); assertTrue(mTestHandler.hasMessagesOrCallbacks()); TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek(); - assertEquals(3000, msgInfo.sendTime); // 2000 + 1000 + assertSendTime(4000, msgInfo.sendTime); // 3000 + 1000 mClock.fastForward(1000); mTestHandler.timeAdvance(); assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 2 + assertEquals(0.047137f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); + } + + @Test + public void testClamper_HdrOff_ThenAmbientLuxChangesBelowLimit() { + mHdrChangeListener.onHdrVisible(false); + mHdrClamper.onAmbientLuxChange(499); + + assertFalse(mTestHandler.hasMessagesOrCallbacks()); + assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + assertEquals(-1, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); + } + + @Test + public void testClamper_HdrOff_ThenAmbientLuxChangesBelowLimit_ThenHdrOn() { + mHdrChangeListener.onHdrVisible(false); + mHdrClamper.onAmbientLuxChange(499); + mHdrChangeListener.onHdrVisible(true); + + assertTrue(mTestHandler.hasMessagesOrCallbacks()); + TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek(); + assertSendTime(3000, msgInfo.sendTime); + assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + + mClock.fastForward(3000); + mTestHandler.timeAdvance(); + assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4 + assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); + } + + // MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis() + // (in Handler.sendMessageDelayed) and then by subtracting SystemClock.uptimeMillis() + // (in TestHandler.sendMessageAtTime, there might be several milliseconds difference between + // SystemClock.uptimeMillis() calls, and subtracted value might be greater than added. + private static void assertSendTime(long expectedTime, long sendTime) { + assertTrue(expectedTime >= sendTime); + assertTrue(expectedTime - SEND_TIME_TOLERANCE < sendTime); } private void configureClamper() { - HdrBrightnessData data = new HdrBrightnessData( - Map.of(500f, 0.6f), - /* brightnessIncreaseDebounceMillis= */ 1000, - /* brightnessIncreaseDurationMillis= */ 1500, - /* brightnessDecreaseDebounceMillis= */ 2000, - /* brightnessDecreaseDurationMillis= */2500 - ); - mHdrClamper.resetHdrConfig(data); + mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, MIN_HDR_PERCENT, mMockBinder); + mHdrChangeListener.onHdrVisible(true); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index fbad369ab19e..b8c18e070397 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -91,6 +91,7 @@ import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.TestUtils; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector.BrightnessObserver; import com.android.server.display.mode.DisplayModeDirector.DesiredDisplayModeSpecs; import com.android.server.sensors.SensorManagerInternal; @@ -110,7 +111,9 @@ import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -121,10 +124,114 @@ import junitparams.Parameters; @SmallTest @RunWith(JUnitParamsRunner.class) public class DisplayModeDirectorTest { - // The tolerance within which we consider something approximately equals. + public static Collection<Object[]> getAppRequestedSizeTestCases() { + var appRequestedSizeTestCases = Arrays.asList(new Object[][] { + {DEFAULT_MODE_75.getModeId(), Float.POSITIVE_INFINITY, + DEFAULT_MODE_75.getRefreshRate(), Map.of()}, + {APP_MODE_HIGH_90.getModeId(), Float.POSITIVE_INFINITY, + APP_MODE_HIGH_90.getRefreshRate(), + Map.of( + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), + APP_MODE_HIGH_90.getPhysicalHeight()), + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()))}, + {LIMIT_MODE_70.getModeId(), Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, + Map.of( + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), + APP_MODE_HIGH_90.getPhysicalHeight()), + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), + LIMIT_MODE_70.getPhysicalHeight()))}, + {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(), + LIMIT_MODE_70.getRefreshRate(), + Map.of( + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_65.getPhysicalWidth(), + APP_MODE_65.getPhysicalHeight()), + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), + LIMIT_MODE_70.getPhysicalHeight()))}, + {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(), + LIMIT_MODE_70.getRefreshRate(), + Map.of( + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_65.getPhysicalWidth(), + APP_MODE_65.getPhysicalHeight()), + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forSizeAndPhysicalRefreshRatesRange( + 0, 0, + LIMIT_MODE_70.getPhysicalWidth(), + LIMIT_MODE_70.getPhysicalHeight(), + 0, Float.POSITIVE_INFINITY)), false}, + {APP_MODE_65.getModeId(), APP_MODE_65.getRefreshRate(), + APP_MODE_65.getRefreshRate(), + Map.of( + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_65.getPhysicalWidth(), + APP_MODE_65.getPhysicalHeight()), + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forSizeAndPhysicalRefreshRatesRange( + 0, 0, + LIMIT_MODE_70.getPhysicalWidth(), + LIMIT_MODE_70.getPhysicalHeight(), + 0, Float.POSITIVE_INFINITY)), true}}); + + final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2); + + // Add additional argument for displayResolutionRangeVotingEnabled=false if not present. + for (var testCaseArrayArgs : appRequestedSizeTestCases) { + if (testCaseArrayArgs.length == 4) { + var testCaseListArgs = new ArrayList<>(Arrays.asList(testCaseArrayArgs)); + testCaseListArgs.add(/* displayResolutionRangeVotingEnabled */ false); + res.add(testCaseListArgs.toArray()); + } else { + res.add(testCaseArrayArgs); + } + } + + // Add additional argument for displayResolutionRangeVotingEnabled=true if not present. + for (var testCaseArrayArgs : appRequestedSizeTestCases) { + if (testCaseArrayArgs.length == 4) { + var testCaseListArgs = new ArrayList<>(Arrays.asList(testCaseArrayArgs)); + testCaseListArgs.add(/* displayResolutionRangeVotingEnabled */ true); + res.add(testCaseListArgs.toArray()); + } + } + + return res; + } + private static final String TAG = "DisplayModeDirectorTest"; private static final boolean DEBUG = false; private static final float FLOAT_TOLERANCE = 0.01f; + + private static final Display.Mode APP_MODE_65 = new Display.Mode( + /*modeId=*/65, /*width=*/1900, /*height=*/1900, 65); + private static final Display.Mode LIMIT_MODE_70 = new Display.Mode( + /*modeId=*/70, /*width=*/2000, /*height=*/2000, 70); + private static final Display.Mode DEFAULT_MODE_75 = new Display.Mode( + /*modeId=*/75, /*width=*/2500, /*height=*/2500, 75); + private static final Display.Mode APP_MODE_HIGH_90 = new Display.Mode( + /*modeId=*/90, /*width=*/3000, /*height=*/3000, 90); + private static final Display.Mode[] TEST_MODES = new Display.Mode[] { + new Display.Mode( + /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60), + APP_MODE_65, + LIMIT_MODE_70, + DEFAULT_MODE_75, + APP_MODE_HIGH_90 + }; + private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY; private static final int MODE_ID = 1; private static final float TRANSITION_POINT = 0.763f; @@ -142,6 +249,8 @@ public class DisplayModeDirectorTest { public SensorManagerInternal mSensorManagerInternalMock; @Mock public DisplayManagerInternal mDisplayManagerInternalMock; + @Mock + private DisplayManagerFlags mDisplayManagerFlags; @Before public void setUp() throws Exception { @@ -177,7 +286,7 @@ public class DisplayModeDirectorTest { private DisplayModeDirector createDirectorFromModeArray(Display.Mode[] modes, Display.Mode defaultMode) { DisplayModeDirector director = - new DisplayModeDirector(mContext, mHandler, mInjector); + new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags); director.setLoggingEnabled(true); SparseArray<Display.Mode[]> supportedModesByDisplay = new SparseArray<>(); supportedModesByDisplay.put(DISPLAY_ID, modes); @@ -219,9 +328,8 @@ public class DisplayModeDirectorTest { // should take precedence over lower priority votes. { int minFps = 60; - int maxFps = 90; - director = createDirectorFromFpsRange(60, 90); - assertTrue(2 * numPriorities < maxFps - minFps + 1); + int maxFps = minFps + 2 * numPriorities; + director = createDirectorFromFpsRange(minFps, maxFps); SparseArray<Vote> votes = new SparseArray<>(); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID, votes); @@ -472,6 +580,7 @@ public class DisplayModeDirectorTest { assertThat(desiredSpecs.primary.render.max).isWithin(FLOAT_TOLERANCE).of(90); } + /** Resolution range voting disabled */ @Test public void testAppRequestRefreshRateRange() { // Confirm that the app request range doesn't include flicker or min refresh rate settings, @@ -530,6 +639,33 @@ public class DisplayModeDirectorTest { assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(90f); } + /** Tests for app requested size */ + @Parameters(method = "getAppRequestedSizeTestCases") + @Test + public void testAppRequestedSize(final int expectedBaseModeId, + final float expectedPhysicalRefreshRate, + final float expectedAppRequestedRefreshRate, + final Map<Integer, Vote> votesWithPriorities, + final boolean displayResolutionRangeVotingEnabled) { + when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled()) + .thenReturn(displayResolutionRangeVotingEnabled); + DisplayModeDirector director = createDirectorFromModeArray(TEST_MODES, DEFAULT_MODE_75); + + SparseArray<Vote> votes = new SparseArray<>(); + votesWithPriorities.forEach(votes::put); + + SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); + votesByDisplay.put(DISPLAY_ID, votes); + director.injectVotesByDisplay(votesByDisplay); + + var desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); + assertThat(desiredSpecs.baseModeId).isEqualTo(expectedBaseModeId); + assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); + assertThat(desiredSpecs.primary.physical.max).isAtLeast(expectedPhysicalRefreshRate); + assertThat(desiredSpecs.appRequest.physical.min).isAtMost(0); + assertThat(desiredSpecs.appRequest.physical.max).isAtLeast(expectedAppRequestedRefreshRate); + } + void verifySpecsWithRefreshRateSettings(DisplayModeDirector director, float minFps, float peakFps, float defaultFps, RefreshRateRanges primary, RefreshRateRanges appRequest) { @@ -843,7 +979,7 @@ public class DisplayModeDirectorTest { @Test public void testStaleAppRequestSize() { DisplayModeDirector director = - new DisplayModeDirector(mContext, mHandler, mInjector); + new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags); Display.Mode[] modes = new Display.Mode[] { new Display.Mode(1, 1280, 720, 60), }; diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java new file mode 100644 index 000000000000..ff91d34470d4 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.mode; + + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.Mode.INVALID_MODE_ID; + + +import static com.android.server.display.mode.DisplayModeDirector.SYNCHRONIZED_REFRESH_RATE_TOLERANCE; +import static com.android.server.display.mode.Vote.PRIORITY_LIMIT_MODE; +import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE; +import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE; +import static com.android.server.display.mode.VotesStorage.GLOBAL_ID; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.Resources; +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.os.Looper; +import android.provider.DeviceConfigInterface; +import android.view.Display; +import android.view.DisplayInfo; + +import androidx.annotation.Nullable; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; + +import com.android.internal.R; +import com.android.server.display.feature.DisplayManagerFlags; +import com.android.server.sensors.SensorManagerInternal; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import junitparams.JUnitParamsRunner; + + +@SmallTest +@RunWith(JUnitParamsRunner.class) +public class DisplayObserverTest { + private static final int EXTERNAL_DISPLAY = 1; + private static final int MAX_WIDTH = 1920; + private static final int MAX_HEIGHT = 1080; + private static final int MAX_REFRESH_RATE = 60; + + private final Display.Mode[] mInternalDisplayModes = new Display.Mode[] { + new Display.Mode(/*modeId=*/ 0, MAX_WIDTH / 2, MAX_HEIGHT / 2, + (float) MAX_REFRESH_RATE / 2), + new Display.Mode(/*modeId=*/ 1, MAX_WIDTH / 2, MAX_HEIGHT / 2, + MAX_REFRESH_RATE), + new Display.Mode(/*modeId=*/ 2, MAX_WIDTH / 2, MAX_HEIGHT / 2, + MAX_REFRESH_RATE * 2), + new Display.Mode(/*modeId=*/ 3, MAX_WIDTH, MAX_HEIGHT, MAX_REFRESH_RATE * 2), + new Display.Mode(/*modeId=*/ 4, MAX_WIDTH, MAX_HEIGHT, MAX_REFRESH_RATE), + new Display.Mode(/*modeId=*/ 5, MAX_WIDTH * 2, MAX_HEIGHT * 2, + MAX_REFRESH_RATE), + new Display.Mode(/*modeId=*/ 6, MAX_WIDTH / 2, MAX_HEIGHT / 2, + MAX_REFRESH_RATE * 3), + }; + + private final Display.Mode[] mExternalDisplayModes = new Display.Mode[] { + new Display.Mode(/*modeId=*/ 0, MAX_WIDTH / 2, MAX_HEIGHT / 2, + (float) MAX_REFRESH_RATE / 2), + new Display.Mode(/*modeId=*/ 1, MAX_WIDTH / 2, MAX_HEIGHT / 2, + MAX_REFRESH_RATE), + new Display.Mode(/*modeId=*/ 2, MAX_WIDTH / 2, MAX_HEIGHT / 2, + MAX_REFRESH_RATE * 2), + new Display.Mode(/*modeId=*/ 3, MAX_WIDTH, MAX_HEIGHT, MAX_REFRESH_RATE * 2), + new Display.Mode(/*modeId=*/ 4, MAX_WIDTH, MAX_HEIGHT, MAX_REFRESH_RATE), + new Display.Mode(/*modeId=*/ 5, MAX_WIDTH * 2, MAX_HEIGHT * 2, + MAX_REFRESH_RATE), + }; + + private DisplayModeDirector mDmd; + private Context mContext; + private DisplayModeDirector.Injector mInjector; + private Handler mHandler; + private DisplayManager.DisplayListener mObserver; + private Resources mResources; + @Mock + private DisplayManagerFlags mDisplayManagerFlags; + private int mExternalDisplayUserPreferredModeId = INVALID_MODE_ID; + private int mInternalDisplayUserPreferredModeId = INVALID_MODE_ID; + private Display mDefaultDisplay; + private Display mExternalDisplay; + + /** Setup tests. */ + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mHandler = new Handler(Looper.getMainLooper()); + mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); + mResources = mock(Resources.class); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate)) + .thenReturn(0); + when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth)) + .thenReturn(0); + when(mResources.getInteger(R.integer.config_externalDisplayPeakHeight)) + .thenReturn(0); + when(mResources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled)) + .thenReturn(false); + + // Necessary configs to initialize DisplayModeDirector + when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate)) + .thenReturn(new int[]{5}); + when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate)) + .thenReturn(new int[]{10}); + when(mResources.getIntArray( + R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate)) + .thenReturn(new int[]{250}); + when(mResources.getIntArray( + R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate)) + .thenReturn(new int[]{7000}); + } + + /** No vote for user preferred mode */ + @Test + public void testExternalDisplay_notVotedUserPreferredMode() { + var preferredMode = mExternalDisplayModes[5]; + mExternalDisplayUserPreferredModeId = preferredMode.getModeId(); + + init(); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + + // Testing that the vote is not added when display is added because feature is disabled + mObserver.onDisplayAdded(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + + // Testing that the vote is not present after display is removed + mObserver.onDisplayRemoved(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + + // Testing that the vote is not added when display is changed because feature is disabled + mObserver.onDisplayChanged(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + } + + /** Vote for user preferred mode */ + @Test + public void testExternalDisplay_voteUserPreferredMode() { + when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + var preferredMode = mExternalDisplayModes[5]; + mExternalDisplayUserPreferredModeId = preferredMode.getModeId(); + var expectedVote = Vote.forSize( + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight()); + init(); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + mObserver.onDisplayAdded(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(expectedVote); + + mExternalDisplayUserPreferredModeId = INVALID_MODE_ID; + mObserver.onDisplayChanged(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + + preferredMode = mExternalDisplayModes[4]; + mExternalDisplayUserPreferredModeId = preferredMode.getModeId(); + expectedVote = Vote.forSize( + preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight()); + mObserver.onDisplayChanged(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(expectedVote); + + // Testing that the vote is removed. + mObserver.onDisplayRemoved(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + } + + /** External display: Do not apply limit to user preferred mode */ + @Test + public void testExternalDisplay_doNotApplyLimitToUserPreferredMode() { + when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true); + when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate)) + .thenReturn(MAX_REFRESH_RATE); + when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth)) + .thenReturn(MAX_WIDTH); + when(mResources.getInteger(R.integer.config_externalDisplayPeakHeight)) + .thenReturn(MAX_HEIGHT); + + var preferredMode = mExternalDisplayModes[5]; + mExternalDisplayUserPreferredModeId = preferredMode.getModeId(); + var expectedResolutionVote = Vote.forSize(preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight()); + init(); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + mObserver.onDisplayAdded(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(expectedResolutionVote); + + // Testing that the vote is removed. + mObserver.onDisplayRemoved(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + } + + /** Default display: Do not apply limit to user preferred mode */ + @Test + public void testDefaultDisplayAdded_notAppliedLimitToUserPreferredMode() { + when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true); + when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate)) + .thenReturn(MAX_REFRESH_RATE); + when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth)) + .thenReturn(MAX_WIDTH); + when(mResources.getInteger(R.integer.config_externalDisplayPeakHeight)) + .thenReturn(MAX_HEIGHT); + var preferredMode = mInternalDisplayModes[5]; + mInternalDisplayUserPreferredModeId = preferredMode.getModeId(); + var expectedResolutionVote = Vote.forSize(preferredMode.getPhysicalWidth(), + preferredMode.getPhysicalHeight()); + init(); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + mObserver.onDisplayAdded(DEFAULT_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(expectedResolutionVote); + mObserver.onDisplayRemoved(DEFAULT_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE)) + .isEqualTo(null); + } + + /** Default display added, no mode limit set */ + @Test + public void testDefaultDisplayAdded() { + when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true); + when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate)) + .thenReturn(MAX_REFRESH_RATE); + when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth)) + .thenReturn(MAX_WIDTH); + when(mResources.getInteger(R.integer.config_externalDisplayPeakHeight)) + .thenReturn(MAX_HEIGHT); + init(); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + mObserver.onDisplayAdded(DEFAULT_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + } + + /** External display added, apply resolution refresh rate limit */ + @Test + public void testExternalDisplayAdded_applyResolutionRefreshRateLimit() { + when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true); + when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate)) + .thenReturn(MAX_REFRESH_RATE); + when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth)) + .thenReturn(MAX_WIDTH); + when(mResources.getInteger(R.integer.config_externalDisplayPeakHeight)) + .thenReturn(MAX_HEIGHT); + init(); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + mObserver.onDisplayAdded(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo( + Vote.forSizeAndPhysicalRefreshRatesRange(0, 0, + MAX_WIDTH, MAX_HEIGHT, + /*minPhysicalRefreshRate=*/ 0, MAX_REFRESH_RATE)); + mObserver.onDisplayRemoved(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + } + + /** External display added, disabled resolution refresh rate limit. */ + @Test + public void testExternalDisplayAdded_disabledResolutionRefreshRateLimit() { + when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true); + init(); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + mObserver.onDisplayAdded(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + mObserver.onDisplayChanged(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + mObserver.onDisplayRemoved(EXTERNAL_DISPLAY); + assertThat(getVote(DEFAULT_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + assertThat(getVote(EXTERNAL_DISPLAY, PRIORITY_LIMIT_MODE)).isEqualTo(null); + } + + /** External display added, applied refresh rates synchronization */ + @Test + public void testExternalDisplayAdded_appliedRefreshRatesSynchronization() { + when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(true); + when(mResources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled)) + .thenReturn(true); + init(); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + mObserver.onDisplayAdded(EXTERNAL_DISPLAY); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo( + Vote.forPhysicalRefreshRates( + MAX_REFRESH_RATE - SYNCHRONIZED_REFRESH_RATE_TOLERANCE, + MAX_REFRESH_RATE + SYNCHRONIZED_REFRESH_RATE_TOLERANCE)); + + // Remove external display and check that sync vote is no longer present. + mObserver.onDisplayRemoved(EXTERNAL_DISPLAY); + + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + } + + /** External display added, disabled feature refresh rates synchronization */ + @Test + public void testExternalDisplayAdded_disabledFeatureRefreshRatesSynchronization() { + when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(false); + when(mResources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled)) + .thenReturn(true); + init(); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + mObserver.onDisplayAdded(EXTERNAL_DISPLAY); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + } + + /** External display not applied refresh rates synchronization, because + * config_refreshRateSynchronizationEnabled is false. */ + @Test + public void testExternalDisplay_notAppliedRefreshRatesSynchronization() { + when(mDisplayManagerFlags.isDisplayResolutionRangeVotingEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isUserPreferredModeVoteEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isExternalDisplayLimitModeEnabled()).thenReturn(true); + when(mDisplayManagerFlags.isDisplaysRefreshRatesSynchronizationEnabled()).thenReturn(true); + init(); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + mObserver.onDisplayAdded(EXTERNAL_DISPLAY); + assertThat(getVote(GLOBAL_ID, PRIORITY_SYNCHRONIZED_REFRESH_RATE)).isEqualTo(null); + } + + private void init() { + mInjector = mock(DisplayModeDirector.Injector.class); + doAnswer(invocation -> { + assertThat(mObserver).isNull(); + mObserver = invocation.getArgument(0); + return null; + }).when(mInjector).registerDisplayListener(any(), any()); + + doAnswer(c -> { + DisplayInfo info = c.getArgument(1); + info.type = Display.TYPE_INTERNAL; + info.displayId = DEFAULT_DISPLAY; + info.defaultModeId = 0; + info.supportedModes = mInternalDisplayModes; + info.userPreferredModeId = mInternalDisplayUserPreferredModeId; + return true; + }).when(mInjector).getDisplayInfo(eq(DEFAULT_DISPLAY), /*displayInfo=*/ any()); + + doAnswer(c -> { + DisplayInfo info = c.getArgument(1); + info.type = Display.TYPE_EXTERNAL; + info.displayId = EXTERNAL_DISPLAY; + info.defaultModeId = 0; + info.supportedModes = mExternalDisplayModes; + info.userPreferredModeId = mExternalDisplayUserPreferredModeId; + return true; + }).when(mInjector).getDisplayInfo(eq(EXTERNAL_DISPLAY), /*displayInfo=*/ any()); + + doAnswer(c -> mock(SensorManagerInternal.class)).when(mInjector).getSensorManagerInternal(); + doAnswer(c -> mock(DeviceConfigInterface.class)).when(mInjector).getDeviceConfig(); + + mDefaultDisplay = mock(Display.class); + when(mDefaultDisplay.getDisplayId()).thenReturn(DEFAULT_DISPLAY); + doAnswer(c -> mInjector.getDisplayInfo(DEFAULT_DISPLAY, c.getArgument(0))) + .when(mDefaultDisplay).getDisplayInfo(/*displayInfo=*/ any()); + + mExternalDisplay = mock(Display.class); + when(mExternalDisplay.getDisplayId()).thenReturn(EXTERNAL_DISPLAY); + doAnswer(c -> mInjector.getDisplayInfo(EXTERNAL_DISPLAY, c.getArgument(0))) + .when(mExternalDisplay).getDisplayInfo(/*displayInfo=*/ any()); + + when(mInjector.getDisplays()).thenReturn(new Display[] {mDefaultDisplay, mExternalDisplay}); + + mDmd = new DisplayModeDirector(mContext, mHandler, mInjector, mDisplayManagerFlags); + mDmd.start(null); + assertThat(mObserver).isNotNull(); + } + + @Nullable + private Vote getVote(final int displayId, final int priority) { + return mDmd.getVote(displayId, priority); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java index 287fdd5c344b..50e239218fa0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java @@ -19,6 +19,7 @@ package com.android.server.display.mode; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.util.SparseArray; @@ -72,6 +73,18 @@ public class VotesStorageTest { verify(mVotesListener).onChanged(); } + /** Verifies that adding the same vote twice results in a single call to onChanged */ + @Test + public void notifiesVoteListenerCalledOnceIfVoteUpdatedTwice() { + // WHEN updateVote is called + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY, VOTE); + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE_OTHER); + mVotesStorage.updateVote(DISPLAY_ID, PRIORITY_OTHER, VOTE); + // THEN listener is notified, but only when vote changes. + verify(mVotesListener, times(3)).onChanged(); + } + @Test public void addsAnotherVoteToStorageWithDifferentPriority() { // GIVEN vote storage with one vote diff --git a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java index 880501f39ac2..f5c6bb2e618b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java @@ -141,6 +141,32 @@ public final class DisplayStateControllerTest { assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition()); } + @Test + public void dozeScreenStateOverrideToDozeSuspend_DozePolicy_updateDisplayStateToDozeSuspend() { + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = + new DisplayManagerInternal.DisplayPowerRequest(); + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; + mDisplayStateController.overrideDozeScreenState(Display.STATE_DOZE_SUSPEND); + + int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED, + !DISPLAY_IN_TRANSITION); + + assertEquals(state, Display.STATE_DOZE_SUSPEND); + } + + @Test + public void dozeScreenStateOverrideToDozeSuspend_OffPolicy_displayRemainOff() { + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = + new DisplayManagerInternal.DisplayPowerRequest(); + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF; + mDisplayStateController.overrideDozeScreenState(Display.STATE_DOZE_SUSPEND); + + int state = mDisplayStateController.updateDisplayState(displayPowerRequest, DISPLAY_ENABLED, + !DISPLAY_IN_TRANSITION); + + assertEquals(state, Display.STATE_OFF); + } + private void validDisplayState(int policy, int displayState, boolean isEnabled, boolean isInTransition) { DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( diff --git a/services/tests/dreamservicetests/TEST_MAPPING b/services/tests/dreamservicetests/TEST_MAPPING index d73d83dedaae..a644ea690dcd 100644 --- a/services/tests/dreamservicetests/TEST_MAPPING +++ b/services/tests/dreamservicetests/TEST_MAPPING @@ -4,7 +4,6 @@ "name": "DreamServiceTests", "options": [ {"include-filter": "com.android.server.dreams"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java index 52044bfaef24..de8b3080907c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java @@ -117,7 +117,9 @@ public class ApexManagerTest { Build.VERSION_CODES.CUR_DEVELOPMENT, Build.VERSION.INCREMENTAL); mMockSystem.system().validateFinalState(); - mInstallPackageHelper = new InstallPackageHelper(mPmService, mock(AppDataHelper.class)); + mInstallPackageHelper = new InstallPackageHelper(mPmService, mock(AppDataHelper.class), + mock(RemovePackageHelper.class), mock(DeletePackageHelper.class), + mock(BroadcastHelper.class)); } @NonNull diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt index d6a4d40c763c..931b38dc2951 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt @@ -34,6 +34,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.mock @RunWith(JUnit4::class) class DeletePackageHelperTest { @@ -79,7 +80,8 @@ class DeletePackageHelperTest { whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0)) whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1) - val dph = DeletePackageHelper(mPms) + val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java), + mock(BroadcastHelper::class.java)) val result = dph.deletePackageX("a.data.package", 1L, 1, 0, false) assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED) @@ -97,7 +99,8 @@ class DeletePackageHelperTest { whenever(mUserManagerInternal.getUserInfo(parentId)).thenReturn( UserInfo(userId, "testparent", 0)) - val dph = DeletePackageHelper(mPms) + val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java), + mock(BroadcastHelper::class.java)) val result = dph.deletePackageX("a.data.package", 1L, userId, 0, false) assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED) @@ -112,7 +115,8 @@ class DeletePackageHelperTest { whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM)) .thenReturn(PERMISSION_DENIED) - val dph = DeletePackageHelper(mPms) + val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java), + mock(BroadcastHelper::class.java)) val result = dph.deletePackageX("a.data.package", 1L, 1, PackageManager.DELETE_SYSTEM_APP, false) @@ -133,7 +137,8 @@ class DeletePackageHelperTest { whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM)) .thenReturn(PERMISSION_DENIED) - val dph = DeletePackageHelper(mPms) + val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java), + mock(BroadcastHelper::class.java)) val result = dph.deletePackageX("a.data.package", 1L, userId, PackageManager.DELETE_SYSTEM_APP, false) @@ -150,7 +155,8 @@ class DeletePackageHelperTest { whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM)) .thenReturn(PERMISSION_DENIED) - val dph = DeletePackageHelper(mPms) + val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java), + mock(BroadcastHelper::class.java)) val result = dph.deletePackageX("a.data.package", 1L, 1, PackageManager.DELETE_SYSTEM_APP, false) @@ -164,7 +170,8 @@ class DeletePackageHelperTest { whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM)) .thenReturn(PERMISSION_GRANTED) - val dph = DeletePackageHelper(mPms) + val dph = DeletePackageHelper(mPms, mock(RemovePackageHelper::class.java), + mock(BroadcastHelper::class.java)) val result = dph.deletePackageX("a.data.package", 1L, 1, PackageManager.DELETE_SYSTEM_APP, false) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt index 9f1cec3b595a..cf81f0a77702 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt @@ -39,7 +39,7 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { override fun setup() { super.setup() distractingPackageHelper = DistractingPackageHelper( - pms, rule.mocks().injector, broadcastHelper, suspendPackageHelper) + pms, broadcastHelper, suspendPackageHelper) } @Test @@ -50,12 +50,11 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { testHandler.flush() verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), - nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), - nullable(), nullable(), nullable(), nullable()) + verify(broadcastHelper).sendDistractingPackagesChanged(any(Computer::class.java), + pkgListCaptor.capture(), any(), any(), flagsCaptor.capture()) - val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS) + val modifiedPackages = pkgListCaptor.value + val distractionFlags = flagsCaptor.value assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) assertThat(distractionFlags).isEqualTo(PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) assertThat(unactionedPackages).isEmpty() @@ -75,10 +74,8 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { PackageManager.RESTRICTION_HIDE_NOTIFICATIONS, TEST_USER_ID, deviceOwnerUid) testHandler.flush() verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper, never()).sendPackageBroadcast( - eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(), - anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(), - nullable()) + verify(broadcastHelper, never()).sendDistractingPackagesChanged( + any(), any(), any(), any(), any()) assertThat(unactionedPackages).isEmpty() } @@ -154,11 +151,11 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { testHandler.flush() verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), - nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), - nullable(), nullable(), nullable(), nullable()) - val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS) + verify(broadcastHelper).sendDistractingPackagesChanged( + any(Computer::class.java), pkgListCaptor.capture(), any(), eq(TEST_USER_ID), + flagsCaptor.capture()) + val modifiedPackages = pkgListCaptor.value + val distractionFlags = flagsCaptor.value assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) assertThat(distractionFlags).isEqualTo(PackageManager.RESTRICTION_NONE) } @@ -170,9 +167,8 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { testHandler.flush() verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper, never()).sendPackageBroadcast(eq( - Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(), - nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable()) + verify(broadcastHelper, never()).sendDistractingPackagesChanged( + any(), any(), any(), any(), any()) } @Test @@ -189,22 +185,21 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { arrayOfNulls(0), TEST_USER_ID) testHandler.flush() verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper, never()).sendPackageBroadcast(eq( - Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(), - nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable()) + verify(broadcastHelper, never()).sendDistractingPackagesChanged( + any(), any(), any(), any(), any()) } @Test fun sendDistractingPackagesChanged() { - distractingPackageHelper.sendDistractingPackagesChanged(packagesToChange, uidsToChange, - TEST_USER_ID, PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) + broadcastHelper.sendDistractingPackagesChanged(pms.snapshotComputer(), + packagesToChange, uidsToChange, TEST_USER_ID, + PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) testHandler.flush() - verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), - nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), - nullable(), nullable(), nullable(), nullable()) + verify(broadcastHelper).sendDistractingPackagesChanged(any(Computer::class.java), + pkgListCaptor.capture(), uidsCaptor.capture(), eq(TEST_USER_ID), any()) - var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) + var changedPackages = pkgListCaptor.value + var changedUids = uidsCaptor.value assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) assertThat(changedUids).asList().containsExactly( packageSetting1.appId, packageSetting2.appId) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt index 5fd270ecb2b4..eb001645863f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt @@ -17,7 +17,6 @@ package com.android.server.pm import android.os.Build -import android.os.Bundle import android.os.UserHandle import android.os.UserManager import com.android.server.pm.pkg.PackageStateInternal @@ -68,7 +67,11 @@ open class PackageHelperTestBase { lateinit var protectedPackages: ProtectedPackages @Captor - lateinit var bundleCaptor: ArgumentCaptor<Bundle> + lateinit var pkgListCaptor: ArgumentCaptor<Array<String>> + @Captor + lateinit var flagsCaptor: ArgumentCaptor<Int> + @Captor + lateinit var uidsCaptor: ArgumentCaptor<IntArray> @Rule @JvmField diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java index c5db5db41787..6c44fd0da1c4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java @@ -17,12 +17,12 @@ package com.android.server.pm; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.after; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.content.Intent; import android.content.pm.PackageInstaller; @@ -63,9 +63,7 @@ public class PackageMonitorCallbackHelperTest { @Before public void setup() { - when(mMockSystem.mocks().getInjector().getHandler()).thenReturn(mHandler); - mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper( - mMockSystem.mocks().getInjector()); + mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper(); } @@ -80,7 +78,7 @@ public class PackageMonitorCallbackHelperTest { mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */, - null /* instantUserIds */, null /* broadcastAllowList */); + null /* instantUserIds */, null /* broadcastAllowList */, mHandler); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any()); } @@ -93,7 +91,7 @@ public class PackageMonitorCallbackHelperTest { Binder.getCallingUid()); mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */, - null /* broadcastAllowList */); + null /* broadcastAllowList */, mHandler); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any()); @@ -101,7 +99,7 @@ public class PackageMonitorCallbackHelperTest { mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback); mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */, - null /* instantUserIds */, null /* broadcastAllowList */); + null /* instantUserIds */, null /* broadcastAllowList */, mHandler); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any()); } @@ -114,7 +112,7 @@ public class PackageMonitorCallbackHelperTest { Binder.getCallingUid()); mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */, - null /* instantUserIds */, null /* broadcastAllowList */); + null /* instantUserIds */, null /* broadcastAllowList */, mHandler); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult( @@ -138,7 +136,7 @@ public class PackageMonitorCallbackHelperTest { // Notify for user 10 mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */, - null /* instantUserIds */, null /* broadcastAllowList */); + null /* instantUserIds */, null /* broadcastAllowList */, mHandler); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any()); } @@ -155,7 +153,7 @@ public class PackageMonitorCallbackHelperTest { mPackageMonitorCallbackHelper.notifyPackageChanged(FAKE_PACKAGE_NAME, false /* dontKillApp */, components, FAKE_PACKAGE_UID, null /* reason */, new int[]{0} /* userIds */, null /* instantUserIds */, - null /* broadcastAllowList */); + null /* broadcastAllowList */, mHandler); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult( @@ -183,7 +181,8 @@ public class PackageMonitorCallbackHelperTest { Binder.getCallingUid()); mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(FAKE_PACKAGE_NAME, FAKE_PACKAGE_UID, new int[]{0} /* userIds */, new int[0], false /* isArchived */, - PackageInstaller.DATA_LOADER_TYPE_STREAMING, null /* broadcastAllowList */); + PackageInstaller.DATA_LOADER_TYPE_STREAMING, null /* broadcastAllowList */, + mHandler); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult( @@ -207,7 +206,7 @@ public class PackageMonitorCallbackHelperTest { Binder.getCallingUid()); mPackageMonitorCallbackHelper.notifyResourcesChanged(true /* mediaStatus */, true /* replacing */, new String[]{FAKE_PACKAGE_NAME}, - new int[]{FAKE_PACKAGE_UID} /* uids */); + new int[]{FAKE_PACKAGE_UID} /* uids */, mHandler); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult( @@ -240,7 +239,7 @@ public class PackageMonitorCallbackHelperTest { mPackageMonitorCallbackHelper.onUserRemoved(10); mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */, - null /* instantUserIds */, null /* broadcastAllowList */); + null /* instantUserIds */, null /* broadcastAllowList */, mHandler); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any()); } @@ -256,7 +255,7 @@ public class PackageMonitorCallbackHelperTest { Binder.getCallingUid()); mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */, - null /* instantUserIds */, broadcastAllowList); + null /* instantUserIds */, broadcastAllowList, mHandler); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any()); } @@ -272,7 +271,7 @@ public class PackageMonitorCallbackHelperTest { Binder.getCallingUid()); mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */, - null /* instantUserIds */, broadcastAllowList); + null /* instantUserIds */, broadcastAllowList, mHandler); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any()); } @@ -288,7 +287,7 @@ public class PackageMonitorCallbackHelperTest { Process.SYSTEM_UID); mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */, - null /* instantUserIds */, broadcastAllowList); + null /* instantUserIds */, broadcastAllowList, mHandler); verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).times(1)).sendResult(any()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt index 679757629e32..4240373b7c1d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt @@ -22,12 +22,11 @@ import android.os.Binder import android.os.PersistableBundle import com.android.server.testutils.any import com.android.server.testutils.eq -import com.android.server.testutils.nullable import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mockito import org.mockito.Mockito.times import org.mockito.Mockito.verify @@ -44,17 +43,10 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { testHandler.flush() verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED), - nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), - nullable(), nullable(), nullable(), nullable()) - verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(), - nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(), - nullable(), nullable()) - verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(), - nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(), - nullable(), nullable()) - - var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java), + eq(Intent.ACTION_PACKAGES_SUSPENDED), pkgListCaptor.capture(), any(), any(), any()) + + var modifiedPackages = pkgListCaptor.value assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) assertThat(failedNames).isEmpty() } @@ -146,6 +138,7 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid, false /* forQuietMode */, false /* quarantined */) testHandler.flush() + Mockito.clearInvocations(broadcastHelper) assertThat(failedNames).isEmpty() failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), targetPackages, false /* suspended */, null /* appExtras */, @@ -154,17 +147,13 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { testHandler.flush() verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED), - nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), - nullable(), nullable(), nullable(), nullable()) - verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), - nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), - nullable(), nullable(), nullable()) - verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), - nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), - nullable(), nullable(), nullable()) - - var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) + verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java), + eq(Intent.ACTION_PACKAGES_UNSUSPENDED), pkgListCaptor.capture(), any(), any(), + any()) + verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(any(Computer::class.java), + any(), any(), any()) + + var modifiedPackages = pkgListCaptor.value assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) assertThat(failedNames).isEmpty() } @@ -206,7 +195,7 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { testHandler.flush() assertThat(failedNames).isEmpty() - val result = suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), + val result = SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)!! assertThat(result.getString(TEST_PACKAGE_1)).isEqualTo(TEST_PACKAGE_1) @@ -222,14 +211,15 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid, false /* forQuietMode */, false /* quarantined */) testHandler.flush() + Mockito.clearInvocations(broadcastHelper) assertThat(failedNames).isEmpty() assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE) - assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), + assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNotNull() - assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), + assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull() suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(), @@ -238,23 +228,18 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { testHandler.flush() verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED), - nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(), - nullable(), nullable(), nullable(), nullable()) - verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), - nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), - nullable(), nullable(), nullable()) - verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED), - nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), - nullable(), nullable(), nullable()) + verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java), + eq(Intent.ACTION_PACKAGES_UNSUSPENDED), any(), any(), any(), any()) + verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(any(Computer::class.java), + any(), any(), any()) assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull() assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull() - assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), + assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull() - assertThat(suspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), + assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(), TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull() } @@ -319,39 +304,4 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { assertThat(result.title).isEqualTo(TEST_PACKAGE_1) } - - @Test - @Throws(Exception::class) - fun sendPackagesSuspendedForUser() { - suspendPackageHelper.sendPackagesSuspendedForUser( - Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, false, TEST_USER_ID) - testHandler.flush() - verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(), - anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(), - nullable()) - - var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) - assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) - assertThat(changedUids).asList().containsExactly( - packageSetting1.appId, packageSetting2.appId) - } - - @Test - @Throws(Exception::class) - fun sendPackagesSuspendModifiedForUser() { - suspendPackageHelper.sendPackagesSuspendedForUser( - Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange, false, TEST_USER_ID) - testHandler.flush() - verify(broadcastHelper).sendPackageBroadcast( - eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(), - anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(), - nullable()) - - var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST) - var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST) - assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) - assertThat(modifiedUids).asList().containsExactly( - packageSetting1.appId, packageSetting2.appId) - } } diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING index e1eb1e4fc0db..eee68a48fc63 100644 --- a/services/tests/powerstatstests/TEST_MAPPING +++ b/services/tests/powerstatstests/TEST_MAPPING @@ -4,7 +4,6 @@ "name": "PowerStatsTests", "options": [ {"include-filter": "com.android.server.power.stats"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"}, {"exclude-annotation": "org.junit.Ignore"} ] diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index 82b75408ad18..2f0257ab6b25 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java @@ -39,11 +39,16 @@ import android.content.pm.ApplicationInfo; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.hardware.display.DisplayManager; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.testing.DexmakerShareClassLoaderRule; import android.view.Display; +import android.view.WindowManager; import com.android.server.accessibility.magnification.MagnificationProcessor; import com.android.server.accessibility.test.MessageCapturingHandler; @@ -76,6 +81,9 @@ public class AccessibilityServiceConnectionTest { public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = new DexmakerShareClassLoaderRule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + AccessibilityServiceConnection mConnection; @Mock AccessibilityUserState mMockUserState; @@ -113,6 +121,8 @@ public class AccessibilityServiceConnectionTest { when(mMockIBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient); when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false); + when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)) + .thenReturn(new DisplayManager(mMockContext)); mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext, COMPONENT_NAME, mMockServiceInfo, SERVICE_ID, mHandler, new Object(), @@ -168,6 +178,18 @@ public class AccessibilityServiceConnectionTest { assertFalse(mConnection.getServiceInfo().crashed); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK) + public void onServiceConnected_addsWindowTokens() { + setServiceBinding(COMPONENT_NAME); + mConnection.bindLocked(); + mConnection.onServiceConnected(COMPONENT_NAME, mMockIBinder); + + verify(mMockWindowManagerInternal).addWindowToken( + any(), eq(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY), + anyInt(), eq(null)); + } + private void setServiceBinding(ComponentName componentName) { when(mMockUserState.getBindingServicesLocked()) .thenReturn(new HashSet<>(Arrays.asList(componentName))); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index b4558b211fc6..63281b77ade7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -35,6 +35,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -46,6 +47,9 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Color; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.test.mock.MockContentResolver; import android.testing.DexmakerShareClassLoaderRule; @@ -88,6 +92,9 @@ public class AccessibilityUserStateTest { @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = new DexmakerShareClassLoaderRule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Mock private AccessibilityServiceInfo mMockServiceInfo; @Mock private AccessibilityServiceConnection mMockConnection; @@ -188,7 +195,7 @@ public class AccessibilityUserStateTest { mUserState.addServiceLocked(mMockConnection); - verify(mMockConnection, never()).onAdded(); + verify(mMockListener, never()).onServiceInfoChangedLocked(any()); } @Test @@ -197,13 +204,24 @@ public class AccessibilityUserStateTest { mUserState.addServiceLocked(mMockConnection); - verify(mMockConnection).onAdded(); assertTrue(mUserState.getBoundServicesLocked().contains(mMockConnection)); assertEquals(mMockConnection, mUserState.mComponentNameToServiceMap.get(COMPONENT_NAME)); verify(mMockListener).onServiceInfoChangedLocked(eq(mUserState)); } @Test + // addServiceLocked only calls addWindowTokensForAllDisplays when + // FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK is off, so skip the test if it is on. + @RequiresFlagsDisabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK) + public void addService_flagDisabled_addsWindowTokens() { + when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME); + + mUserState.addServiceLocked(mMockConnection); + + verify(mMockConnection).addWindowTokensForAllDisplays(); + } + + @Test public void reconcileSoftKeyboardMode_whenStateNotMatchSettings_setBothDefault() { // When soft kb show mode is hidden in settings and is auto in state. putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java index 4ce9ba031b25..3ee5f61bb8cd 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java @@ -21,8 +21,11 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -36,6 +39,10 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.hardware.display.DisplayManager; import android.os.IBinder; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import com.android.server.accessibility.test.MessageCapturingHandler; @@ -43,6 +50,7 @@ import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -58,6 +66,9 @@ public class UiAutomationManagerTest { MessageCapturingHandler mMessageCapturingHandler; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Mock Context mMockContext; @Mock AccessibilityServiceInfo mMockServiceInfo; @Mock ResolveInfo mMockResolveInfo; @@ -197,6 +208,24 @@ public class UiAutomationManagerTest { assertEquals(0, mUiAutomationManager.getRequestedEventMaskLocked()); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_ADD_WINDOW_TOKEN_WITHOUT_LOCK) + public void registerUiAutomationService_callsAddWindowTokenUsingHandler() { + register(0); + // registerUiTestAutomationServiceLocked() should not directly call addWindowToken. + verify(mMockWindowManagerInternal, never()).addWindowToken( + any(), anyInt(), anyInt(), any()); + + // Advance UiAutomationManager#UiAutomationService's handler. + mMessageCapturingHandler.sendAllMessages(); + + // After advancing the handler we expect addWindowToken to have been called + // by the UiAutomationService instance. + verify(mMockWindowManagerInternal).addWindowToken( + any(), eq(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY), + anyInt(), eq(null)); + } + private void register(int flags) { mUiAutomationManager.registerUiTestAutomationServiceLocked(mMockOwner, mMockAccessibilityServiceClient, mMockContext, mMockServiceInfo, SERVICE_ID, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java index a0bca3b6b9fe..24ad976f6e45 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java @@ -50,7 +50,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; -import android.platform.test.annotations.FlakyTest; import android.provider.Settings; import android.test.mock.MockContentResolver; import android.view.InputDevice; @@ -60,6 +59,7 @@ import android.view.accessibility.IWindowMagnificationConnectionCallback; import android.view.accessibility.MagnificationAnimationCallback; import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.FlakyTest; import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.LocalServices; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java index a50871899575..b69b55448bcd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java @@ -125,6 +125,8 @@ public class BiometricLoggerTest { eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), anyLong(), anyInt(), eq(requireConfirmation), eq(targetUserId), any()); + + verify(mAuthenticationStatsCollector).authenticate(eq(targetUserId), eq(authenticated)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java new file mode 100644 index 000000000000..a7c8a6cee0d9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java @@ -0,0 +1,890 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual; + +import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES; +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + +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.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.WindowConfiguration; +import android.companion.virtual.IVirtualDeviceIntentInterceptor; +import android.companion.virtual.VirtualDeviceManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.net.Uri; +import android.os.RemoteException; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; +import android.util.ArraySet; +import android.view.Display; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.app.BlockedAppStreamingActivity; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class GenericWindowPolicyControllerTest { + + private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY + 1; + private static final int TEST_UID = 1234567; + private static final String DISPLAY_CATEGORY = "com.display.category"; + private static final String NONBLOCKED_APP_PACKAGE_NAME = "com.someapp"; + private static final String BLOCKED_PACKAGE_NAME = "com.blockedapp"; + private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000; + private static final String TEST_SITE = "http://test"; + private static final ComponentName BLOCKED_APP_STREAMING_COMPONENT = + new ComponentName("android", BlockedAppStreamingActivity.class.getName()); + private static final ComponentName BLOCKED_COMPONENT = new ComponentName(BLOCKED_PACKAGE_NAME, + BLOCKED_PACKAGE_NAME); + private static final ComponentName NONBLOCKED_COMPONENT = new ComponentName( + NONBLOCKED_APP_PACKAGE_NAME, NONBLOCKED_APP_PACKAGE_NAME); + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Mock + private GenericWindowPolicyController.PipBlockedCallback mPipBlockedCallback; + @Mock + private VirtualDeviceManager.ActivityListener mActivityListener; + @Mock + private GenericWindowPolicyController.IntentListenerCallback mIntentListenerCallback; + @Mock + private GenericWindowPolicyController.ActivityBlockedCallback mActivityBlockedCallback; + @Mock + private GenericWindowPolicyController.RunningAppsChangedListener mRunningAppsChangedListener; + @Mock + private GenericWindowPolicyController.SecureWindowCallback mSecureWindowCallback; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void showTasksInHostDeviceRecents() { + GenericWindowPolicyController gwpc = createGwpc(); + + gwpc.setShowInHostDeviceRecents(true); + assertThat(gwpc.canShowTasksInHostDeviceRecents()).isTrue(); + + gwpc.setShowInHostDeviceRecents(false); + assertThat(gwpc.canShowTasksInHostDeviceRecents()).isFalse(); + } + + @Test + public void containsUid() { + GenericWindowPolicyController gwpc = createGwpc(); + + assertThat(gwpc.containsUid(TEST_UID)).isFalse(); + + gwpc.onRunningAppsChanged(new ArraySet<>(Arrays.asList(TEST_UID))); + assertThat(gwpc.containsUid(TEST_UID)).isTrue(); + + gwpc.onRunningAppsChanged(new ArraySet<>()); + assertThat(gwpc.containsUid(TEST_UID)).isFalse(); + } + + @Test + public void isEnteringPipAllowed_falseByDefault() { + GenericWindowPolicyController gwpc = createGwpc(); + + assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isFalse(); + verify(mPipBlockedCallback).onEnteringPipBlocked(TEST_UID); + } + + @Test + public void isEnteringPipAllowed_dpcSupportsPinned_allowed() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setSupportedWindowingModes(new HashSet<>( + Arrays.asList(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, + WindowConfiguration.WINDOWING_MODE_PINNED))); + assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isTrue(); + verify(mPipBlockedCallback, never()).onEnteringPipBlocked(TEST_UID); + } + + @Test + public void openNonBlockedAppOnVirtualDisplay_isNotBlocked() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityCanBeLaunched(gwpc, activityInfo); + } + + @Test + public void activityDoesNotSupportDisplayOnRemoteDevices_isBlocked() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ false, + /* targetDisplayCategory */ null); + assertActivityIsBlocked(gwpc, activityInfo); + } + + @Test + public void openBlockedComponentOnVirtualDisplay_isBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithBlockedComponent(BLOCKED_COMPONENT); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_PACKAGE_NAME, + BLOCKED_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityIsBlocked(gwpc, activityInfo); + } + + @Test + public void addActivityPolicyExemption_openBlockedOnVirtualDisplay_isBlocked() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + gwpc.setActivityLaunchDefaultAllowed(true); + gwpc.addActivityPolicyExemption(BLOCKED_COMPONENT); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_PACKAGE_NAME, + BLOCKED_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityIsBlocked(gwpc, activityInfo); + } + + @Test + public void openNotAllowedComponentOnBlocklistVirtualDisplay_isBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_PACKAGE_NAME, + BLOCKED_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityIsBlocked(gwpc, activityInfo); + } + + @Test + public void addActivityPolicyExemption_openNotAllowedOnVirtualDisplay_isBlocked() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + gwpc.setActivityLaunchDefaultAllowed(false); + gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_PACKAGE_NAME, + BLOCKED_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityIsBlocked(gwpc, activityInfo); + } + + @Test + public void openAllowedComponentOnBlocklistVirtualDisplay_startsActivity() { + GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityCanBeLaunched(gwpc, activityInfo); + } + + @Test + public void addActivityPolicyExemption_openAllowedOnVirtualDisplay_startsActivity() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + gwpc.setActivityLaunchDefaultAllowed(false); + gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityCanBeLaunched(gwpc, activityInfo); + } + + @Test + public void canActivityBeLaunched_mismatchingUserHandle_isBlocked() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null, + /* uid */ UserHandle.PER_USER_RANGE + 1); + assertActivityIsBlocked(gwpc, activityInfo); + } + + @Test + public void canActivityBeLaunched_blockedAppStreamingComponent_isNeverBlocked() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_APP_STREAMING_COMPONENT.getPackageName(), + BLOCKED_APP_STREAMING_COMPONENT.getClassName(), + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityCanBeLaunched(gwpc, activityInfo); + } + + @Test + public void canActivityBeLaunched_blockedAppStreamingComponentExplicitlyBlocked_isNeverBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithBlockedComponent( + BLOCKED_APP_STREAMING_COMPONENT); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_APP_STREAMING_COMPONENT.getPackageName(), + BLOCKED_APP_STREAMING_COMPONENT.getClassName(), + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + + assertActivityCanBeLaunched(gwpc, activityInfo); + } + + @Test + public void canActivityBeLaunched_blockedAppStreamingComponentExemptFromStreaming_isNeverBlocked() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + gwpc.setActivityLaunchDefaultAllowed(true); + gwpc.addActivityPolicyExemption(BLOCKED_APP_STREAMING_COMPONENT); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_APP_STREAMING_COMPONENT.getPackageName(), + BLOCKED_APP_STREAMING_COMPONENT.getClassName(), + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + + assertActivityCanBeLaunched(gwpc, activityInfo); + } + + @Test + public void canActivityBeLaunched_blockedAppStreamingComponentNotAllowlisted_isNeverBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithAllowedComponent(NONBLOCKED_COMPONENT); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_APP_STREAMING_COMPONENT.getPackageName(), + BLOCKED_APP_STREAMING_COMPONENT.getClassName(), + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + + assertActivityCanBeLaunched(gwpc, activityInfo); + } + + @Test + public void canActivityBeLaunched_blockedAppStreamingComponentNotExemptFromBlocklist_isNeverBlocked() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + gwpc.setActivityLaunchDefaultAllowed(false); + gwpc.addActivityPolicyExemption(NONBLOCKED_COMPONENT); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_APP_STREAMING_COMPONENT.getPackageName(), + BLOCKED_APP_STREAMING_COMPONENT.getClassName(), + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + + assertActivityCanBeLaunched(gwpc, activityInfo); + } + + @Test + public void canActivityBeLaunched_customDisplayCategoryMatches_isNotBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithDisplayCategory(DISPLAY_CATEGORY); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ DISPLAY_CATEGORY); + + assertActivityCanBeLaunched(gwpc, activityInfo); + } + + @Test + public void canActivityBeLaunched_customDisplayCategoryDoesNotMatch_isBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithDisplayCategory(DISPLAY_CATEGORY); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ "some.random.category"); + assertActivityIsBlocked(gwpc, activityInfo); + } + + @Test + public void canActivityBeLaunched_crossTaskLaunch_fromDefaultDisplay_isNotBlocked() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityCanBeLaunched(gwpc, Display.DEFAULT_DISPLAY, true, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo); + } + + @Test + public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_notExplicitlyBlocked_isNotBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationBlockedFor( + BLOCKED_COMPONENT); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + + assertActivityCanBeLaunched(gwpc, DISPLAY_ID, true, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo); + } + + @Test + public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_explicitlyBlocked_isBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationBlockedFor( + BLOCKED_COMPONENT); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_PACKAGE_NAME, + BLOCKED_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityIsBlocked(gwpc, DISPLAY_ID, true, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo); + } + + @Test + public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_notAllowed_isBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationAllowed( + NONBLOCKED_COMPONENT); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_PACKAGE_NAME, + BLOCKED_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityIsBlocked(gwpc, DISPLAY_ID, true, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo); + } + + @Test + public void canActivityBeLaunched_crossTaskLaunchFromVirtualDisplay_allowed_isNotBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithCrossTaskNavigationAllowed( + NONBLOCKED_COMPONENT); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityCanBeLaunched(gwpc, DISPLAY_ID, true, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo); + } + + @Test + public void canActivityBeLaunched_unsupportedWindowingMode_isBlocked() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityIsBlocked(gwpc, DISPLAY_ID, true, WindowConfiguration.WINDOWING_MODE_PINNED, + activityInfo); + } + + @Test + public void canActivityBeLaunched_permissionComponent_isBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithPermissionComponent(BLOCKED_COMPONENT); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + BLOCKED_PACKAGE_NAME, + BLOCKED_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityIsBlocked(gwpc, activityInfo); + } + + @Test + public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() { + ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(TEST_UID)); + GenericWindowPolicyController gwpc = createGwpc(); + + gwpc.registerRunningAppsChangedListener(mRunningAppsChangedListener); + gwpc.onRunningAppsChanged(uids); + + assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(1); + verify(mRunningAppsChangedListener).onRunningAppsChanged(uids); + } + + @Test + public void onRunningAppsChanged_empty_onDisplayEmpty() { + ArraySet<Integer> uids = new ArraySet<>(); + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + + gwpc.onRunningAppsChanged(uids); + + assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0); + verify(mActivityListener).onDisplayEmpty(DISPLAY_ID); + } + + @Test + public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() { + ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(TEST_UID)); + GenericWindowPolicyController gwpc = createGwpc(); + + gwpc.onRunningAppsChanged(uids); + + assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0); + verify(mRunningAppsChangedListener, never()).onRunningAppsChanged(uids); + } + + @Test + public void registerUnregisterRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() { + ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(TEST_UID)); + GenericWindowPolicyController gwpc = createGwpc(); + + gwpc.registerRunningAppsChangedListener(mRunningAppsChangedListener); + gwpc.unregisterRunningAppsChangedListener(mRunningAppsChangedListener); + gwpc.onRunningAppsChanged(uids); + + assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0); + verify(mRunningAppsChangedListener, never()).onRunningAppsChanged(uids); + } + + @Test + public void canActivityBeLaunched_intentInterceptedWhenRegistered_activityNoLaunch() + throws RemoteException { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(TEST_SITE)); + + IVirtualDeviceIntentInterceptor.Stub interceptor = + mock(IVirtualDeviceIntentInterceptor.Stub.class); + doNothing().when(interceptor).onIntentIntercepted(any()); + doReturn(interceptor).when(interceptor).asBinder(); + doReturn(interceptor).when(interceptor).queryLocalInterface(anyString()); + + GenericWindowPolicyController gwpc = createGwpc(); + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + + // register interceptor and intercept intent + when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(true); + assertThat(gwpc.canActivityBeLaunched(activityInfo, intent, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false)) + .isFalse(); + + // unregister interceptor and launch activity + when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(false); + assertThat(gwpc.canActivityBeLaunched(activityInfo, intent, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false)) + .isTrue(); + } + + @Test + public void canActivityBeLaunched_noMatchIntentFilter_activityLaunches() { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("testing")); + + GenericWindowPolicyController gwpc = createGwpc(); + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + + // register interceptor with different filter + when(mIntentListenerCallback.shouldInterceptIntent(any(Intent.class))).thenReturn(false); + assertThat(gwpc.canActivityBeLaunched(activityInfo, intent, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false)) + .isTrue(); + verify(mIntentListenerCallback).shouldInterceptIntent(any(Intent.class)); + } + + @Test + public void onTopActivitychanged_null_noCallback() { + GenericWindowPolicyController gwpc = createGwpc(); + + gwpc.onTopActivityChanged(null, 0, 0); + verify(mActivityListener, never()) + .onTopActivityChanged(anyInt(), any(ComponentName.class), anyInt()); + } + + @Test + public void onTopActivitychanged_activityListenerCallbackObserved() { + int userId = 1000; + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + + gwpc.onTopActivityChanged(BLOCKED_COMPONENT, 0, userId); + verify(mActivityListener) + .onTopActivityChanged(eq(DISPLAY_ID), eq(BLOCKED_COMPONENT), eq(userId)); + } + + @Test + public void keepActivityOnWindowFlagsChanged_noChange() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + + assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, 0)).isTrue(); + + verify(mSecureWindowCallback, never()).onSecureWindowShown(DISPLAY_ID, + activityInfo.applicationInfo.uid); + verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo); + } + + @Test + public void keepActivityOnWindowFlagsChanged_flagSecure_isAllowedAfterTM() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + + assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, FLAG_SECURE, 0)).isTrue(); + + verify(mSecureWindowCallback).onSecureWindowShown(DISPLAY_ID, + activityInfo.applicationInfo.uid); + verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo); + } + + @Test + public void keepActivityOnWindowFlagsChanged_systemFlagHideNonSystemOverlayWindows_isAllowedAfterTM() { + GenericWindowPolicyController gwpc = createGwpc(); + gwpc.setDisplayId(DISPLAY_ID); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + + assertThat(gwpc.keepActivityOnWindowFlagsChanged(activityInfo, 0, + SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)).isTrue(); + + verify(mSecureWindowCallback, never()).onSecureWindowShown(DISPLAY_ID, + activityInfo.applicationInfo.uid); + verify(mActivityBlockedCallback, never()).onActivityBlocked(DISPLAY_ID, activityInfo); + } + + @Test + public void getCustomHomeComponent_noneSet() { + GenericWindowPolicyController gwpc = createGwpc(); + + assertThat(gwpc.getCustomHomeComponent()).isNull(); + } + + @Test + public void getCustomHomeComponent_returnsHomeComponent() { + GenericWindowPolicyController gwpc = createGwpcWithCustomHomeComponent( + NONBLOCKED_COMPONENT); + + assertThat(gwpc.getCustomHomeComponent()).isEqualTo(NONBLOCKED_COMPONENT); + } + + private GenericWindowPolicyController createGwpc() { + return new GenericWindowPolicyController( + 0, + 0, + /* allowedUsers= */ new ArraySet<>(getCurrentUserId()), + /* activityLaunchAllowedByDefault= */ true, + /* activityPolicyExemptions= */ new ArraySet<>(), + /* crossTaskNavigationAllowedByDefault= */ true, + /* crossTaskNavigationExemptions= */ new ArraySet<>(), + /* permissionDialogComponent= */ null, + /* activityListener= */ mActivityListener, + /* pipBlockedCallback= */ mPipBlockedCallback, + /* activityBlockedCallback= */ mActivityBlockedCallback, + /* secureWindowCallback= */ mSecureWindowCallback, + /* intentListenerCallback= */ mIntentListenerCallback, + /* displayCategories= */ new ArraySet<>(), + /* showTasksInHostDeviceRecents= */ true, + /* customHomeComponent= */ null); + } + + private GenericWindowPolicyController createGwpcWithCustomHomeComponent( + ComponentName homeComponent) { + return new GenericWindowPolicyController( + 0, + 0, + /* allowedUsers= */ new ArraySet<>(getCurrentUserId()), + /* activityLaunchAllowedByDefault= */ true, + /* activityPolicyExemptions= */ new ArraySet<>(), + /* crossTaskNavigationAllowedByDefault= */ true, + /* crossTaskNavigationExemptions= */ new ArraySet<>(), + /* permissionDialogComponent= */ null, + /* activityListener= */ mActivityListener, + /* pipBlockedCallback= */ mPipBlockedCallback, + /* activityBlockedCallback= */ mActivityBlockedCallback, + /* secureWindowCallback= */ null, + /* intentListenerCallback= */ mIntentListenerCallback, + /* displayCategories= */ new ArraySet<>(), + /* showTasksInHostDeviceRecents= */ true, + /* customHomeComponent= */ homeComponent); + } + + private GenericWindowPolicyController createGwpcWithBlockedComponent( + ComponentName blockedComponent) { + return new GenericWindowPolicyController( + 0, + 0, + /* allowedUsers= */ new ArraySet<>(getCurrentUserId()), + /* activityLaunchAllowedByDefault= */ true, + /* activityPolicyExemptions= */ Collections.singleton(blockedComponent), + /* crossTaskNavigationAllowedByDefault= */ true, + /* crossTaskNavigationExemptions= */ new ArraySet<>(), + /* permissionDialogComponent= */ null, + /* activityListener= */ mActivityListener, + /* pipBlockedCallback= */ mPipBlockedCallback, + /* activityBlockedCallback= */ mActivityBlockedCallback, + /* secureWindowCallback= */ null, + /* intentListenerCallback= */ mIntentListenerCallback, + /* displayCategories= */ new ArraySet<>(), + /* showTasksInHostDeviceRecents= */ true, + /* customHomeComponent= */ null); + } + + private GenericWindowPolicyController createGwpcWithAllowedComponent( + ComponentName allowedComponent) { + return new GenericWindowPolicyController( + 0, + 0, + /* allowedUsers= */ new ArraySet<>(getCurrentUserId()), + /* activityLaunchAllowedByDefault= */ false, + /* activityPolicyExemptions= */ Collections.singleton(allowedComponent), + /* crossTaskNavigationAllowedByDefault= */ true, + /* crossTaskNavigationExemptions= */ new ArraySet<>(), + /* permissionDialogComponent= */ null, + /* activityListener= */ mActivityListener, + /* pipBlockedCallback= */ mPipBlockedCallback, + /* activityBlockedCallback= */ mActivityBlockedCallback, + /* secureWindowCallback= */ null, + /* intentListenerCallback= */ mIntentListenerCallback, + /* displayCategories= */ new ArraySet<>(), + /* showTasksInHostDeviceRecents= */ true, + /* customHomeComponent= */ null); + } + + private GenericWindowPolicyController createGwpcWithDisplayCategory( + String displayCategory) { + return new GenericWindowPolicyController( + 0, + 0, + /* allowedUsers= */ new ArraySet<>(getCurrentUserId()), + /* activityLaunchAllowedByDefault= */ true, + /* activityPolicyExemptions= */ new ArraySet<>(), + /* crossTaskNavigationAllowedByDefault= */ true, + /* crossTaskNavigationExemptions= */ new ArraySet<>(), + /* permissionDialogComponent= */ null, + /* activityListener= */ mActivityListener, + /* pipBlockedCallback= */ mPipBlockedCallback, + /* activityBlockedCallback= */ mActivityBlockedCallback, + /* secureWindowCallback= */ null, + /* intentListenerCallback= */ mIntentListenerCallback, + /* displayCategories= */ Collections.singleton(displayCategory), + /* showTasksInHostDeviceRecents= */ true, + /* customHomeComponent= */ null); + } + + private GenericWindowPolicyController createGwpcWithCrossTaskNavigationBlockedFor( + ComponentName blockedComponent) { + return new GenericWindowPolicyController( + 0, + 0, + /* allowedUsers= */ new ArraySet<>(getCurrentUserId()), + /* activityLaunchAllowedByDefault= */ true, + /* activityPolicyExemptions= */ new ArraySet<>(), + /* crossTaskNavigationAllowedByDefault= */ true, + /* crossTaskNavigationExemptions= */ Collections.singleton(blockedComponent), + /* permissionDialogComponent= */ null, + /* activityListener= */ mActivityListener, + /* pipBlockedCallback= */ mPipBlockedCallback, + /* activityBlockedCallback= */ mActivityBlockedCallback, + /* secureWindowCallback= */ null, + /* intentListenerCallback= */ mIntentListenerCallback, + /* displayCategories= */ new ArraySet<>(), + /* showTasksInHostDeviceRecents= */ true, + /* customHomeComponent= */ null); + } + + private GenericWindowPolicyController createGwpcWithCrossTaskNavigationAllowed( + ComponentName allowedComponent) { + return new GenericWindowPolicyController( + 0, + 0, + /* allowedUsers= */ new ArraySet<>(getCurrentUserId()), + /* activityLaunchAllowedByDefault= */ true, + /* activityPolicyExemptions= */ new ArraySet<>(), + /* crossTaskNavigationAllowedByDefault= */ false, + /* crossTaskNavigationExemptions= */ Collections.singleton(allowedComponent), + /* permissionDialogComponent= */ null, + /* activityListener= */ mActivityListener, + /* pipBlockedCallback= */ mPipBlockedCallback, + /* activityBlockedCallback= */ mActivityBlockedCallback, + /* secureWindowCallback= */ null, + /* intentListenerCallback= */ mIntentListenerCallback, + /* displayCategories= */ new ArraySet<>(), + /* showTasksInHostDeviceRecents= */ true, + /* customHomeComponent= */ null); + } + + private GenericWindowPolicyController createGwpcWithPermissionComponent( + ComponentName permissionComponent) { + //TODO instert the component + return new GenericWindowPolicyController( + 0, + 0, + /* allowedUsers= */ new ArraySet<>(getCurrentUserId()), + /* activityLaunchAllowedByDefault= */ true, + /* activityPolicyExemptions= */ new ArraySet<>(), + /* crossTaskNavigationAllowedByDefault= */ false, + /* crossTaskNavigationExemptions= */ new ArraySet<>(), + /* permissionDialogComponent= */ permissionComponent, + /* activityListener= */ mActivityListener, + /* pipBlockedCallback= */ mPipBlockedCallback, + /* activityBlockedCallback= */ mActivityBlockedCallback, + /* secureWindowCallback= */ null, + /* intentListenerCallback= */ mIntentListenerCallback, + /* displayCategories= */ new ArraySet<>(), + /* showTasksInHostDeviceRecents= */ true, + /* customHomeComponent= */ null); + } + + private Set<UserHandle> getCurrentUserId() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + return new ArraySet<>(Arrays.asList(context.getUser())); + } + + private ActivityInfo getActivityInfo( + String packageName, String name, boolean displayOnRemoteDevices, + String requiredDisplayCategory) { + return getActivityInfo(packageName, name, displayOnRemoteDevices, requiredDisplayCategory, + 0); + } + + private ActivityInfo getActivityInfo( + String packageName, String name, boolean displayOnRemoteDevices, + String requiredDisplayCategory, int uid) { + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = uid; + + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.packageName = packageName; + activityInfo.name = name; + activityInfo.flags = displayOnRemoteDevices + ? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES; + activityInfo.applicationInfo = applicationInfo; + activityInfo.requiredDisplayCategory = requiredDisplayCategory; + return activityInfo; + } + + private void assertActivityCanBeLaunched(GenericWindowPolicyController gwpc, + ActivityInfo activityInfo) { + assertActivityCanBeLaunched(gwpc, DISPLAY_ID, false, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo); + } + + private void assertActivityCanBeLaunched(GenericWindowPolicyController gwpc, int fromDisplay, + boolean isNewTask, int windowingMode, ActivityInfo activityInfo) { + assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay, + isNewTask)).isTrue(); + + verify(mActivityBlockedCallback, never()).onActivityBlocked(fromDisplay, activityInfo); + verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class)); + } + + private void assertActivityIsBlocked(GenericWindowPolicyController gwpc, + ActivityInfo activityInfo) { + assertActivityIsBlocked(gwpc, DISPLAY_ID, false, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, activityInfo); + } + + private void assertActivityIsBlocked(GenericWindowPolicyController gwpc, int fromDisplay, + boolean isNewTask, int windowingMode, ActivityInfo activityInfo) { + assertThat(gwpc.canActivityBeLaunched(activityInfo, null, windowingMode, fromDisplay, + isNewTask)).isFalse(); + + verify(mActivityBlockedCallback).onActivityBlocked(fromDisplay, activityInfo); + verify(mIntentListenerCallback, never()).shouldInterceptIntent(any(Intent.class)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java new file mode 100644 index 000000000000..8f77e9b8d523 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual; + +import static com.google.common.truth.Truth.assertThat; + +import android.Manifest; +import android.companion.virtual.VirtualDeviceParams; +import android.companion.virtual.camera.IVirtualCamera; +import android.companion.virtual.camera.IVirtualCameraSession; +import android.companion.virtual.camera.VirtualCamera; +import android.companion.virtual.camera.VirtualCameraConfig; +import android.companion.virtual.camera.VirtualCameraHalConfig; +import android.companion.virtual.camera.VirtualCameraSession; +import android.companion.virtual.camera.VirtualCameraStreamConfig; +import android.companion.virtual.flags.Flags; +import android.content.ComponentName; +import android.graphics.ImageFormat; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Looper; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.testing.TestableLooper; + +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.compatibility.common.util.AdoptShellPermissionsRule; +import com.android.server.companion.virtual.camera.IVirtualCameraService; +import com.android.server.companion.virtual.camera.VirtualCameraController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashSet; +import java.util.Set; + +@Presubmit +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class VirtualCameraTest { + + private static final String PKG = "com.android.virtualcamera"; + private static final String CLS = ".VirtualCameraService"; + public static final String CAMERA_DISPLAY_NAME = "testCamera"; + + private final TestableContext mContext = + new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()); + private FakeVirtualCameraService mFakeVirtualCameraService; + private VirtualCameraController mVirtualCameraController; + + @Rule public final VirtualDeviceRule mVirtualDeviceRule = new VirtualDeviceRule(mContext); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Rule + public AdoptShellPermissionsRule mAdoptShellPermissionsRule = + new AdoptShellPermissionsRule( + InstrumentationRegistry.getInstrumentation().getUiAutomation(), + Manifest.permission.CREATE_VIRTUAL_DEVICE); + + @Before + public void setUp() { + mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> mVirtualCameraController); + mFakeVirtualCameraService = new FakeVirtualCameraService(); + connectFakeService(); + mVirtualCameraController = new VirtualCameraController(mContext); + } + + private VirtualDeviceImpl createVirtualDevice() { + return mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build()); + } + + private void connectFakeService() { + mContext.addMockService( + ComponentName.createRelative(PKG, CLS), mFakeVirtualCameraService.asBinder()); + } + + @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA) + @Test + public void addVirtualCamera() { + VirtualDeviceImpl virtualDevice = createVirtualDevice(); + VirtualCameraConfig config = createVirtualCameraConfig(null); + IVirtualCamera.Default camera = new IVirtualCamera.Default(); + virtualDevice.registerVirtualCamera(camera); + + assertThat(mFakeVirtualCameraService.mCameras).contains(camera); + } + + @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA) + @Test + public void addVirtualCamera_serviceNotReady() { + TestableContext context = + new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()); + VirtualCameraController virtualCameraController = new VirtualCameraController(context); + mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> virtualCameraController); + + VirtualDeviceImpl virtualDevice = + mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build()); + IVirtualCamera.Default camera = new IVirtualCamera.Default(); + VirtualCameraConfig config = createVirtualCameraConfig(null); + virtualDevice.registerVirtualCamera(camera); + FakeVirtualCameraService fakeVirtualCameraService = new FakeVirtualCameraService(); + + // Only add the service after connecting the camera + virtualCameraController.onServiceConnected( + ComponentName.createRelative(PKG, CLS), fakeVirtualCameraService.asBinder()); + + assertThat(fakeVirtualCameraService.mCameras).contains(camera); + } + + @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA) + @Test + public void getCameraConfiguration() { + VirtualDeviceImpl virtualDevice = createVirtualDevice(); + VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {}; + VirtualCameraConfig config = + new VirtualCameraConfig.Builder() + .addStreamConfiguration(10, 10, ImageFormat.RGB_565) + .setDisplayName(CAMERA_DISPLAY_NAME) + .setCallback( + new HandlerExecutor(new Handler(Looper.getMainLooper())), + () -> virtualCameraSession) + .build(); + + VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config); + + VirtualCameraConfig returnedConfig = virtualCamera.getConfig(); + assertThat(returnedConfig).isNotNull(); + assertThat(returnedConfig.getDisplayName()).isEqualTo(CAMERA_DISPLAY_NAME); + Set<VirtualCameraStreamConfig> streamConfigs = returnedConfig.getStreamConfigs(); + assertThat(streamConfigs).hasSize(1); + VirtualCameraStreamConfig streamConfig = + streamConfigs.toArray(new VirtualCameraStreamConfig[0])[0]; + assertThat(streamConfig.format).isEqualTo(ImageFormat.RGB_565); + assertThat(streamConfig.width).isEqualTo(10); + assertThat(streamConfig.height).isEqualTo(10); + + VirtualCameraHalConfig halConfig = virtualCamera.getHalConfig(); + assertThat(halConfig).isNotNull(); + assertThat(halConfig.displayName).isEqualTo(CAMERA_DISPLAY_NAME); + assertThat(halConfig.streamConfigs).asList().hasSize(1); + assertThat(halConfig.streamConfigs[0].format).isEqualTo(ImageFormat.RGB_565); + assertThat(halConfig.streamConfigs[0].width).isEqualTo(10); + assertThat(halConfig.streamConfigs[0].height).isEqualTo(10); + } + + @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA) + @Test + public void createCameraWithVirtualCameraInstance() { + VirtualDeviceImpl virtualDevice = createVirtualDevice(); + + VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {}; + VirtualCameraConfig config = createVirtualCameraConfig(virtualCameraSession); + VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config); + + assertThat(mFakeVirtualCameraService.mCameras).contains(virtualCamera); + assertThat(virtualCamera.open()).isInstanceOf(IVirtualCameraSession.class); + } + + @RequiresFlagsDisabled(Flags.FLAG_VIRTUAL_CAMERA) + @Test + public void createCameraDoesNothingWhenControllerIsNull() { + mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> null); + VirtualDeviceImpl virtualDevice = createVirtualDevice(); + IVirtualCamera.Default camera = new IVirtualCamera.Default(); + VirtualCameraConfig config = createVirtualCameraConfig(null); + virtualDevice.registerVirtualCamera(camera); + + assertThat(mFakeVirtualCameraService.mCameras).doesNotContain(camera); + } + + @NonNull + private static VirtualCameraConfig createVirtualCameraConfig( + VirtualCameraSession virtualCameraSession) { + return new VirtualCameraConfig.Builder() + .addStreamConfiguration(10, 10, ImageFormat.RGB_565) + .setDisplayName(CAMERA_DISPLAY_NAME) + .setCallback( + new HandlerExecutor(new Handler(Looper.getMainLooper())), + () -> virtualCameraSession) + .build(); + } + + private static class FakeVirtualCameraService extends IVirtualCameraService.Stub { + + final Set<IVirtualCamera> mCameras = new HashSet<>(); + + @Override + public boolean registerCamera(IVirtualCamera camera) throws RemoteException { + mCameras.add(camera); + return true; + } + + @Override + public void unregisterCamera(IVirtualCamera camera) throws RemoteException { + mCameras.remove(camera); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 41af9e31dbdd..61b30a024478 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -24,6 +24,7 @@ import static android.content.Context.DEVICE_ID_DEFAULT; import static android.content.Context.DEVICE_ID_INVALID; import static android.content.Intent.ACTION_VIEW; import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES; +import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -57,6 +58,7 @@ import android.companion.virtual.IVirtualDeviceSoundEffectListener; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; +import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.IVirtualSensorCallback; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorCallback; @@ -69,6 +71,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.hardware.Sensor; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; @@ -100,6 +103,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.WorkSource; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArraySet; @@ -114,6 +118,7 @@ import com.android.compatibility.common.util.AdoptShellPermissionsRule; import com.android.internal.app.BlockedAppStreamingActivity; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; +import com.android.server.companion.virtual.camera.VirtualCameraController; import com.android.server.input.InputManagerInternal; import com.android.server.sensors.SensorManagerInternal; @@ -211,6 +216,9 @@ public class VirtualDeviceManagerServiceTest { private static final String TEST_SITE = "http://test"; @Rule + public SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Rule public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( InstrumentationRegistry.getInstrumentation().getUiAutomation(), Manifest.permission.CREATE_VIRTUAL_DEVICE); @@ -298,7 +306,7 @@ public class VirtualDeviceManagerServiceTest { ActivityInfo activityInfo = getActivityInfo( NONBLOCKED_APP_PACKAGE_NAME, NONBLOCKED_APP_PACKAGE_NAME, - /* displayOnRemoveDevices= */ true, + /* displayOnRemoteDevices= */ true, targetDisplayCategory); Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( activityInfo, mAssociationInfo.getDisplayName()); @@ -309,12 +317,12 @@ public class VirtualDeviceManagerServiceTest { private ActivityInfo getActivityInfo( - String packageName, String name, boolean displayOnRemoveDevices, + String packageName, String name, boolean displayOnRemoteDevices, String requiredDisplayCategory) { ActivityInfo activityInfo = new ActivityInfo(); activityInfo.packageName = packageName; activityInfo.name = name; - activityInfo.flags = displayOnRemoveDevices + activityInfo.flags = displayOnRemoteDevices ? FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES : FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES; activityInfo.applicationInfo = mApplicationInfoMock; activityInfo.requiredDisplayCategory = requiredDisplayCategory; @@ -328,6 +336,11 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); + mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS); + mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY); + mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS); + mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME); + doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt()); @@ -1417,7 +1430,7 @@ public class VirtualDeviceManagerServiceTest { ActivityInfo activityInfo = getActivityInfo( NONBLOCKED_APP_PACKAGE_NAME, NONBLOCKED_APP_PACKAGE_NAME, - /* displayOnRemoveDevices */ true, + /* displayOnRemoteDevices */ true, /* targetDisplayCategory */ null); Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( activityInfo, mAssociationInfo.getDisplayName()); @@ -1438,7 +1451,51 @@ public class VirtualDeviceManagerServiceTest { ActivityInfo activityInfo = getActivityInfo( PERMISSION_CONTROLLER_PACKAGE_NAME, PERMISSION_CONTROLLER_PACKAGE_NAME, - /* displayOnRemoveDevices */ false, + /* displayOnRemoteDevices */ false, + /* targetDisplayCategory */ null); + Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( + activityInfo, mAssociationInfo.getDisplayName()); + gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false); + + verify(mContext).startActivityAsUser(argThat(intent -> + intent.filterEquals(blockedAppIntent)), any(), any()); + } + + @Test + public void openPermissionControllerOnVirtualDisplay_displayOnRemoteDevices_startsWhenFlagIsEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( + DISPLAY_ID_1); + doNothing().when(mContext).startActivityAsUser(any(), any(), any()); + + ActivityInfo activityInfo = getActivityInfo( + PERMISSION_CONTROLLER_PACKAGE_NAME, + PERMISSION_CONTROLLER_PACKAGE_NAME, + /* displayOnRemoveDevices */ true, + /* targetDisplayCategory */ null); + Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( + activityInfo, mAssociationInfo.getDisplayName()); + gwpc.canActivityBeLaunched(activityInfo, blockedAppIntent, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false); + + verify(mContext, never()).startActivityAsUser(argThat(intent -> + intent.filterEquals(blockedAppIntent)), any(), any()); + } + + @Test + public void openPermissionControllerOnVirtualDisplay_dontDisplayOnRemoteDevices_startsWhenFlagIsEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS); + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( + DISPLAY_ID_1); + doNothing().when(mContext).startActivityAsUser(any(), any(), any()); + + ActivityInfo activityInfo = getActivityInfo( + PERMISSION_CONTROLLER_PACKAGE_NAME, + PERMISSION_CONTROLLER_PACKAGE_NAME, + /* displayOnRemoveDevices */ false, /* targetDisplayCategory */ null); Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( activityInfo, mAssociationInfo.getDisplayName()); @@ -1459,7 +1516,7 @@ public class VirtualDeviceManagerServiceTest { ActivityInfo activityInfo = getActivityInfo( SETTINGS_PACKAGE_NAME, SETTINGS_PACKAGE_NAME, - /* displayOnRemoveDevices */ true, + /* displayOnRemoteDevices */ true, /* targetDisplayCategory */ null); Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( activityInfo, mAssociationInfo.getDisplayName()); @@ -1480,7 +1537,7 @@ public class VirtualDeviceManagerServiceTest { ActivityInfo activityInfo = getActivityInfo( VENDING_PACKAGE_NAME, VENDING_PACKAGE_NAME, - /* displayOnRemoveDevices */ true, + /* displayOnRemoteDevices */ true, /* targetDisplayCategory */ null); Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( activityInfo, mAssociationInfo.getDisplayName()); @@ -1501,7 +1558,7 @@ public class VirtualDeviceManagerServiceTest { ActivityInfo activityInfo = getActivityInfo( GOOGLE_DIALER_PACKAGE_NAME, GOOGLE_DIALER_PACKAGE_NAME, - /* displayOnRemoveDevices */ true, + /* displayOnRemoteDevices */ true, /* targetDisplayCategory */ null); Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( activityInfo, mAssociationInfo.getDisplayName()); @@ -1522,7 +1579,7 @@ public class VirtualDeviceManagerServiceTest { ActivityInfo activityInfo = getActivityInfo( GOOGLE_MAPS_PACKAGE_NAME, GOOGLE_MAPS_PACKAGE_NAME, - /* displayOnRemoveDevices */ true, + /* displayOnRemoteDevices */ true, /* targetDisplayCategory */ null); Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( activityInfo, mAssociationInfo.getDisplayName()); @@ -1562,6 +1619,54 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void canActivityBeLaunched_permissionDialog_flagDisabled_isBlocked() { + mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS); + VirtualDeviceParams params = new VirtualDeviceParams.Builder().build(); + mDeviceImpl.close(); + mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params); + doNothing().when(mContext).startActivityAsUser(any(), any(), any()); + + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( + DISPLAY_ID_1); + ComponentName permissionComponent = getPermissionDialogComponent(); + ActivityInfo activityInfo = getActivityInfo( + permissionComponent.getPackageName(), + permissionComponent.getClassName(), + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertThat(gwpc.canActivityBeLaunched(activityInfo, null, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false)) + .isFalse(); + + Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent( + activityInfo, mAssociationInfo.getDisplayName()); + verify(mContext).startActivityAsUser(argThat(intent -> + intent.filterEquals(blockedAppIntent)), any(), any()); + } + + @Test + public void canActivityBeLaunched_permissionDialog_flagEnabled_isStreamed() { + mSetFlagsRule.enableFlags(Flags.FLAG_STREAM_PERMISSIONS); + VirtualDeviceParams params = new VirtualDeviceParams.Builder().build(); + mDeviceImpl.close(); + mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1, params); + + addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest( + DISPLAY_ID_1); + ComponentName permissionComponent = getPermissionDialogComponent(); + ActivityInfo activityInfo = getActivityInfo( + permissionComponent.getPackageName(), + permissionComponent.getClassName(), + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertThat(gwpc.canActivityBeLaunched(activityInfo, null, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false)) + .isTrue(); + } + + @Test public void canActivityBeLaunched_activityCanLaunch() { Intent intent = new Intent(ACTION_VIEW, Uri.parse(TEST_SITE)); addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); @@ -1570,7 +1675,7 @@ public class VirtualDeviceManagerServiceTest { ActivityInfo activityInfo = getActivityInfo( NONBLOCKED_APP_PACKAGE_NAME, NONBLOCKED_APP_PACKAGE_NAME, - /* displayOnRemoveDevices */ true, + /* displayOnRemoteDevices */ true, /* targetDisplayCategory */ null); assertThat(gwpc.canActivityBeLaunched(activityInfo, intent, WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false)) @@ -1594,7 +1699,7 @@ public class VirtualDeviceManagerServiceTest { ActivityInfo activityInfo = getActivityInfo( NONBLOCKED_APP_PACKAGE_NAME, NONBLOCKED_APP_PACKAGE_NAME, - /* displayOnRemoveDevices */ true, + /* displayOnRemoteDevices */ true, /* targetDisplayCategory */ null); IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW); @@ -1637,7 +1742,7 @@ public class VirtualDeviceManagerServiceTest { ActivityInfo activityInfo = getActivityInfo( NONBLOCKED_APP_PACKAGE_NAME, NONBLOCKED_APP_PACKAGE_NAME, - /* displayOnRemoveDevices */ true, + /* displayOnRemoteDevices */ true, /* targetDisplayCategory */ null); IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW); @@ -1737,13 +1842,25 @@ public class VirtualDeviceManagerServiceTest { private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid, VirtualDeviceParams params) { - VirtualDeviceImpl virtualDeviceImpl = new VirtualDeviceImpl(mContext, - mAssociationInfo, mVdms, mVirtualDeviceLog, new Binder(), - new AttributionSource(ownerUid, "com.android.virtualdevice.test", "virtualdevice"), - virtualDeviceId, - mInputController, mCameraAccessController, - mPendingTrampolineCallback, mActivityListener, mSoundEffectListener, - mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager)); + VirtualDeviceImpl virtualDeviceImpl = + new VirtualDeviceImpl( + mContext, + mAssociationInfo, + mVdms, + mVirtualDeviceLog, + new Binder(), + new AttributionSource( + ownerUid, "com.android.virtualdevice.test", "virtualdevice"), + virtualDeviceId, + mInputController, + mCameraAccessController, + mPendingTrampolineCallback, + mActivityListener, + mSoundEffectListener, + mRunningAppsChangedCallback, + params, + new DisplayManagerGlobal(mIDisplayManager), + new VirtualCameraController(mContext)); mVdms.addVirtualDevice(virtualDeviceImpl); assertThat(virtualDeviceImpl.getAssociationId()).isEqualTo(mAssociationInfo.getId()); assertThat(virtualDeviceImpl.getPersistentDeviceId()) @@ -1758,6 +1875,13 @@ public class VirtualDeviceManagerServiceTest { NONBLOCKED_APP_PACKAGE_NAME); } + private ComponentName getPermissionDialogComponent() { + Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS); + PackageManager packageManager = mContext.getPackageManager(); + intent.setPackage(packageManager.getPermissionControllerPackageName()); + return intent.resolveActivity(packageManager); + } + /** Helper class to drop permissions temporarily and restore them at the end of a test. */ static final class DropShellPermissionsTemporarily implements AutoCloseable { DropShellPermissionsTemporarily() { diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java new file mode 100644 index 000000000000..af633cc75b4d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; + +import android.app.admin.DevicePolicyManager; +import android.companion.AssociationInfo; +import android.companion.virtual.IVirtualDeviceActivityListener; +import android.companion.virtual.IVirtualDeviceSoundEffectListener; +import android.companion.virtual.VirtualDeviceParams; +import android.companion.virtual.flags.Flags; +import android.content.AttributionSource; +import android.content.Context; +import android.hardware.display.DisplayManagerGlobal; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.IDisplayManager; +import android.net.MacAddress; +import android.os.Binder; +import android.testing.TestableContext; +import android.util.ArraySet; +import android.view.DisplayInfo; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.LocalServices; +import com.android.server.companion.virtual.camera.VirtualCameraController; +import com.android.server.input.InputManagerInternal; +import com.android.server.sensors.SensorManagerInternal; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** Test rule to generate instances of {@link VirtualDeviceImpl}. */ +public class VirtualDeviceRule implements TestRule { + + private static final int DEVICE_OWNER_UID = 50; + private static final int VIRTUAL_DEVICE_ID = 42; + + private final Context mContext; + private InputController mInputController; + private CameraAccessController mCameraAccessController; + private AssociationInfo mAssociationInfo; + private VirtualDeviceManagerService mVdms; + private VirtualDeviceManagerInternal mLocalService; + private VirtualDeviceLog mVirtualDeviceLog; + + // Mocks + @Mock private InputController.NativeWrapper mNativeWrapperMock; + @Mock private DisplayManagerInternal mDisplayManagerInternalMock; + @Mock private IDisplayManager mIDisplayManager; + @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback; + @Mock private DevicePolicyManager mDevicePolicyManagerMock; + @Mock private InputManagerInternal mInputManagerInternalMock; + @Mock private SensorManagerInternal mSensorManagerInternalMock; + @Mock private IVirtualDeviceActivityListener mActivityListener; + @Mock private IVirtualDeviceSoundEffectListener mSoundEffectListener; + @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; + @Mock private CameraAccessController.CameraAccessBlockedCallback mCameraAccessBlockedCallback; + + // Test instance suppliers + private Supplier<VirtualCameraController> mVirtualCameraControllerSupplier; + + /** + * Create a new {@link VirtualDeviceRule} + * + * @param context The context to be used with the rule. + */ + public VirtualDeviceRule(@NonNull Context context) { + Objects.requireNonNull(context); + mContext = context; + } + + /** + * Sets a supplier that will supply an instance of {@link VirtualCameraController}. If the + * supplier returns null, a new instance will be created. + */ + public VirtualDeviceRule withVirtualCameraControllerSupplier( + Supplier<VirtualCameraController> virtualCameraControllerSupplier) { + mVirtualCameraControllerSupplier = virtualCameraControllerSupplier; + return this; + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + init(new TestableContext(mContext)); + base.evaluate(); + } + }; + } + + private void init(@NonNull TestableContext context) { + MockitoAnnotations.initMocks(this); + + LocalServices.removeServiceForTest(DisplayManagerInternal.class); + LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); + + doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); + doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); + doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt()); + LocalServices.removeServiceForTest(InputManagerInternal.class); + LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); + + LocalServices.removeServiceForTest(SensorManagerInternal.class); + LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock); + + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.uniqueId = "uniqueId"; + doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt()); + LocalServices.removeServiceForTest(DisplayManagerInternal.class); + LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); + + context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManagerMock); + + // Allow virtual devices to be created on the looper thread for testing. + final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true; + mInputController = + new InputController( + mNativeWrapperMock, + InstrumentationRegistry.getInstrumentation() + .getContext() + .getMainThreadHandler(), + context.getSystemService(WindowManager.class), + threadVerifier); + mCameraAccessController = + new CameraAccessController(context, mLocalService, mCameraAccessBlockedCallback); + + mAssociationInfo = + new AssociationInfo( + /* associationId= */ 1, + 0, + null, + null, + MacAddress.BROADCAST_ADDRESS, + "", + null, + null, + true, + false, + false, + 0, + 0, + -1); + + mVdms = new VirtualDeviceManagerService(context); + mLocalService = mVdms.getLocalServiceInstance(); + mVirtualDeviceLog = new VirtualDeviceLog(context); + } + + /** + * Create a {@link VirtualDeviceImpl} with the required mocks + * + * @param params See {@link + * android.companion.virtual.VirtualDeviceManager#createVirtualDevice(int, + * VirtualDeviceParams)} + */ + public VirtualDeviceImpl createVirtualDevice(VirtualDeviceParams params) { + VirtualCameraController virtualCameraController = mVirtualCameraControllerSupplier.get(); + if (Flags.virtualCamera()) { + if (virtualCameraController == null) { + virtualCameraController = new VirtualCameraController(mContext); + } + } + + VirtualDeviceImpl virtualDeviceImpl = + new VirtualDeviceImpl( + mContext, + mAssociationInfo, + mVdms, + mVirtualDeviceLog, + new Binder(), + new AttributionSource( + DEVICE_OWNER_UID, + "com.android.virtualdevice.test", + "virtualdevicerule"), + VIRTUAL_DEVICE_ID, + mInputController, + mCameraAccessController, + mPendingTrampolineCallback, + mActivityListener, + mSoundEffectListener, + mRunningAppsChangedCallback, + params, + new DisplayManagerGlobal(mIDisplayManager), + virtualCameraController); + mVdms.addVirtualDevice(virtualDeviceImpl); + return virtualDeviceImpl; + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java index c65452aa2fa1..07dd59d2e2d8 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceTest.java @@ -32,11 +32,12 @@ import android.companion.virtual.VirtualDevice; import android.companion.virtual.flags.Flags; import android.os.Parcel; import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -49,6 +50,10 @@ public class VirtualDeviceTest { private static final int VIRTUAL_DEVICE_ID = 42; private static final String PERSISTENT_ID = "persistentId"; private static final String DEVICE_NAME = "VirtualDeviceName"; + private static final String DISPLAY_NAME = "DisplayName"; + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private IVirtualDevice mVirtualDevice; @@ -87,7 +92,8 @@ public class VirtualDeviceTest { @Test public void parcelable_shouldRecreateSuccessfully() { VirtualDevice originalDevice = - new VirtualDevice(mVirtualDevice, VIRTUAL_DEVICE_ID, PERSISTENT_ID, DEVICE_NAME); + new VirtualDevice(mVirtualDevice, VIRTUAL_DEVICE_ID, PERSISTENT_ID, DEVICE_NAME, + DISPLAY_NAME); Parcel parcel = Parcel.obtain(); originalDevice.writeToParcel(parcel, 0); parcel.setDataPosition(0); @@ -96,11 +102,13 @@ public class VirtualDeviceTest { assertThat(device.getDeviceId()).isEqualTo(VIRTUAL_DEVICE_ID); assertThat(device.getPersistentDeviceId()).isEqualTo(PERSISTENT_ID); assertThat(device.getName()).isEqualTo(DEVICE_NAME); + assertThat(device.getDisplayName().toString()).isEqualTo(DISPLAY_NAME); } - @RequiresFlagsEnabled(Flags.FLAG_VDM_PUBLIC_APIS) @Test public void virtualDevice_getDisplayIds() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS); + VirtualDevice virtualDevice = new VirtualDevice( mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null); @@ -113,9 +121,10 @@ public class VirtualDeviceTest { assertThat(virtualDevice.getDisplayIds()).isEqualTo(displayIds); } - @RequiresFlagsEnabled(Flags.FLAG_VDM_PUBLIC_APIS) @Test public void virtualDevice_hasCustomSensorSupport() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_VDM_PUBLIC_APIS); + VirtualDevice virtualDevice = new VirtualDevice( mVirtualDevice, VIRTUAL_DEVICE_ID, /*persistentId=*/null, /*name=*/null); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java index 1c48b8aa79f9..ef5270e8f003 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java @@ -82,6 +82,7 @@ public class VirtualAudioControllerTest { /* activityPolicyExemptions= */ new ArraySet<>(), /* crossTaskNavigationAllowedByDefault= */ true, /* crossTaskNavigationExemptions= */ new ArraySet<>(), + /* permissionDialogComponent */ null, /* activityListener= */ null, /* pipBlockedCallback= */ null, /* activityBlockedCallback= */ null, diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java index dee77806e4f3..37a6d22f038b 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java @@ -781,7 +781,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode()); assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); - mService.onCleanupUser(PRIMARY_USER_ID); + mService.onUserStopped(PRIMARY_USER_ID); assertNull(mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID)); assertTrue(mLocalService.unlockUserWithToken(handle, token, PRIMARY_USER_ID)); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java index 2c9ba34e850f..e8b7ad7e7389 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java @@ -169,7 +169,7 @@ public class WeakEscrowTokenTests extends BaseLockSettingsServiceTests{ assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID)); assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID)); - mService.onCleanupUser(PRIMARY_USER_ID); + mService.onUserStopped(PRIMARY_USER_ID); assertNull(mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID)); assertTrue(mLocalService.unlockUserWithToken(handle, token, PRIMARY_USER_ID)); diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING index 4d1e4051032f..e9d8b2e709e7 100644 --- a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING +++ b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING @@ -7,7 +7,7 @@ "include-filter": "com.android.server.recoverysystem." }, { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" + "exclude-annotation": "androidx.test.filters.FlakyTest" } ] } diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java index 5a482fc37998..f221b75564a3 100644 --- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java @@ -484,6 +484,7 @@ public class StatusBarManagerServiceTest { new Callback()); verify(mMockStatusBar).requestAddTile( + eq(Binder.getCallingUid()), eq(TEST_COMPONENT), eq(APP_NAME), eq(TILE_LABEL), @@ -534,6 +535,7 @@ public class StatusBarManagerServiceTest { mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback); verify(mMockStatusBar).requestAddTile( + eq(Binder.getCallingUid()), eq(TEST_COMPONENT), eq(APP_NAME), eq(TILE_LABEL), @@ -555,6 +557,7 @@ public class StatusBarManagerServiceTest { mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback); verify(mMockStatusBar).requestAddTile( + eq(Binder.getCallingUid()), eq(TEST_COMPONENT), eq(APP_NAME), eq(TILE_LABEL), @@ -577,6 +580,7 @@ public class StatusBarManagerServiceTest { mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback); verify(mMockStatusBar).requestAddTile( + eq(Binder.getCallingUid()), eq(TEST_COMPONENT), eq(APP_NAME), eq(TILE_LABEL), @@ -599,6 +603,7 @@ public class StatusBarManagerServiceTest { mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback); verify(mMockStatusBar).requestAddTile( + eq(Binder.getCallingUid()), eq(TEST_COMPONENT), eq(APP_NAME), eq(TILE_LABEL), @@ -623,6 +628,7 @@ public class StatusBarManagerServiceTest { new Callback()); verify(mMockStatusBar, times(i + 1)).requestAddTile( + eq(Binder.getCallingUid()), eq(TEST_COMPONENT), eq(APP_NAME), eq(TILE_LABEL), @@ -638,6 +644,7 @@ public class StatusBarManagerServiceTest { // Only called MAX_NUM_DENIALS times verify(mMockStatusBar, times(TileRequestTracker.MAX_NUM_DENIALS)).requestAddTile( + anyInt(), any(), any(), any(), @@ -658,6 +665,7 @@ public class StatusBarManagerServiceTest { new Callback()); verify(mMockStatusBar, times(i + 1)).requestAddTile( + eq(Binder.getCallingUid()), eq(TEST_COMPONENT), eq(APP_NAME), eq(TILE_LABEL), diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 020afdbce987..8d7b5cb984b4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -141,6 +141,7 @@ import com.android.server.UiServiceTestCase; import com.android.server.notification.PermissionHelper.PackagePermission; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.protobuf.InvalidProtocolBufferException; import org.json.JSONArray; @@ -563,7 +564,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2.getId(), false)); List<NotificationChannelGroup> actualGroups = mHelper.getNotificationChannelGroups( - PKG_N_MR1, UID_N_MR1, false, true, false).getList(); + PKG_N_MR1, UID_N_MR1, false, true, false, true, null).getList(); boolean foundNcg = false; for (NotificationChannelGroup actual : actualGroups) { if (ncg.getId().equals(actual.getId())) { @@ -647,7 +648,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3.getId(), false)); List<NotificationChannelGroup> actualGroups = mHelper.getNotificationChannelGroups( - PKG_N_MR1, UID_N_MR1, false, true, false).getList(); + PKG_N_MR1, UID_N_MR1, false, true, false, true, null).getList(); boolean foundNcg = false; for (NotificationChannelGroup actual : actualGroups) { if (ncg.getId().equals(actual.getId())) { @@ -2620,6 +2621,16 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testOnlyHasDefaultChannel() throws Exception { + assertTrue(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1)); + assertFalse(mHelper.onlyHasDefaultChannel(PKG_O, UID_O)); + + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false, + UID_N_MR1, false); + assertFalse(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1)); + } + + @Test public void testCreateDeletedChannel() throws Exception { long[] vibration = new long[]{100, 67, 145, 156}; NotificationChannel channel = @@ -2644,16 +2655,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testOnlyHasDefaultChannel() throws Exception { - assertTrue(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1)); - assertFalse(mHelper.onlyHasDefaultChannel(PKG_O, UID_O)); - - mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, getChannel(), true, false, - UID_N_MR1, false); - assertFalse(mHelper.onlyHasDefaultChannel(PKG_N_MR1, UID_N_MR1)); - } - - @Test public void testCreateChannel_defaultChannelId() throws Exception { try { mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, new NotificationChannel( @@ -2884,7 +2885,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { UID_N_MR1}); assertEquals(0, mHelper.getNotificationChannelGroups( - PKG_N_MR1, UID_N_MR1, true, true, false).getList().size()); + PKG_N_MR1, UID_N_MR1, true, true, false, true, null).getList().size()); } @Test @@ -3022,7 +3023,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { UID_N_MR1, false); List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups( - PKG_N_MR1, UID_N_MR1, true, true, false).getList(); + PKG_N_MR1, UID_N_MR1, true, true, false, true, null).getList(); assertEquals(3, actual.size()); for (NotificationChannelGroup group : actual) { if (group.getId() == null) { @@ -3056,14 +3057,15 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel1.setGroup(ncg.getId()); mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false, UID_N_MR1, false); - mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1, true, true, false).getList(); + mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1, true, true, false, true, null) + .getList(); channel1.setImportance(IMPORTANCE_LOW); mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, UID_N_MR1, false); List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups( - PKG_N_MR1, UID_N_MR1, true, true, false).getList(); + PKG_N_MR1, UID_N_MR1, true, true, false, true, null).getList(); assertEquals(2, actual.size()); for (NotificationChannelGroup group : actual) { @@ -3089,7 +3091,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { UID_N_MR1, false); List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups( - PKG_N_MR1, UID_N_MR1, false, false, true).getList(); + PKG_N_MR1, UID_N_MR1, false, false, true, true, null).getList(); assertEquals(2, actual.size()); for (NotificationChannelGroup group : actual) { @@ -5786,6 +5788,62 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertFalse(isUserSet); } + @Test + public void testGetNotificationChannelGroups_withChannelFilter_includeBlocked() { + NotificationChannel channel = + new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, + UID_N_MR1, false); + // modifying same object, don't need to call updateNotificationChannel + channel.setImportance(IMPORTANCE_NONE); + + NotificationChannel channel2 = + new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true, false, + UID_N_MR1, false); + + NotificationChannel channel3 = + new NotificationChannel("id3", "name3", NotificationManager.IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, true, false, + UID_N_MR1, false); + + Set<String> filter = ImmutableSet.of("id3"); + + NotificationChannelGroup actual = mHelper.getNotificationChannelGroups( + PKG_N_MR1, UID_N_MR1, false, true, false, true, filter).getList().get(0); + assertEquals(2, actual.getChannels().size()); + assertEquals(1, actual.getChannels().stream().filter(c -> c.getId().equals("id3")).count()); + assertEquals(1, actual.getChannels().stream().filter(c -> c.getId().equals("id2")).count()); + } + + @Test + public void testGetNotificationChannelGroups_withChannelFilter_doNotIncludeBlocked() { + NotificationChannel channel = + new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, + UID_N_MR1, false); + // modifying same object, don't need to call updateNotificationChannel + channel.setImportance(IMPORTANCE_NONE); + + NotificationChannel channel2 = + new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, true, false, + UID_N_MR1, false); + + NotificationChannel channel3 = + new NotificationChannel("id3", "name3", NotificationManager.IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, true, false, + UID_N_MR1, false); + + Set<String> filter = ImmutableSet.of("id3"); + + NotificationChannelGroup actual = mHelper.getNotificationChannelGroups( + PKG_N_MR1, UID_N_MR1, false, true, false, false, filter).getList().get(0); + assertEquals(1, actual.getChannels().size()); + assertEquals(1, actual.getChannels().stream().filter(c -> c.getId().equals("id3")).count()); + assertEquals(0, actual.getChannels().stream().filter(c -> c.getId().equals("id2")).count()); + } + private static NotificationChannel cloneChannel(NotificationChannel original) { Parcel parcel = Parcel.obtain(); try { diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java index 61c4d06131e1..8db09f9e3a16 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java @@ -46,6 +46,7 @@ import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER; import static java.util.Collections.unmodifiableMap; import android.content.Context; +import android.os.Looper; import android.os.SystemClock; import android.util.ArrayMap; import android.view.InputDevice; @@ -98,6 +99,10 @@ class ShortcutKeyTestBase { * settings values. */ protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + doReturn(mSettingsProviderRule.mockContentResolver(mContext)) .when(mContext).getContentResolver(); mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate); diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 6e2c4bd7e570..ef28ffa7da8f 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -70,6 +70,7 @@ import android.hardware.display.DisplayManagerInternal; import android.hardware.input.InputManager; import android.media.AudioManagerInternal; import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.PowerManager; import android.os.PowerManagerInternal; @@ -77,7 +78,6 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.Vibrator; import android.os.VibratorInfo; -import android.os.test.TestLooper; import android.service.dreams.DreamManagerInternal; import android.telecom.TelecomManager; import android.util.FeatureFlagUtils; @@ -160,8 +160,8 @@ class TestPhoneWindowManager { @Mock private KeyguardServiceDelegate mKeyguardServiceDelegate; private StaticMockitoSession mMockitoSession; + private HandlerThread mHandlerThread; private Handler mHandler; - private TestLooper mTestLooper; private class TestInjector extends PhoneWindowManager.Injector { TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) { @@ -184,11 +184,12 @@ class TestPhoneWindowManager { TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) { MockitoAnnotations.initMocks(this); - mTestLooper = new TestLooper(); - mHandler = new Handler(mTestLooper.getLooper()); + mHandlerThread = new HandlerThread("fake window manager"); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); mContext = mockingDetails(context).isSpy() ? context : spy(context); - mHandler.post(() -> setUp(supportSettingsUpdate)); - mTestLooper.dispatchAll(); + mHandler.runWithScissors(() -> setUp(supportSettingsUpdate), 0 /* timeout */); + waitForIdle(); } private void setUp(boolean supportSettingsUpdate) { @@ -300,6 +301,7 @@ class TestPhoneWindowManager { } void tearDown() { + mHandlerThread.quitSafely(); LocalServices.removeServiceForTest(InputMethodManagerInternal.class); Mockito.reset(mPhoneWindowManager); mMockitoSession.finishMocking(); @@ -326,7 +328,7 @@ class TestPhoneWindowManager { } void waitForIdle() { - mTestLooper.dispatchAll(); + mHandler.runWithScissors(() -> { }, 0 /* timeout */); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index f65cb93258bc..40ac7b1ccdca 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -2855,14 +2855,14 @@ public class ActivityRecordTests extends WindowTestsBase { .setTask(sourceRecord.getTask()).build(); secondRecord.showStartingWindow(null /* prev */, true /* newTask */, false, true /* startActivity */, sourceRecord); - assertTrue(secondRecord.mAllowIconSplashScreen); + assertFalse(secondRecord.mSplashScreenStyleSolidColor); secondRecord.onStartingWindowDrawn(); final ActivityRecord finalRecord = new ActivityBuilder(mAtm) .setTask(sourceRecord.getTask()).build(); finalRecord.showStartingWindow(null /* prev */, true /* newTask */, false, true /* startActivity */, secondRecord); - assertFalse(finalRecord.mAllowIconSplashScreen); + assertTrue(finalRecord.mSplashScreenStyleSolidColor); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 25619b9ec5e8..4c25a4bf1455 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -91,6 +91,7 @@ import java.util.List; import java.util.Random; import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; /** * Build/Install/Run: @@ -563,6 +564,38 @@ public class RecentTasksTest extends WindowTestsBase { } @Test + public void testTasksWithCorrectOrderOfLastActiveTime() { + mRecentTasks.setOnlyTestVisibleRange(); + mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_0_ID); + + // Setup some tasks for the user + mTaskPersister.mUserTaskIdsOverride = new SparseBooleanArray(); + mTaskPersister.mUserTaskIdsOverride.put(1, true); + mTaskPersister.mUserTaskIdsOverride.put(2, true); + mTaskPersister.mUserTaskIdsOverride.put(3, true); + mTaskPersister.mUserTasksOverride = new ArrayList<>(); + mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask1").build()); + mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask2").build()); + mTaskPersister.mUserTasksOverride.add(createTaskBuilder(".UserTask3").build()); + + // Assert no user tasks are initially loaded + assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).hasLength(0); + + // Load tasks + mRecentTasks.loadUserRecentsLocked(TEST_USER_0_ID); + assertThat(mRecentTasks.usersWithRecentsLoadedLocked()).asList().contains(TEST_USER_0_ID); + + // Sort the time descendingly so the order should be in-sync with task recency (most + // recent to least recent) + List<Task> tasksSortedByTime = mRecentTasks.getRawTasks().stream() + .sorted((o1, o2) -> Long.compare(o2.lastActiveTime, o1.lastActiveTime)) + .collect(Collectors.toList()); + + assertTrue("Task order is not in sync with its recency", + mRecentTasks.getRawTasks().equals(tasksSortedByTime)); + } + + @Test public void testOrderedIteration() { mRecentTasks.setOnlyTestVisibleRange(); Task task1 = createTaskBuilder(".Task1").build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index bfa279d5c3d5..2bf13857e537 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -212,7 +212,30 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.dispatchPendingEvents(); assertTaskFragmentParentInfoChangedTransaction(mTask); - assertTaskFragmentAppearedTransaction(); + assertTaskFragmentAppearedTransaction(false /* hasSurfaceControl */); + } + + @Test + public void testOnTaskFragmentAppeared_systemOrganizer() { + mController.unregisterOrganizer(mIOrganizer); + mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); + + // No-op when the TaskFragment is not attached. + mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + verify(mOrganizer, never()).onTransactionReady(any()); + + // Send callback when the TaskFragment is attached. + setupMockParent(mTaskFragment, mTask); + + mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment); + mController.dispatchPendingEvents(); + + assertTaskFragmentParentInfoChangedTransaction(mTask); + + // System organizer should receive the SurfaceControl + assertTaskFragmentAppearedTransaction(true /* hasSurfaceControl */); } @Test @@ -1664,7 +1687,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { } /** Asserts that there will be a transaction for TaskFragment appeared. */ - private void assertTaskFragmentAppearedTransaction() { + private void assertTaskFragmentAppearedTransaction(boolean hasSurfaceControl) { verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture()); final TaskFragmentTransaction transaction = mTransactionCaptor.getValue(); final List<TaskFragmentTransaction.Change> changes = transaction.getChanges(); @@ -1675,6 +1698,11 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { assertEquals(TYPE_TASK_FRAGMENT_APPEARED, change.getType()); assertEquals(mTaskFragmentInfo, change.getTaskFragmentInfo()); assertEquals(mFragmentToken, change.getTaskFragmentToken()); + if (hasSurfaceControl) { + assertNotNull(change.getTaskFragmentSurfaceControl()); + } else { + assertNull(change.getTaskFragmentSurfaceControl()); + } } /** Asserts that there will be a transaction for TaskFragment info changed. */ diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 6a9bb6c85c70..5205bb0038c0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -27,8 +27,12 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -124,6 +128,17 @@ public class TaskFragmentTest extends WindowTestsBase { } @Test + public void testUpdateOrganizedTaskFragmentSurface_noSurfaceUpdateWhenOrganizedBySystem() { + clearInvocations(mTransaction); + mTaskFragment.mIsSurfaceManagedBySystemOrganizer = true; + + mTaskFragment.updateOrganizedTaskFragmentSurface(); + + verify(mTransaction, never()).setPosition(eq(mLeash), anyFloat(), anyFloat()); + verify(mTransaction, never()).setWindowCrop(eq(mLeash), anyInt(), anyInt()); + } + + @Test public void testShouldStartChangeTransition_relativePositionChange() { final Task task = createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 98cc1dab1601..138e575a6872 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -2403,10 +2403,6 @@ public class VoiceInteractionManagerService extends SystemService { } @Override - public void onHandleUserStop(Intent intent, int userHandle) { - } - - @Override public void onPackageModified(@NonNull String pkgName) { // If the package modified is not in the current user, then don't bother making // any changes as we are going to do any initialization needed when we switch users. diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 29a952a5d6d1..250c3a54c928 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -35,6 +35,8 @@ import android.telephony.TelephonyManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.flags.FeatureFlags; +import com.android.internal.telephony.flags.FeatureFlagsImpl; import java.util.HashMap; import java.util.HashSet; @@ -46,7 +48,8 @@ public final class TelephonyPermissions { private static final String LOG_TAG = "TelephonyPermissions"; private static final boolean DBG = false; - + /** Feature flags */ + private static final FeatureFlags sFeatureFlag = new FeatureFlagsImpl(); /** * Whether to disable the new device identifier access restrictions. */ @@ -854,7 +857,8 @@ public final class TelephonyPermissions { public static boolean checkSubscriptionAssociatedWithUser(@NonNull Context context, int subId, @NonNull UserHandle callerUserHandle, @NonNull String destAddr) { // Skip subscription-user association check for emergency numbers - TelephonyManager tm = context.getSystemService(TelephonyManager.class); + TelephonyManager tm = (TelephonyManager) context.getSystemService( + Context.TELEPHONY_SERVICE); final long token = Binder.clearCallingIdentity(); try { if (tm != null && tm.isEmergencyNumber(destAddr)) { @@ -876,16 +880,19 @@ public final class TelephonyPermissions { * @param context Context * @param subId subscription ID * @param callerUserHandle caller user handle - * @return false if user is not associated with the subscription. + * @return false if user is not associated with the subscription, or no record found of this + * subscription. */ public static boolean checkSubscriptionAssociatedWithUser(@NonNull Context context, int subId, @NonNull UserHandle callerUserHandle) { - if (!SubscriptionManager.isValidSubscriptionId(subId)) { - // No subscription on device, return true. + if (!sFeatureFlag.rejectBadSubIdInteraction() + && !SubscriptionManager.isValidSubscriptionId(subId)) { + // Return true for invalid sub Id. return true; } - SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class); + SubscriptionManager subManager = (SubscriptionManager) context.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE); final long token = Binder.clearCallingIdentity(); try { if ((subManager != null) && @@ -894,8 +901,11 @@ public final class TelephonyPermissions { Log.e(LOG_TAG, "User[User ID:" + callerUserHandle.getIdentifier() + "] is not associated with Subscription ID:" + subId); return false; - } + } catch (IllegalArgumentException e) { + // Found no record of this sub Id. + Log.e(LOG_TAG, "Subscription[Subscription ID:" + subId + "] has no records on device"); + return !sFeatureFlag.rejectBadSubIdInteraction(); } finally { Binder.restoreCallingIdentity(token); } diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java index 9a8c9655375d..aed8fb8c4503 100644 --- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java +++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java @@ -274,6 +274,10 @@ public final class TelephonyUtils { SubscriptionManager subscriptionManager = context.getSystemService( SubscriptionManager.class); + if (!subscriptionManager.isActiveSubscriptionId(subId)) { + Log.e(LOG_TAG, "Tried to send message with an inactive subscription " + subId); + return; + } UserHandle associatedUserHandle = subscriptionManager.getSubscriptionUserHandle(subId); UserManager um = context.getSystemService(UserManager.class); @@ -319,4 +323,4 @@ public final class TelephonyUtils { return false; } -}
\ No newline at end of file +} diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 36a8fc1c43a0..a5e0638fec95 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -18,6 +18,7 @@ package android.telephony; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -52,6 +53,7 @@ import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.feature.RcsFeature; import com.android.internal.telephony.ICarrierConfigLoader; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; import java.util.List; @@ -9436,22 +9438,9 @@ public class CarrierConfigManager { * </carrier_config> * }</pre> * <p> - * If this carrier config is not present, the device overlay config - * {@code config_satellite_services_supported_by_providers} will be used. If the carrier config - * is present, the supported services associated with the PLMNs listed in the carrier config - * will override that of the device overlay config. The supported satellite services will be - * identified as follows: - * <ul> - * <li>For each PLMN that exists only in the carrier provided satellite services, use the - * carrier provided services as the supported services.</li> - * <li>For each PLMN that is present only in the device provided satellite services, use the - * device provided services as the supported services.</li> - * <li>For each PLMN that is present in both the carrier provided and device provided satellite - * services, use the carrier provided services as the supported services.</li> - * </ul> - * <p> * This config is empty by default. */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle"; @@ -9462,9 +9451,8 @@ public class CarrierConfigManager { * satellite provider and the carrier before enabling this flag. * * The default value is false. - * - * @hide */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool"; @@ -10430,8 +10418,12 @@ public class CarrierConfigManager { sDefaults.putAll(Iwlan.getDefaults()); sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, new String[0]); sDefaults.putBoolean(KEY_FORMAT_INCOMING_NUMBER_TO_NATIONAL_FOR_JP_BOOL, false); - sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY, - new int[] {4 /* BUSY */}); + if (Flags.doNotOverridePreciseLabel()) { + sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY, new int[]{}); + } else { + sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY, + new int[]{4 /* BUSY */}); + } sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false); sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 5000); sDefaults.putStringArray(KEY_MMI_TWO_DIGIT_NUMBER_PATTERN_STRING_ARRAY, new String[0]); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 29149b95e471..fa5fd875a024 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -4348,7 +4348,7 @@ public class SubscriptionManager { * {code true} if there are no subscriptions on device * else {@code false} if subscription is not associated with user. * - * @throws IllegalArgumentException if subscription is invalid. + * @throws IllegalArgumentException if subscription doesn't exist. * @throws SecurityException if the caller doesn't have permissions required. * * @hide diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index a4527f12a199..8dc2de8178c4 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -85,29 +85,11 @@ public final class SatelliteManager { @Nullable private final Context mContext; /** - * Create a new SatelliteManager object pinned to the given subscription ID. - * This is needed only to handle carrier specific satellite features. - * For eg: requestSatelliteAttachEnabledForCarrier and - * requestIsSatelliteAttachEnabledForCarrier - * - * @return a SatelliteManager that uses the given subId for all satellite activities. - * @throws IllegalArgumentException if the subscription is invalid. - * @hide - */ - public SatelliteManager createForSubscriptionId(int subId) { - if (!SubscriptionManager.isValidSubscriptionId(subId)) { - throw new IllegalArgumentException("Invalid subscription ID"); - } - return new SatelliteManager(mContext, subId); - } - - /** * Create an instance of the SatelliteManager. * * @param context The context the SatelliteManager belongs to. * @hide */ - public SatelliteManager(@Nullable Context context) { this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); } @@ -846,8 +828,8 @@ public final class SatelliteManager { /** * Satellite communication restricted by geolocation. This can be * triggered based upon geofence input provided by carrier to enable or disable satellite. - * @hide */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; /** @hide */ @@ -1643,26 +1625,28 @@ public final class SatelliteManager { * {@code true}.</li> * </ul> * + * @param subId The subscription ID of the carrier. * @param enableSatellite {@code true} to enable the satellite and {@code false} to disable. * @param executor The executor on which the error code listener will be called. * @param resultListener Listener for the {@link SatelliteError} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. - * @hide + * @throws IllegalArgumentException if the subscription is invalid. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - public void requestSatelliteAttachEnabledForCarrier(boolean enableSatellite, + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public void requestSatelliteAttachEnabledForCarrier(int subId, boolean enableSatellite, @NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { Objects.requireNonNull(executor); Objects.requireNonNull(resultListener); if (enableSatellite) { - removeSatelliteAttachRestrictionForCarrier( + removeSatelliteAttachRestrictionForCarrier(subId, SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener); } else { - addSatelliteAttachRestrictionForCarrier( + addSatelliteAttachRestrictionForCarrier(subId, SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, executor, resultListener); } } @@ -1671,6 +1655,7 @@ public final class SatelliteManager { * Request to get whether the carrier supported satellite plmn scan and attach by modem is * enabled by user. * + * @param subId The subscription ID of the carrier. * @param executor The executor on which the callback will be called. * @param callback The callback object to which the result will be delivered. * If the request is successful, {@link OutcomeReceiver#onResult(Object)} @@ -1681,16 +1666,17 @@ public final class SatelliteManager { * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. - * @hide + * @throws IllegalArgumentException if the subscription is invalid. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - public void requestIsSatelliteAttachEnabledForCarrier( + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public void requestIsSatelliteAttachEnabledForCarrier(int subId, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); Objects.requireNonNull(callback); - Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier(); + Set<Integer> restrictionReason = getSatelliteAttachRestrictionReasonsForCarrier(subId); executor.execute(() -> callback.onResult( !restrictionReason.contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER))); } @@ -1699,19 +1685,25 @@ public final class SatelliteManager { * Add a restriction reason for disallowing carrier supported satellite plmn scan and attach * by modem. * + * @param subId The subscription ID of the carrier. * @param reason Reason for disallowing satellite communication. * @param executor The executor on which the error code listener will be called. * @param resultListener Listener for the {@link SatelliteError} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. - * @hide + * @throws IllegalArgumentException if the subscription is invalid. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - public void addSatelliteAttachRestrictionForCarrier( + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public void addSatelliteAttachRestrictionForCarrier(int subId, @SatelliteCommunicationRestrictionReason int reason, @NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + throw new IllegalArgumentException("Invalid subscription ID"); + } + try { ITelephony telephony = getITelephony(); if (telephony != null) { @@ -1722,7 +1714,7 @@ public final class SatelliteManager { () -> resultListener.accept(result))); } }; - telephony.addSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback); + telephony.addSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback); } else { throw new IllegalStateException("telephony service is null."); } @@ -1736,19 +1728,25 @@ public final class SatelliteManager { * Remove a restriction reason for disallowing carrier supported satellite plmn scan and attach * by modem. * + * @param subId The subscription ID of the carrier. * @param reason Reason for disallowing satellite communication. * @param executor The executor on which the error code listener will be called. * @param resultListener Listener for the {@link SatelliteError} result of the operation. * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. - * @hide + * @throws IllegalArgumentException if the subscription is invalid. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - public void removeSatelliteAttachRestrictionForCarrier( + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public void removeSatelliteAttachRestrictionForCarrier(int subId, @SatelliteCommunicationRestrictionReason int reason, @NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + throw new IllegalArgumentException("Invalid subscription ID"); + } + try { ITelephony telephony = getITelephony(); if (telephony != null) { @@ -1759,7 +1757,7 @@ public final class SatelliteManager { () -> resultListener.accept(result))); } }; - telephony.removeSatelliteAttachRestrictionForCarrier(mSubId, reason, errorCallback); + telephony.removeSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback); } else { throw new IllegalStateException("telephony service is null."); } @@ -1773,34 +1771,40 @@ public final class SatelliteManager { * Get reasons for disallowing satellite attach, as requested by * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)} * + * @param subId The subscription ID of the carrier. * @return Set of reasons for disallowing satellite communication. * * @throws SecurityException if the caller doesn't have required permission. * @throws IllegalStateException if the Telephony process is not currently available. - * @hide + * @throws IllegalArgumentException if the subscription is invalid. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @SatelliteCommunicationRestrictionReason - public @NonNull Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier() { + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @NonNull public Set<Integer> getSatelliteAttachRestrictionReasonsForCarrier(int subId) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + throw new IllegalArgumentException("Invalid subscription ID"); + } + try { ITelephony telephony = getITelephony(); if (telephony != null) { int[] receivedArray = - telephony.getSatelliteAttachRestrictionReasonsForCarrier(mSubId); + telephony.getSatelliteAttachRestrictionReasonsForCarrier(subId); if (receivedArray.length == 0) { - logd("received set is empty, create empty set"); + logd("receivedArray is empty, create empty set"); return new HashSet<>(); } else { return Arrays.stream(receivedArray).boxed().collect(Collectors.toSet()); } } else { - throw new IllegalStateException("telephony service is null."); + throw new IllegalStateException("Telephony service is null."); } } catch (RemoteException ex) { loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex); ex.rethrowFromSystemServer(); } - return null; + return new HashSet<>(); } private static ITelephony getITelephony() { diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl index 02661de34fb2..0fcd0d622d36 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -392,7 +392,11 @@ oneway interface ISatellite { * * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use * this information to determine the relevant carrier. - * @param plmnList The list of roaming PLMN used for connecting to satellite networks. + * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks + * supported by user subscription. + * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite + * PLMNs that are not supported by the carrier and make sure not to + * attach to them. * @param resultCallback The callback to receive the error code result of the operation. * * Valid error codes returned: @@ -404,8 +408,8 @@ oneway interface ISatellite { * SatelliteError:RADIO_NOT_AVAILABLE * SatelliteError:REQUEST_NOT_SUPPORTED */ - void setSatellitePlmn(int simSlot, in List<String> plmnList, - in IIntegerConsumer resultCallback); + void setSatellitePlmn(int simSlot, in List<String> carrierPlmnList, + in List<String> allSatellitePlmnList, in IIntegerConsumer resultCallback); /** * Enable or disable satellite in the cellular modem associated with a carrier. diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java index 6451daf2e752..a9c09c94d57c 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -214,12 +214,12 @@ public class SatelliteImplBase extends SatelliteService { } @Override - public void setSatellitePlmn(int simSlot, List<String> plmnList, - IIntegerConsumer errorCallback) + public void setSatellitePlmn(int simSlot, List<String> carrierPlmnList, + List<String> devicePlmnList, IIntegerConsumer errorCallback) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this - .setSatellitePlmn(simSlot, plmnList, errorCallback), + () -> SatelliteImplBase.this.setSatellitePlmn( + simSlot, carrierPlmnList, devicePlmnList, errorCallback), "setSatellitePlmn"); } @@ -655,13 +655,15 @@ public class SatelliteImplBase extends SatelliteService { * SIM profile. Acquisition of satellite based system is lower priority to terrestrial * networks. UE shall make all attempts to acquire terrestrial service prior to camping on * satellite LTE service. - * This method must only take effect if {@link #setSatelliteEnabledForCarrier} is {@code true}, - * and return an error otherwise. * * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be * applied. The modem will use this information to determine the relevant carrier. * @param errorCallback The callback to receive the error code result of the operation. - * @param plmnList The list of roaming PLMN used for connecting to satellite networks. + * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks + * supported by user subscription. + * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite + * PLMNs that are not supported by the carrier and make sure not to + * attach to them. * * Valid error codes returned: * SatelliteError:NONE @@ -672,7 +674,8 @@ public class SatelliteImplBase extends SatelliteService { * SatelliteError:RADIO_NOT_AVAILABLE * SatelliteError:REQUEST_NOT_SUPPORTED */ - public void setSatellitePlmn(@NonNull int simLogicalSlotIndex, @NonNull List<String> plmnList, + public void setSatellitePlmn(@NonNull int simLogicalSlotIndex, + @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList, @NonNull IIntegerConsumer errorCallback) { // stub implementation } diff --git a/tests/BinderLeakTest/Android.bp b/tests/BinderLeakTest/Android.bp new file mode 100644 index 000000000000..78b0ede76d4e --- /dev/null +++ b/tests/BinderLeakTest/Android.bp @@ -0,0 +1,40 @@ +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"], +} + +filegroup { + name: "binder_leak_test_aidl", + srcs: ["**/*.aidl"], + path: "aidl", +} + +java_defaults { + name: "BinderTest.defaults", + srcs: [ + "**/*.java", + ":binder_leak_test_aidl", + ], + static_libs: [ + "androidx.test.ext.junit", + "androidx.test.rules", + "androidx.test.runner", + ], +} + +// Built with target_sdk_version: current +android_test { + name: "BinderLeakTest", + defaults: ["BinderTest.defaults"], +} + +// Built with target_sdk_version: 33 +android_test { + name: "BinderLeakTest_legacy", + defaults: ["BinderTest.defaults"], + manifest: "AndroidManifest_legacy.xml", +} diff --git a/tests/BinderLeakTest/AndroidManifest.xml b/tests/BinderLeakTest/AndroidManifest.xml new file mode 100644 index 000000000000..756def7ac29d --- /dev/null +++ b/tests/BinderLeakTest/AndroidManifest.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.binder"> + <application> + <service + android:name=".MyService" + android:enabled="true" + android:exported="true" + android:process=":service"> + </service> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.test.binder" + android:label="Binder leak test"> + </instrumentation> +</manifest> diff --git a/tests/BinderLeakTest/AndroidManifest_legacy.xml b/tests/BinderLeakTest/AndroidManifest_legacy.xml new file mode 100644 index 000000000000..03d1dfd1fd83 --- /dev/null +++ b/tests/BinderLeakTest/AndroidManifest_legacy.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.binder"> + <uses-sdk android:minSdkVersion="33" + android:targetSdkVersion="33" + android:maxSdkVersion="33" /> + <application> + <service + android:name=".MyService" + android:enabled="true" + android:exported="true" + android:process=":service"> + </service> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.test.binder" + android:label="Binder leak test"> + </instrumentation> +</manifest> diff --git a/tests/BinderLeakTest/aidl/com/android/test/binder/IFoo.aidl b/tests/BinderLeakTest/aidl/com/android/test/binder/IFoo.aidl new file mode 100644 index 000000000000..a721959d19b4 --- /dev/null +++ b/tests/BinderLeakTest/aidl/com/android/test/binder/IFoo.aidl @@ -0,0 +1,5 @@ +package com.android.test.binder; + +interface IFoo { + +} diff --git a/tests/BinderLeakTest/aidl/com/android/test/binder/IFooProvider.aidl b/tests/BinderLeakTest/aidl/com/android/test/binder/IFooProvider.aidl new file mode 100644 index 000000000000..b487f51f812c --- /dev/null +++ b/tests/BinderLeakTest/aidl/com/android/test/binder/IFooProvider.aidl @@ -0,0 +1,10 @@ +package com.android.test.binder; +import com.android.test.binder.IFoo; + +interface IFooProvider { + IFoo createFoo(); + + boolean isFooGarbageCollected(); + + oneway void killProcess(); +} diff --git a/tests/BinderLeakTest/java/com/android/test/binder/BinderTest.java b/tests/BinderLeakTest/java/com/android/test/binder/BinderTest.java new file mode 100644 index 000000000000..f07317f7d5f3 --- /dev/null +++ b/tests/BinderLeakTest/java/com/android/test/binder/BinderTest.java @@ -0,0 +1,153 @@ +/* + * 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.test.binder; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import android.content.Intent; +import android.os.Build; +import android.os.IBinder; +import android.os.RemoteException; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.ServiceTestRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.concurrent.TimeoutException; + +@RunWith(AndroidJUnit4.class) +public class BinderTest { + @Rule + public final ServiceTestRule serviceRule = new ServiceTestRule(); + + @Test + public void testDeathRecipientLeaksOrNot() + throws RemoteException, TimeoutException, InterruptedException { + Intent intent = new Intent(ApplicationProvider.getApplicationContext(), MyService.class); + IFooProvider provider = IFooProvider.Stub.asInterface(serviceRule.bindService(intent)); + FooHolder holder = new FooHolder(provider.createFoo()); + + // ref will get enqueued right after holder is finalized for gc. + ReferenceQueue<FooHolder> refQueue = new ReferenceQueue<>(); + PhantomReference<FooHolder> ref = new PhantomReference<>(holder, refQueue); + + DeathRecorder deathRecorder = new DeathRecorder(); + holder.registerDeathRecorder(deathRecorder); + + if (getSdkVersion() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + ///////////////////////////////////////////// + // New behavior + // + // Reference chain at this moment: + // holder --(java strong ref)--> FooHolder + // FooHolder.mProxy --(java strong ref)--> IFoo.Proxy + // IFoo.Proxy.mRemote --(java strong ref)--> BinderProxy + // BinderProxy --(binder ref)--> Foo.Stub + // In other words, the variable "holder" is the root of the reference chain. + + // By setting the variable to null, we make FooHolder, IFoo.Proxy, BinderProxy, and even + // Foo.Stub unreachable. + holder = null; + + // Ensure that the objects are garbage collected + forceGc(); + assertEquals(ref, refQueue.poll()); + assertTrue(provider.isFooGarbageCollected()); + + // The binder has died, but we don't get notified since the death recipient is GC'ed. + provider.killProcess(); + Thread.sleep(1000); // give some time for the service process to die and reaped + assertFalse(deathRecorder.deathRecorded); + } else { + ///////////////////////////////////////////// + // Legacy behavior + // + // Reference chain at this moment: + // JavaDeathRecipient --(JNI strong ref)--> FooHolder + // holder --(java strong ref)--> FooHolder + // FooHolder.mProxy --(java strong ref)--> IFoo.Proxy + // IFoo.Proxy.mRemote --(java strong ref)--> BinderProxy + // BinderProxy --(binder ref)--> Foo.Stub + // So, BOTH JavaDeathRecipient and holder are roots of the reference chain. + + // Even if we set holder to null, it doesn't make other objects unreachable; they are + // still reachable via the JNI strong ref. + holder = null; + + // Check that objects are not garbage collected + forceGc(); + assertNotEquals(ref, refQueue.poll()); + assertFalse(provider.isFooGarbageCollected()); + + // The legacy behavior is getting notified even when there's no reference + provider.killProcess(); + Thread.sleep(1000); // give some time for the service process to die and reaped + assertTrue(deathRecorder.deathRecorded); + } + } + + static class FooHolder implements IBinder.DeathRecipient { + private IFoo mProxy; + private DeathRecorder mDeathRecorder; + + FooHolder(IFoo proxy) throws RemoteException { + proxy.asBinder().linkToDeath(this, 0); + + // A strong reference from DeathRecipient(this) to the binder proxy is created here + mProxy = proxy; + } + + public void registerDeathRecorder(DeathRecorder dr) { + mDeathRecorder = dr; + } + + @Override + public void binderDied() { + if (mDeathRecorder != null) { + mDeathRecorder.deathRecorded = true; + } + } + } + + static class DeathRecorder { + public boolean deathRecorded = false; + } + + // Try calling System.gc() until an orphaned object is confirmed to be finalized + private static void forceGc() { + Object obj = new Object(); + ReferenceQueue<Object> refQueue = new ReferenceQueue<>(); + PhantomReference<Object> ref = new PhantomReference<>(obj, refQueue); + obj = null; // make it an orphan + while (refQueue.poll() != ref) { + System.gc(); + } + } + + private static int getSdkVersion() { + return ApplicationProvider.getApplicationContext().getApplicationInfo().targetSdkVersion; + } +} diff --git a/tests/BinderLeakTest/java/com/android/test/binder/MyService.java b/tests/BinderLeakTest/java/com/android/test/binder/MyService.java new file mode 100644 index 000000000000..c701253446f4 --- /dev/null +++ b/tests/BinderLeakTest/java/com/android/test/binder/MyService.java @@ -0,0 +1,63 @@ +/* + * 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.test.binder; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; + +public class MyService extends Service { + @Override + public IBinder onBind(Intent intent) { + return new IFooProvider.Stub() { + ReferenceQueue<IFoo> mRefQueue = new ReferenceQueue<>(); + PhantomReference<IFoo> mRef; + + @Override + public IFoo createFoo() throws RemoteException { + IFoo binder = new IFoo.Stub() {}; + mRef = new PhantomReference<>(binder, mRefQueue); + return binder; + } + + @Override + public boolean isFooGarbageCollected() throws RemoteException { + forceGc(); + return mRefQueue.poll() == mRef; + } + + @Override + public void killProcess() throws RemoteException { + android.os.Process.killProcess(android.os.Process.myPid()); + } + }; + } + + private static void forceGc() { + Object obj = new Object(); + ReferenceQueue<Object> refQueue = new ReferenceQueue<>(); + PhantomReference<Object> ref = new PhantomReference<>(obj, refQueue); + obj = null; // make it an orphan + while (refQueue.poll() != ref) { + System.gc(); + } + } +} diff --git a/tests/utils/testutils/TEST_MAPPING b/tests/utils/testutils/TEST_MAPPING index 6468d8cc99a6..52fd5a8779ad 100644 --- a/tests/utils/testutils/TEST_MAPPING +++ b/tests/utils/testutils/TEST_MAPPING @@ -7,9 +7,6 @@ "exclude-annotation": "androidx.test.filters.LargeTest" }, { - "exclude-annotation": "android.platform.test.annotations.FlakyTest" - }, - { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java index f7719a6e55b2..d176b5e97b0b 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java +++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java @@ -38,6 +38,10 @@ public class HostTestUtils { private static final boolean SKIP_METHOD_LOG = "1".equals(System.getenv( "HOSTTEST_SKIP_METHOD_LOG")); + /** If true, we won't print class load log. */ + private static final boolean SKIP_CLASS_LOG = "1".equals(System.getenv( + "HOSTTEST_SKIP_CLASS_LOG")); + /** If true, we won't perform non-stub method direct call check. */ private static final boolean SKIP_NON_STUB_METHOD_CHECK = "1".equals(System.getenv( "HOSTTEST_SKIP_NON_STUB_METHOD_CHECK")); @@ -57,17 +61,35 @@ public class HostTestUtils { } /** - * Called from methods with FilterPolicy.Log. + * Trampoline method for method-call-hook. + */ + public static void callMethodCallHook( + Class<?> methodClass, + String methodName, + String methodDescriptor, + String callbackMethod + ) { + callStaticMethodByName(callbackMethod, "method call hook", methodClass, + methodName, methodDescriptor); + } + + /** + * I can be used as + * {@code --default-method-call-hook + * com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall}. + * + * It logs every single methods called. */ public static void logMethodCall( - String methodClass, + Class<?> methodClass, String methodName, String methodDescriptor ) { if (SKIP_METHOD_LOG) { return; } - logPrintStream.println("# " + methodClass + "." + methodName + methodDescriptor); + logPrintStream.println("# method called: " + methodClass.getCanonicalName() + "." + + methodName + methodDescriptor); } private static final StackWalker sStackWalker = @@ -146,52 +168,81 @@ public class HostTestUtils { logPrintStream.println("! Class loaded: " + loadedClass.getCanonicalName() + " calling hook " + callbackMethod); + callStaticMethodByName(callbackMethod, "class load hook", loadedClass); + } + + private static void callStaticMethodByName(String classAndMethodName, + String description, Object... args) { // Forward the call to callbackMethod. - final int lastPeriod = callbackMethod.lastIndexOf("."); - final String className = callbackMethod.substring(0, lastPeriod); - final String methodName = callbackMethod.substring(lastPeriod + 1); + final int lastPeriod = classAndMethodName.lastIndexOf("."); - if (lastPeriod < 0 || className.isEmpty() || methodName.isEmpty()) { + if ((lastPeriod) < 0 || (lastPeriod == classAndMethodName.length() - 1)) { throw new HostTestException(String.format( - "Unable to find class load hook: malformed method name \"%s\"", - callbackMethod)); + "Unable to find %s: malformed method name \"%s\"", + description, + classAndMethodName)); } + final String className = classAndMethodName.substring(0, lastPeriod); + final String methodName = classAndMethodName.substring(lastPeriod + 1); + Class<?> clazz = null; try { clazz = Class.forName(className); } catch (Exception e) { throw new HostTestException(String.format( - "Unable to find class load hook: Class %s not found", className), e); + "Unable to find %s: Class %s not found", + description, + className), e); } if (!Modifier.isPublic(clazz.getModifiers())) { throw new HostTestException(String.format( - "Unable to find class load hook: Class %s must be public", className)); + "Unable to find %s: Class %s must be public", + description, + className)); + } + + Class<?>[] argTypes = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + argTypes[i] = args[i].getClass(); } Method method = null; try { - method = clazz.getMethod(methodName, Class.class); + method = clazz.getMethod(methodName, argTypes); } catch (Exception e) { throw new HostTestException(String.format( - "Unable to find class load hook: class %s doesn't have method %s" - + " (method must take exactly one parameter of type Class, and public static)", - className, - methodName), e); + "Unable to find %s: class %s doesn't have method %s" + + " (method must take exactly one parameter of type Class," + + " and public static)", + description, className, methodName), e); } if (!(Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers()))) { throw new HostTestException(String.format( - "Unable to find class load hook: Method %s in class %s must be public static", - methodName, className)); + "Unable to find %s: Method %s in class %s must be public static", + description, methodName, className)); } try { - method.invoke(null, loadedClass); + method.invoke(null, args); } catch (Exception e) { throw new HostTestException(String.format( - "Unable to invoke class load hook %s.%s", - className, - methodName), e); + "Unable to invoke %s %s.%s", + description, className, methodName), e); + } + } + + /** + * I can be used as + * {@code --default-class-load-hook + * com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded}. + * + * It logs every loaded class. + */ + public static void logClassLoaded(Class<?> clazz) { + if (SKIP_CLASS_LOG) { + return; } + logPrintStream.println("# class loaded: " + clazz.getCanonicalName()); } } diff --git a/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt b/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt index 828d2a3e01c6..3f875272d0a8 100644 --- a/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt +++ b/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt @@ -6,8 +6,10 @@ --enable-non-stub-method-check # --no-non-stub-method-check -# --enable-method-logging - +#--default-method-call-hook +# com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall +#--default-class-load-hook +# com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded # Standard annotations. # Note, each line is a single argument, so we need newlines after each `--xxx-annotation`. diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 8db4b6961376..7531759aa106 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -17,6 +17,7 @@ package com.android.hoststubgen import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.filters.AnnotationBasedFilter +import com.android.hoststubgen.filters.DefaultHookInjectingFilter import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter import com.android.hoststubgen.filters.ConstantFilter import com.android.hoststubgen.filters.FilterPolicy @@ -156,22 +157,29 @@ class HostStubGen(val options: HostStubGenOptions) { // This is used when a member (methods, fields, nested classes) don't get any polices // from upper filters. e.g. when a method has no annotations, then this filter will apply // the class-wide policy, if any. (if not, we'll fall back to the above filter.) - val classWidePropagator = ClassWidePolicyPropagatingFilter(filter) + filter = ClassWidePolicyPropagatingFilter(filter) + + // Inject default hooks from options. + filter = DefaultHookInjectingFilter( + options.defaultClassLoadHook, + options.defaultMethodCallHook, + filter + ) // Next, Java annotation based filter. filter = AnnotationBasedFilter( - errors, - allClasses, - options.stubAnnotations, - options.keepAnnotations, - options.stubClassAnnotations, - options.keepClassAnnotations, - options.throwAnnotations, - options.removeAnnotations, - options.substituteAnnotations, - options.nativeSubstituteAnnotations, - options.classLoadHookAnnotations, - classWidePropagator + errors, + allClasses, + options.stubAnnotations, + options.keepAnnotations, + options.stubClassAnnotations, + options.keepClassAnnotations, + options.throwAnnotations, + options.removeAnnotations, + options.substituteAnnotations, + options.nativeSubstituteAnnotations, + options.classLoadHookAnnotations, + filter ) // Next, "text based" filter, which allows to override polices without touching diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index 9a54ecffc8c2..bbb7dab2c910 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -48,6 +48,9 @@ class HostStubGenOptions( var nativeSubstituteAnnotations: MutableSet<String> = mutableSetOf(), var classLoadHookAnnotations: MutableSet<String> = mutableSetOf(), + var defaultClassLoadHook: String? = null, + var defaultMethodCallHook: String? = null, + var intersectStubJars: MutableSet<String> = mutableSetOf(), var policyOverrideFile: String? = null, @@ -63,8 +66,6 @@ class HostStubGenOptions( var enablePreTrace: Boolean = false, var enablePostTrace: Boolean = false, - var enableMethodLogging: Boolean = false, - var enableNonStubMethodCallDetection: Boolean = true, ) { companion object { @@ -151,6 +152,12 @@ class HostStubGenOptions( ret.classLoadHookAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg)) + "--default-class-load-hook" -> + ret.defaultClassLoadHook = ai.nextArgRequired(arg) + + "--default-method-call-hook" -> + ret.defaultMethodCallHook = ai.nextArgRequired(arg) + "--intersect-stub-jar" -> ret.intersectStubJars += ai.nextArgRequired(arg).ensureFileExists() @@ -167,9 +174,6 @@ class HostStubGenOptions( "--enable-post-trace" -> ret.enablePostTrace = true "--no-post-trace" -> ret.enablePostTrace = false - "--enable-method-logging" -> ret.enableMethodLogging = true - "--no-method-logging" -> ret.enableMethodLogging = false - "--enable-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = true "--no-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = false @@ -290,6 +294,8 @@ class HostStubGenOptions( substituteAnnotations=$substituteAnnotations, nativeSubstituteAnnotations=$nativeSubstituteAnnotations, classLoadHookAnnotations=$classLoadHookAnnotations, + defaultClassLoadHook=$defaultClassLoadHook, + defaultMethodCallHook=$defaultMethodCallHook, intersectStubJars=$intersectStubJars, policyOverrideFile=$policyOverrideFile, defaultPolicy=$defaultPolicy, @@ -299,7 +305,6 @@ class HostStubGenOptions( enableClassChecker=$enableClassChecker, enablePreTrace=$enablePreTrace, enablePostTrace=$enablePostTrace, - enableMethodLogging=$enableMethodLogging, enableNonStubMethodCallDetection=$enableNonStubMethodCallDetection, } """.trimIndent() diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt index 9fbd6d09bfb0..f75062b3a878 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt @@ -31,3 +31,31 @@ fun normalizeTextLine(s: String): String { // Remove surrounding whitespace. return uncommented.trim() } + +/** + * Concatenate list [a] and [b] and return it. As an optimization, it returns an input + * [List] as-is if the other [List] is empty, so do not modify input [List]'s. + */ +fun <T> addLists(a: List<T>, b: List<T>): List<T> { + if (a.isEmpty()) { + return b + } + if (b.isEmpty()) { + return a + } + return a + b +} + +/** + * Add element [b] to list [a] if [b] is not null. Otherwise, just return [a]. + * (because the method may return [a] as-is, do not modify it after passing it.) + */ +fun <T> addNonNullElement(a: List<T>, b: T?): List<T> { + if (b == null) { + return a + } + if (a.isEmpty()) { + return listOf(b) + } + return a + b +}
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt index 454569d2f1c5..3f492e834637 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt @@ -19,6 +19,7 @@ import com.android.hoststubgen.ClassParseException import com.android.hoststubgen.HostStubGenErrors import com.android.hoststubgen.HostStubGenInternalException import com.android.hoststubgen.InvalidAnnotationException +import com.android.hoststubgen.addNonNullElement import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.findAnnotationValueAsString import com.android.hoststubgen.asm.findAnyAnnotation @@ -253,14 +254,14 @@ class AnnotationBasedFilter( return null } - override fun getClassLoadHook(className: String): String? { - classes.getClass(className).let { cn -> + override fun getClassLoadHooks(className: String): List<String> { + val e = classes.getClass(className).let { cn -> findAnyAnnotation(classLoadHookAnnotations, cn.visibleAnnotations, cn.invisibleAnnotations)?.let { an -> - return getAnnotationField(an, "value")?.toHumanReadableMethodName() + getAnnotationField(an, "value")?.toHumanReadableMethodName() } } - return null + return addNonNullElement(super.getClassLoadHooks(className), e) } private data class MethodKey(val name: String, val desc: String) diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt new file mode 100644 index 000000000000..d771003a955d --- /dev/null +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.hoststubgen.filters + +import com.android.hoststubgen.addLists + +class DefaultHookInjectingFilter( + defaultClassLoadHook: String?, + defaultMethodCallHook: String?, + fallback: OutputFilter +) : DelegatingFilter(fallback) { + /** + * Create a List containing a single element [e], if e != null. Otherwise, returns + * an empty list. + */ + private fun toSingleList(e: String?): List<String> { + if (e == null) { + return emptyList() + } + return listOf(e) + } + + private val defaultClassLoadHookAsList: List<String> = toSingleList(defaultClassLoadHook) + private val defaultMethodCallHookAsList: List<String> = toSingleList(defaultMethodCallHook) + + override fun getClassLoadHooks(className: String): List<String> { + return addLists(super.getClassLoadHooks(className), defaultClassLoadHookAsList) + } + + override fun getMethodCallHooks( + className: String, + methodName: String, + descriptor: String + ): List<String> { + return addLists( + super.getMethodCallHooks(className, methodName, descriptor), + defaultMethodCallHookAsList, + ) + } +}
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt index f0763c4ba097..45f61c5b2c22 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt @@ -66,7 +66,15 @@ abstract class DelegatingFilter( return fallback.getNativeSubstitutionClass(className) } - override fun getClassLoadHook(className: String): String? { - return fallback.getClassLoadHook(className) + override fun getClassLoadHooks(className: String): List<String> { + return fallback.getClassLoadHooks(className) + } + + override fun getMethodCallHooks( + className: String, + methodName: String, + descriptor: String + ): List<String> { + return fallback.getMethodCallHooks(className, methodName, descriptor) } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt index f3551d49bd36..5659a35170c1 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt @@ -16,6 +16,7 @@ package com.android.hoststubgen.filters import com.android.hoststubgen.UnknownApiException +import com.android.hoststubgen.addNonNullElement import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.toHumanReadableClassName import com.android.hoststubgen.asm.toHumanReadableMethodName @@ -127,9 +128,9 @@ class InMemoryOutputFilter( mNativeSubstitutionClasses[getClassKey(from)] = to.toHumanReadableClassName() } - override fun getClassLoadHook(className: String): String? { - return mClassLoadHooks[getClassKey(className)] - ?: super.getClassLoadHook(className) + override fun getClassLoadHooks(className: String): List<String> { + return addNonNullElement(super.getClassLoadHooks(className), + mClassLoadHooks[getClassKey(className)]) } fun setClassLoadHook(className: String, methodName: String) { diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt index 392ee4b81613..3df16ffa99b3 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt @@ -71,11 +71,22 @@ abstract class OutputFilter { } /** - * Return a "class load hook" method name for a given class. + * Return a "class load hook" class name for a given class. * * (which corresponds to @HostSideTestClassLoadHook of the standard annotations.) */ - open fun getClassLoadHook(className: String): String? { - return null + open fun getClassLoadHooks(className: String): List<String> { + return emptyList() + } + + /** + * Return the "method call hook" class name. + * + * The class has to have a function with the following signature: + * `public static void onMethodCalled(Class<?> clazz, String name, String descriptor)`. + */ + open fun getMethodCallHooks(className: String, methodName: String, descriptor: String): + List<String> { + return emptyList() } }
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt index ac068861ec8d..57b668954f98 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt @@ -45,7 +45,7 @@ class ImplGeneratingAdapter( return policy.needsInImpl } - private var classLoadHookMethod: String? = null + private var classLoadHooks: List<String> = emptyList() override fun visit( version: Int, @@ -57,22 +57,22 @@ class ImplGeneratingAdapter( ) { super.visit(version, access, name, signature, superName, interfaces) - classLoadHookMethod = filter.getClassLoadHook(currentClassName) + classLoadHooks = filter.getClassLoadHooks(currentClassName) // classLoadHookMethod is non-null, then we need to inject code to call it // in the class initializer. // If the target class already has a class initializer, then we need to inject code to it. // Otherwise, we need to create one. - classLoadHookMethod?.let { callback -> - log.d(" ClassLoadHook: $callback") + if (classLoadHooks.isNotEmpty()) { + log.d(" ClassLoadHooks: $classLoadHooks") if (!classes.hasClassInitializer(currentClassName)) { - injectClassLoadHook(callback) + injectClassLoadHook() } } } - private fun injectClassLoadHook(callback: String) { + private fun injectClassLoadHook() { writeRawMembers { // Create a class initializer to call onClassLoaded(). // Each class can only have at most one class initializer, but the base class @@ -87,7 +87,7 @@ class ImplGeneratingAdapter( // Method prologue mv.visitCode() - writeClassLoadHookCall(mv) + writeClassLoadHookCalls(mv) mv.visitInsn(Opcodes.RETURN) // Method epilogue @@ -97,21 +97,23 @@ class ImplGeneratingAdapter( } } - private fun writeClassLoadHookCall(mv: MethodVisitor) { - // First argument: the class type. - mv.visitLdcInsn(Type.getType("L" + currentClassName + ";")) - - // Second argument: method name - mv.visitLdcInsn(classLoadHookMethod) - - // Call HostTestUtils.onClassLoaded(). - mv.visitMethodInsn( - Opcodes.INVOKESTATIC, - HostTestUtils.CLASS_INTERNAL_NAME, - "onClassLoaded", - "(Ljava/lang/Class;Ljava/lang/String;)V", - false - ) + private fun writeClassLoadHookCalls(mv: MethodVisitor) { + classLoadHooks.forEach { classLoadHook -> + // First argument: the class type. + mv.visitLdcInsn(Type.getType("L" + currentClassName + ";")) + + // Second argument: method name + mv.visitLdcInsn(classLoadHook) + + // Call HostTestUtils.onClassLoaded(). + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, + HostTestUtils.CLASS_INTERNAL_NAME, + "onClassLoaded", + "(Ljava/lang/Class;Ljava/lang/String;)V", + false + ) + } } override fun updateAccessFlags( @@ -138,20 +140,22 @@ class ImplGeneratingAdapter( var innerVisitor = superVisitor // If method logging is enabled, inject call to the logging method. - if (options.enableMethodLogging) { - innerVisitor = LogInjectingMethodAdapter( - access, - name, - descriptor, - signature, - exceptions, - innerVisitor, - ) + val methodCallHooks = filter.getMethodCallHooks(currentClassName, name, descriptor) + if (methodCallHooks.isNotEmpty()) { + innerVisitor = MethodCallHookInjectingAdapter( + access, + name, + descriptor, + signature, + exceptions, + innerVisitor, + methodCallHooks, + ) } // If this class already has a class initializer and a class load hook is needed, then // we inject code. - if (classLoadHookMethod != null && + if (classLoadHooks.isNotEmpty() && name == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC) { innerVisitor = ClassLoadHookInjectingMethodAdapter( @@ -283,29 +287,37 @@ class ImplGeneratingAdapter( } /** - * A method adapter that injects a call to HostTestUtils.logMethodCall() to every method. + * Inject calls to the method call hooks. * * Note, when the target method is a constructor, it may contain calls to `super(...)` or * `this(...)`. The logging code will be injected *before* such calls. */ - private inner class LogInjectingMethodAdapter( + private inner class MethodCallHookInjectingAdapter( access: Int, val name: String, val descriptor: String, signature: String?, exceptions: Array<String>?, - next: MethodVisitor? + next: MethodVisitor?, + val hooks: List<String>, ) : MethodVisitor(OPCODE_VERSION, next) { override fun visitCode() { super.visitCode() - visitLdcInsn(currentClassName) - visitLdcInsn(name) - visitLdcInsn(descriptor) - visitMethodInsn(Opcodes.INVOKESTATIC, + + hooks.forEach { hook -> + mv.visitLdcInsn(Type.getType("L" + currentClassName + ";")) + visitLdcInsn(name) + visitLdcInsn(descriptor) + visitLdcInsn(hook) + + visitMethodInsn( + Opcodes.INVOKESTATIC, HostTestUtils.CLASS_INTERNAL_NAME, - "logMethodCall", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", - false) + "callMethodCallHook", + "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + false + ) + } } } @@ -323,7 +335,7 @@ class ImplGeneratingAdapter( override fun visitCode() { super.visitCode() - writeClassLoadHookCall(this) + writeClassLoadHookCalls(this) } } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp index 8c76a612020f..05d6a43cdb0f 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp @@ -53,6 +53,46 @@ java_genrule_host { ], } +// Same as "hoststubgen-test-tiny-framework-host", but with more options, to test more hoststubgen +// features. +java_genrule_host { + name: "hoststubgen-test-tiny-framework-host-ext", + defaults: ["hoststubgen-command-defaults"], + cmd: hoststubgen_common_options + + "--in-jar $(location :hoststubgen-test-tiny-framework) " + + "--policy-override-file $(location policy-override-tiny-framework.txt) " + + + // More options. + "--default-method-call-hook com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall " + + "--default-class-load-hook com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded ", + srcs: [ + ":hoststubgen-test-tiny-framework", + "policy-override-tiny-framework.txt", + ], +} + +java_genrule_host { + name: "hoststubgen-test-tiny-framework-host-ext-stub", + cmd: "cp $(in) $(out)", + srcs: [ + ":hoststubgen-test-tiny-framework-host-ext{host_stub.jar}", + ], + out: [ + "host_stub.jar", + ], +} + +java_genrule_host { + name: "hoststubgen-test-tiny-framework-host-ext-impl", + cmd: "cp $(in) $(out)", + srcs: [ + ":hoststubgen-test-tiny-framework-host-ext{host_impl.jar}", + ], + out: [ + "host_impl.jar", + ], +} + // Compile the test jar, using 2 rules. // 1. Build the test against the stub. java_library_host { @@ -123,6 +163,30 @@ java_genrule_host { visibility: ["//visibility:private"], } +java_genrule_host { + name: "hoststubgen-test-tiny-framework-host-ext-stub-dump", + defaults: ["hoststubgen-jar-dump-defaults"], + srcs: [ + ":hoststubgen-test-tiny-framework-host-ext-stub", + ], + out: [ + "12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt", + ], + visibility: ["//visibility:private"], +} + +java_genrule_host { + name: "hoststubgen-test-tiny-framework-host-ext-impl-dump", + defaults: ["hoststubgen-jar-dump-defaults"], + srcs: [ + ":hoststubgen-test-tiny-framework-host-ext-impl", + ], + out: [ + "13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt", + ], + visibility: ["//visibility:private"], +} + // Run it with `atest`. Compare the dump of the jar files to the golden output. python_test_host { name: "tiny-framework-dump-test", @@ -136,6 +200,8 @@ python_test_host { "hoststubgen-test-tiny-framework-host-stub-dump", "hoststubgen-test-tiny-framework-host-impl-dump", "hoststubgen-test-tiny-framework-orig-dump", + "hoststubgen-test-tiny-framework-host-ext-stub-dump", + "hoststubgen-test-tiny-framework-host-ext-impl-dump", ], test_suites: ["general-tests"], } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh index 4d588694b8da..639fb16c4251 100755 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh @@ -112,7 +112,7 @@ done if (( $three_way )) ; then echo "# Running 3-way diff with meld..." - run meld ${files[*]} & + run meld ${files[0]} ${files[1]} ${files[2]} & fi if (( $two_way )) ; then diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt index 1aa485963f97..0c1d88ad0ff9 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt @@ -13,11 +13,11 @@ public interface android.hosttest.annotation.HostSideTestClassLoadHook extends j } SourceFile: "HostSideTestClassLoadHook.java" RuntimeVisibleAnnotations: - 0: #x(#x=[e#x.#x]) + x: #x(#x=[e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE] ) - 1: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -33,11 +33,11 @@ public interface android.hosttest.annotation.HostSideTestKeep extends java.lang. } SourceFile: "HostSideTestKeep.java" RuntimeVisibleAnnotations: - 0: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) + x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] ) - 1: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -56,11 +56,11 @@ public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass } SourceFile: "HostSideTestNativeSubstitutionClass.java" RuntimeVisibleAnnotations: - 0: #x(#x=[e#x.#x]) + x: #x(#x=[e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE] ) - 1: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -76,11 +76,11 @@ public interface android.hosttest.annotation.HostSideTestRemove extends java.lan } SourceFile: "HostSideTestRemove.java" RuntimeVisibleAnnotations: - 0: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) + x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] ) - 1: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -96,11 +96,11 @@ public interface android.hosttest.annotation.HostSideTestStub extends java.lang. } SourceFile: "HostSideTestStub.java" RuntimeVisibleAnnotations: - 0: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) + x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] ) - 1: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -119,11 +119,11 @@ public interface android.hosttest.annotation.HostSideTestSubstitute extends java } SourceFile: "HostSideTestSubstitute.java" RuntimeVisibleAnnotations: - 0: #x(#x=[e#x.#x]) + x: #x(#x=[e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.METHOD] ) - 1: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -139,11 +139,11 @@ public interface android.hosttest.annotation.HostSideTestThrow extends java.lang } SourceFile: "HostSideTestThrow.java" RuntimeVisibleAnnotations: - 0: #x(#x=[e#x.#x,e#x.#x]) + x: #x(#x=[e#x.#x,e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] ) - 1: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -159,11 +159,11 @@ public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends } SourceFile: "HostSideTestWholeClassKeep.java" RuntimeVisibleAnnotations: - 0: #x(#x=[e#x.#x]) + x: #x(#x=[e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE] ) - 1: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -179,11 +179,11 @@ public interface android.hosttest.annotation.HostSideTestWholeClassStub extends } SourceFile: "HostSideTestWholeClassStub.java" RuntimeVisibleAnnotations: - 0: #x(#x=[e#x.#x]) + x: #x(#x=[e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE] ) - 1: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -199,7 +199,7 @@ public interface android.hosttest.annotation.tests.HostSideTestSuppress extends } SourceFile: "HostSideTestSuppress.java" RuntimeVisibleAnnotations: - 0: #x(#x=[e#x.#x,e#x.#x,e#x.#x]) + x: #x(#x=[e#x.#x,e#x.#x,e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD] ) @@ -217,9 +217,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl flags: (0x0002) ACC_PRIVATE Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -230,11 +230,11 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 - 0: iconst_1 - 1: ireturn + x: iconst_1 + x: ireturn LineNumberTable: RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestKeep public static int getOneStub(); @@ -242,11 +242,11 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 - 0: iconst_1 - 1: ireturn + x: iconst_1 + x: ireturn LineNumberTable: RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub } SourceFile: "TinyFrameworkCallerCheck.java" @@ -267,9 +267,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -280,8 +280,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 - 0: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I - 3: ireturn + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I + x: ireturn LineNumberTable: public static int getOne_noCheck(); @@ -289,13 +289,13 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 - 0: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I - 3: ireturn + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I + x: ireturn LineNumberTable: } SourceFile: "TinyFrameworkCallerCheck.java" RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestMembers: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl @@ -314,14 +314,14 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota descriptor: I flags: (0x0001) ACC_PUBLIC RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub public int keep; descriptor: I flags: (0x0001) ACC_PUBLIC RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestKeep public int remove; @@ -333,21 +333,21 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: iconst_1 - 6: putfield #x // Field stub:I - 9: aload_0 - 10: iconst_2 - 11: putfield #x // Field keep:I - 14: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_1 + x: putfield #x // Field stub:I + x: aload_0 + x: iconst_2 + x: putfield #x // Field keep:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub public int addOne(int); @@ -355,17 +355,17 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: iload_1 - 2: invokevirtual #x // Method addOneInner:(I)I - 5: ireturn + x: aload_0 + x: iload_1 + x: invokevirtual #x // Method addOneInner:(I)I + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; 0 6 1 value I RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub public int addOneInner(int); @@ -373,17 +373,17 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: iload_1 - 1: iconst_1 - 2: iadd - 3: ireturn + x: iload_1 + x: iconst_1 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 0 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; 0 4 1 value I RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestKeep public void toBeRemoved(java.lang.String); @@ -391,17 +391,17 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: invokespecial #x // Method java/lang/RuntimeException."<init>":()V - 7: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V + x: athrow LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 0 8 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; 0 8 1 foo Ljava/lang/String; RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestRemove public int addTwo(int); @@ -409,20 +409,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String not supported on host side - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String not supported on host side + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; 0 10 1 value I RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub - 1: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestSubstitute( suffix="_host" ) @@ -432,10 +432,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: iload_1 - 1: iconst_2 - 2: iadd - 3: ireturn + x: iload_1 + x: iconst_2 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -446,9 +446,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota descriptor: (I)I flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub - 1: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestSubstitute( suffix="_host" ) @@ -458,10 +458,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 - 0: iload_0 - 1: iconst_3 - 2: iadd - 3: ireturn + x: iload_0 + x: iconst_3 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -472,14 +472,14 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: ldc #x // String This value shouldn\'t be seen on the host side. - 2: areturn + x: ldc #x // String This value shouldn\'t be seen on the host side. + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 0 3 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestThrow public java.lang.String visibleButUsesUnsupportedMethod(); @@ -487,22 +487,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; - 4: areturn + x: aload_0 + x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub } SourceFile: "TinyFrameworkClassAnnotations.java" RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub - 1: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestClassLoadHook( value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded" ) @@ -532,15 +532,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: iconst_1 - 6: putfield #x // Field stub:I - 9: aload_0 - 10: iconst_2 - 11: putfield #x // Field keep:I - 14: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_1 + x: putfield #x // Field stub:I + x: aload_0 + x: iconst_2 + x: putfield #x // Field keep:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -551,10 +551,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: iload_1 - 2: invokevirtual #x // Method addOneInner:(I)I - 5: ireturn + x: aload_0 + x: iload_1 + x: invokevirtual #x // Method addOneInner:(I)I + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -566,10 +566,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: iload_1 - 1: iconst_1 - 2: iadd - 3: ireturn + x: iload_1 + x: iconst_1 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -581,10 +581,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: invokespecial #x // Method java/lang/RuntimeException."<init>":()V - 7: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V + x: athrow LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -596,20 +596,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String not supported on host side - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String not supported on host side + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 0 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; 0 10 1 value I RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub - 1: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestSubstitute( suffix="_host" ) @@ -619,10 +619,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: iload_1 - 1: iconst_2 - 2: iadd - 3: ireturn + x: iload_1 + x: iconst_2 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -633,9 +633,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW descriptor: (I)I flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub - 1: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestSubstitute( suffix="_host" ) @@ -645,10 +645,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 - 0: iload_0 - 1: iconst_3 - 2: iadd - 3: ireturn + x: iload_0 + x: iconst_3 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -659,8 +659,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: ldc #x // String This value shouldn\'t be seen on the host side. - 2: areturn + x: ldc #x // String This value shouldn\'t be seen on the host side. + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -671,9 +671,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; - 4: areturn + x: aload_0 + x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -681,7 +681,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW } SourceFile: "TinyFrameworkClassClassWideAnnotations.java" RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class Compiled from "TinyFrameworkClassLoadHook.java" @@ -702,9 +702,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo flags: (0x0002) ACC_PRIVATE Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -715,11 +715,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 - 0: getstatic #x // Field sLoadedClasses:Ljava/util/Set; - 3: aload_0 - 4: invokeinterface #x, 2 // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z - 9: pop - 10: return + x: getstatic #x // Field sLoadedClasses:Ljava/util/Set; + x: aload_0 + x: invokeinterface #x, 2 // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z + x: pop + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -734,16 +734,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo flags: (0x0008) ACC_STATIC Code: stack=2, locals=0, args_size=0 - 0: new #x // class java/util/HashSet - 3: dup - 4: invokespecial #x // Method java/util/HashSet."<init>":()V - 7: putstatic #x // Field sLoadedClasses:Ljava/util/Set; - 10: return + x: new #x // class java/util/HashSet + x: dup + x: invokespecial #x // Method java/util/HashSet."<init>":()V + x: putstatic #x // Field sLoadedClasses:Ljava/util/Set; + x: return LineNumberTable: } SourceFile: "TinyFrameworkClassLoadHook.java" RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class Compiled from "TinyFrameworkClassWithInitializer.java" @@ -763,9 +763,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -776,18 +776,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn flags: (0x0008) ACC_STATIC Code: stack=1, locals=0, args_size=0 - 0: iconst_1 - 1: putstatic #x // Field sInitialized:Z - 4: return + x: iconst_1 + x: putstatic #x // Field sInitialized:Z + x: return LineNumberTable: } SourceFile: "TinyFrameworkClassWithInitializer.java" RuntimeInvisibleAnnotations: - 0: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestClassLoadHook( value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded" ) - 1: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class Compiled from "TinyFrameworkExceptionTester.java" @@ -803,9 +803,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -816,18 +816,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=0 - 0: new #x // class java/lang/IllegalStateException - 3: dup - 4: ldc #x // String Inner exception - 6: invokespecial #x // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V - 9: athrow - 10: astore_0 - 11: new #x // class java/lang/RuntimeException - 14: dup - 15: ldc #x // String Outer exception - 17: aload_0 - 18: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V - 21: athrow + x: new #x // class java/lang/IllegalStateException + x: dup + x: ldc #x // String Inner exception + x: invokespecial #x // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V + x: athrow + x: astore_0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Outer exception + x: aload_0 + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V + x: athrow Exception table: from to target type 0 10 10 Class java/lang/Exception @@ -841,7 +841,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe } SourceFile: "TinyFrameworkExceptionTester.java" RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class Compiled from "TinyFrameworkForTextPolicy.java" @@ -869,15 +869,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: iconst_1 - 6: putfield #x // Field stub:I - 9: aload_0 - 10: iconst_2 - 11: putfield #x // Field keep:I - 14: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_1 + x: putfield #x // Field stub:I + x: aload_0 + x: iconst_2 + x: putfield #x // Field keep:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -888,10 +888,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: iload_1 - 2: invokevirtual #x // Method addOneInner:(I)I - 5: ireturn + x: aload_0 + x: iload_1 + x: invokevirtual #x // Method addOneInner:(I)I + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -903,10 +903,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: iload_1 - 1: iconst_1 - 2: iadd - 3: ireturn + x: iload_1 + x: iconst_1 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -918,10 +918,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: invokespecial #x // Method java/lang/RuntimeException."<init>":()V - 7: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V + x: athrow LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -933,11 +933,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String not supported on host side - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String not supported on host side + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -949,10 +949,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: iload_1 - 1: iconst_2 - 2: iadd - 3: ireturn + x: iload_1 + x: iconst_2 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -968,10 +968,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 - 0: iload_0 - 1: iconst_3 - 2: iadd - 3: ireturn + x: iload_0 + x: iconst_3 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -982,8 +982,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: ldc #x // String This value shouldn\'t be seen on the host side. - 2: areturn + x: ldc #x // String This value shouldn\'t be seen on the host side. + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -994,9 +994,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; - 4: areturn + x: aload_0 + x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1017,9 +1017,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1034,9 +1034,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 - 0: iload_0 - 1: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I - 4: ireturn + x: iload_0 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1051,10 +1051,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=4, args_size=2 - 0: lload_0 - 1: lload_2 - 2: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J - 5: lreturn + x: lload_0 + x: lload_2 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J + x: lreturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1063,9 +1063,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative } SourceFile: "TinyFrameworkNative.java" RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub - 1: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestNativeSubstitutionClass( value="TinyFrameworkNative_host" ) @@ -1083,9 +1083,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1096,10 +1096,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 - 0: iload_0 - 1: iconst_2 - 2: iadd - 3: ireturn + x: iload_0 + x: iconst_2 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1110,10 +1110,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=4, args_size=2 - 0: lload_0 - 1: lload_2 - 2: ladd - 3: lreturn + x: lload_0 + x: lload_2 + x: ladd + x: lreturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1122,7 +1122,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host } SourceFile: "TinyFrameworkNative_host.java" RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassKeep ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1.class Compiled from "TinyFrameworkNestedClasses.java" @@ -1142,12 +1142,12 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex flags: (0x0000) Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: aload_1 - 2: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; - 5: aload_0 - 6: invokespecial #x // Method java/lang/Object."<init>":()V - 9: return + x: aload_0 + x: aload_1 + x: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1159,9 +1159,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: iconst_1 - 1: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; - 4: areturn + x: iconst_1 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1172,9 +1172,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokevirtual #x // Method get:()Ljava/lang/Integer; - 4: areturn + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1200,9 +1200,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex flags: (0x0000) Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1213,9 +1213,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: iconst_2 - 1: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; - 4: areturn + x: iconst_2 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1226,9 +1226,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokevirtual #x // Method get:()Ljava/lang/Integer; - 4: areturn + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1258,12 +1258,12 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex flags: (0x0000) Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: aload_1 - 2: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; - 5: aload_0 - 6: invokespecial #x // Method java/lang/Object."<init>":()V - 9: return + x: aload_0 + x: aload_1 + x: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1275,9 +1275,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: iconst_3 - 1: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; - 4: areturn + x: iconst_3 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1288,9 +1288,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokevirtual #x // Method get:()Ljava/lang/Integer; - 4: areturn + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1316,9 +1316,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex flags: (0x0000) Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1329,9 +1329,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: iconst_4 - 1: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; - 4: areturn + x: iconst_4 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1342,9 +1342,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokevirtual #x // Method get:()Ljava/lang/Integer; - 4: areturn + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1374,12 +1374,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: iload_1 - 6: putfield #x // Field value:I - 9: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iload_1 + x: putfield #x // Field value:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1412,15 +1412,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: aload_1 - 2: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; - 5: aload_0 - 6: invokespecial #x // Method java/lang/Object."<init>":()V - 9: aload_0 - 10: iconst_5 - 11: putfield #x // Field value:I - 14: return + x: aload_0 + x: aload_1 + x: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_5 + x: putfield #x // Field value:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1429,7 +1429,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass } SourceFile: "TinyFrameworkNestedClasses.java" RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses InnerClasses: @@ -1448,9 +1448,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat flags: (0x0000) Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1461,9 +1461,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: bipush 7 - 2: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; - 5: areturn + x: bipush 7 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1474,9 +1474,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokevirtual #x // Method get:()Ljava/lang/Integer; - 4: areturn + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1507,12 +1507,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: bipush 6 - 7: putfield #x // Field value:I - 10: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: bipush 6 + x: putfield #x // Field value:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1523,16 +1523,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=0, args_size=0 - 0: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 - 3: dup - 4: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V - 7: areturn + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + x: dup + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V + x: areturn LineNumberTable: Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; } SourceFile: "TinyFrameworkNestedClasses.java" RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses InnerClasses: @@ -1552,10 +1552,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: iload_1 - 2: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V - 5: return + x: aload_0 + x: iload_1 + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1591,15 +1591,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 - 8: dup - 9: aload_0 - 10: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V - 13: putfield #x // Field mSupplier:Ljava/util/function/Supplier; - 16: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + x: dup + x: aload_0 + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + x: putfield #x // Field mSupplier:Ljava/util/function/Supplier; + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1610,11 +1610,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 - 3: dup - 4: aload_0 - 5: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V - 8: areturn + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + x: dup + x: aload_0 + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1626,10 +1626,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=0, args_size=0 - 0: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 - 3: dup - 4: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V - 7: areturn + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + x: dup + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V + x: areturn LineNumberTable: Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; @@ -1638,16 +1638,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0008) ACC_STATIC Code: stack=2, locals=0, args_size=0 - 0: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 - 3: dup - 4: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V - 7: putstatic #x // Field sSupplier:Ljava/util/function/Supplier; - 10: return + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + x: dup + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V + x: putstatic #x // Field sSupplier:Ljava/util/function/Supplier; + x: return LineNumberTable: } SourceFile: "TinyFrameworkNestedClasses.java" RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestMembers: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt index 6e1528aedc1e..43ceec42660d 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt @@ -12,33 +12,33 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl flags: (0x0002) ACC_PRIVATE Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public static int getOneStub(); descriptor: ()I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=0, args_size=0 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub } InnerClasses: private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck SourceFile: "TinyFrameworkCallerCheck.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class @@ -55,44 +55,44 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public static int getOne_withCheck(); descriptor: ()I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=0, args_size=0 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public static int getOne_noCheck(); descriptor: ()I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=0, args_size=0 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } InnerClasses: private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck SourceFile: "TinyFrameworkCallerCheck.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestMembers: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl @@ -109,7 +109,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota descriptor: I flags: (0x0001) ACC_PUBLIC RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations(); @@ -117,13 +117,13 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub public int addOne(int); @@ -131,13 +131,13 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub public int addTwo(int); @@ -145,47 +145,47 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public static int nativeAddThree(int); descriptor: (I)I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public java.lang.String visibleButUsesUnsupportedMethod(); descriptor: ()Ljava/lang/String; flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub } SourceFile: "TinyFrameworkClassAnnotations.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub - 1: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestClassLoadHook( value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded" ) @@ -215,97 +215,97 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public int addOne(int); descriptor: (I)I flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public int addOneInner(int); descriptor: (I)I flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public void toBeRemoved(java.lang.String); descriptor: (Ljava/lang/String;)V flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public int addTwo(int); descriptor: (I)I flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public static int nativeAddThree(int); descriptor: (I)I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public java.lang.String unsupportedMethod(); descriptor: ()Ljava/lang/String; flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public java.lang.String visibleButUsesUnsupportedMethod(); descriptor: ()Ljava/lang/String; flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } SourceFile: "TinyFrameworkClassClassWideAnnotations.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class Compiled from "TinyFrameworkClassLoadHook.java" @@ -326,22 +326,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo flags: (0x0002) ACC_PRIVATE Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public static void onClassLoaded(java.lang.Class<?>); descriptor: (Ljava/lang/Class;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow Signature: #x // (Ljava/lang/Class<*>;)V static {}; @@ -349,20 +349,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo flags: (0x0008) ACC_STATIC Code: stack=3, locals=0, args_size=0 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } SourceFile: "TinyFrameworkClassLoadHook.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class Compiled from "TinyFrameworkClassWithInitializer.java" @@ -382,35 +382,35 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=3, locals=0, args_size=0 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } SourceFile: "TinyFrameworkClassWithInitializer.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestClassLoadHook( value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded" ) - 1: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class Compiled from "TinyFrameworkExceptionTester.java" @@ -426,31 +426,31 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public static int testException(); descriptor: ()I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=0, args_size=0 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } SourceFile: "TinyFrameworkExceptionTester.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class Compiled from "TinyFrameworkForTextPolicy.java" @@ -470,61 +470,61 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public int addOne(int); descriptor: (I)I flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public int addTwo(int); descriptor: (I)I flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public static int nativeAddThree(int); descriptor: (I)I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public java.lang.String visibleButUsesUnsupportedMethod(); descriptor: ()Ljava/lang/String; flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } SourceFile: "TinyFrameworkForTextPolicy.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class Compiled from "TinyFrameworkNative.java" @@ -540,11 +540,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public static native int nativeAddTwo(int); descriptor: (I)I @@ -555,11 +555,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public static native long nativeLongPlus(long, long); descriptor: (JJ)J @@ -570,22 +570,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=4, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub - 1: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestNativeSubstitutionClass( value="TinyFrameworkNative_host" ) @@ -607,19 +607,19 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } InnerClasses: public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class @@ -644,22 +644,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } InnerClasses: public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class @@ -680,22 +680,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public static java.util.function.Supplier<java.lang.Integer> getSupplier_static(); descriptor: ()Ljava/util/function/Supplier; flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=0, args_size=0 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; } InnerClasses: @@ -703,12 +703,12 @@ InnerClasses: #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class @@ -725,20 +725,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } InnerClasses: public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses public static #x= #x of #x; // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class @@ -765,22 +765,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public java.util.function.Supplier<java.lang.Integer> getSupplier(); descriptor: ()Ljava/util/function/Supplier; flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; public static java.util.function.Supplier<java.lang.Integer> getSupplier_static(); @@ -788,11 +788,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=0, args_size=0 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; static {}; @@ -800,11 +800,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0008) ACC_STATIC Code: stack=3, locals=0, args_size=0 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: ldc #x // String Stub! - 6: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 9: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow } InnerClasses: #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 @@ -818,12 +818,12 @@ InnerClasses: #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestMembers: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt index 5672e9c36706..faf0a46c8fab 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt @@ -13,13 +13,13 @@ public interface android.hosttest.annotation.HostSideTestClassLoadHook extends j } SourceFile: "HostSideTestClassLoadHook.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass - 1: #x(#x=[e#x.#x]) + x: #x(#x=[e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE] ) - 2: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -35,13 +35,13 @@ public interface android.hosttest.annotation.HostSideTestKeep extends java.lang. } SourceFile: "HostSideTestKeep.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass - 1: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) + x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] ) - 2: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -60,13 +60,13 @@ public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass } SourceFile: "HostSideTestNativeSubstitutionClass.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass - 1: #x(#x=[e#x.#x]) + x: #x(#x=[e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE] ) - 2: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -82,13 +82,13 @@ public interface android.hosttest.annotation.HostSideTestRemove extends java.lan } SourceFile: "HostSideTestRemove.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass - 1: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) + x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] ) - 2: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -104,13 +104,13 @@ public interface android.hosttest.annotation.HostSideTestStub extends java.lang. } SourceFile: "HostSideTestStub.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass - 1: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) + x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] ) - 2: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -129,13 +129,13 @@ public interface android.hosttest.annotation.HostSideTestSubstitute extends java } SourceFile: "HostSideTestSubstitute.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass - 1: #x(#x=[e#x.#x]) + x: #x(#x=[e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.METHOD] ) - 2: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -151,13 +151,13 @@ public interface android.hosttest.annotation.HostSideTestThrow extends java.lang } SourceFile: "HostSideTestThrow.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass - 1: #x(#x=[e#x.#x,e#x.#x]) + x: #x(#x=[e#x.#x,e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] ) - 2: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -173,13 +173,13 @@ public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends } SourceFile: "HostSideTestWholeClassKeep.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass - 1: #x(#x=[e#x.#x]) + x: #x(#x=[e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE] ) - 2: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -195,13 +195,13 @@ public interface android.hosttest.annotation.HostSideTestWholeClassStub extends } SourceFile: "HostSideTestWholeClassStub.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass - 1: #x(#x=[e#x.#x]) + x: #x(#x=[e#x.#x]) java.lang.annotation.Target( value=[Ljava/lang/annotation/ElementType;.TYPE] ) - 2: #x(#x=e#x.#x) + x: #x(#x=e#x.#x) java.lang.annotation.Retention( value=Ljava/lang/annotation/RetentionPolicy;.CLASS ) @@ -219,9 +219,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl flags: (0x0002) ACC_PRIVATE Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -232,17 +232,17 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=0, args_size=0 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl - 2: ldc #x // String getOneKeep - 4: ldc #x // String ()I - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: iconst_1 - 16: ireturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + x: ldc #x // String getOneKeep + x: ldc #x // String ()I + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iconst_1 + x: ireturn LineNumberTable: RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestKeep public static int getOneStub(); @@ -250,20 +250,20 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 - 0: iconst_1 - 1: ireturn + x: iconst_1 + x: ireturn LineNumberTable: RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub } InnerClasses: private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck SourceFile: "TinyFrameworkCallerCheck.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class @@ -280,9 +280,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -293,8 +293,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 - 0: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I - 3: ireturn + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I + x: ireturn LineNumberTable: public static int getOne_noCheck(); @@ -302,20 +302,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 - 0: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I - 3: ireturn + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I + x: ireturn LineNumberTable: } InnerClasses: private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck SourceFile: "TinyFrameworkCallerCheck.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestMembers: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl @@ -332,14 +332,14 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota descriptor: I flags: (0x0001) ACC_PUBLIC RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub public int keep; descriptor: I flags: (0x0001) ACC_PUBLIC RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestKeep private static {}; @@ -347,31 +347,31 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x000a) ACC_PRIVATE, ACC_STATIC Code: stack=2, locals=0, args_size=0 - 0: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations - 2: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded - 4: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V - 7: return + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: iconst_1 - 6: putfield #x // Field stub:I - 9: aload_0 - 10: iconst_2 - 11: putfield #x // Field keep:I - 14: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_1 + x: putfield #x // Field stub:I + x: aload_0 + x: iconst_2 + x: putfield #x // Field keep:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 0 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub public int addOne(int); @@ -379,17 +379,17 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: iload_1 - 2: invokevirtual #x // Method addOneInner:(I)I - 5: ireturn + x: aload_0 + x: iload_1 + x: invokevirtual #x // Method addOneInner:(I)I + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 0 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; 0 6 1 value I RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub public int addOneInner(int); @@ -397,23 +397,23 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=2, args_size=2 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations - 2: ldc #x // String addOneInner - 4: ldc #x // String (I)I - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: iload_1 - 16: iconst_1 - 17: iadd - 18: ireturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String addOneInner + x: ldc #x // String (I)I + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iload_1 + x: iconst_1 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 15 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; 15 4 1 value I RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestKeep public int addTwo(int); @@ -421,10 +421,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: iload_1 - 1: iconst_2 - 2: iadd - 3: ireturn + x: iload_1 + x: iconst_2 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -436,10 +436,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 - 0: iload_0 - 1: iconst_3 - 2: iadd - 3: ireturn + x: iload_0 + x: iconst_3 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -450,20 +450,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations - 2: ldc #x // String unsupportedMethod - 4: ldc #x // String ()Ljava/lang/String; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V - 18: new #x // class java/lang/RuntimeException - 21: dup - 22: ldc #x // String Unreachable - 24: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 27: athrow + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String unsupportedMethod + x: ldc #x // String ()Ljava/lang/String; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Unreachable + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestThrow public java.lang.String visibleButUsesUnsupportedMethod(); @@ -471,27 +471,27 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; - 4: areturn + x: aload_0 + x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub } SourceFile: "TinyFrameworkClassAnnotations.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestStub - 1: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestClassLoadHook( value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded" ) @@ -521,15 +521,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: iconst_1 - 6: putfield #x // Field stub:I - 9: aload_0 - 10: iconst_2 - 11: putfield #x // Field keep:I - 14: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_1 + x: putfield #x // Field stub:I + x: aload_0 + x: iconst_2 + x: putfield #x // Field keep:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -540,10 +540,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: iload_1 - 2: invokevirtual #x // Method addOneInner:(I)I - 5: ireturn + x: aload_0 + x: iload_1 + x: invokevirtual #x // Method addOneInner:(I)I + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -555,10 +555,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: iload_1 - 1: iconst_1 - 2: iadd - 3: ireturn + x: iload_1 + x: iconst_1 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -570,10 +570,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: new #x // class java/lang/RuntimeException - 3: dup - 4: invokespecial #x // Method java/lang/RuntimeException."<init>":()V - 7: athrow + x: new #x // class java/lang/RuntimeException + x: dup + x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V + x: athrow LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -585,10 +585,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: iload_1 - 1: iconst_2 - 2: iadd - 3: ireturn + x: iload_1 + x: iconst_2 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -600,10 +600,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 - 0: iload_0 - 1: iconst_3 - 2: iadd - 3: ireturn + x: iload_0 + x: iconst_3 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -614,8 +614,8 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: ldc #x // String This value shouldn\'t be seen on the host side. - 2: areturn + x: ldc #x // String This value shouldn\'t be seen on the host side. + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -626,9 +626,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; - 4: areturn + x: aload_0 + x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -636,12 +636,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassW } SourceFile: "TinyFrameworkClassClassWideAnnotations.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class Compiled from "TinyFrameworkClassLoadHook.java" @@ -662,9 +662,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo flags: (0x0002) ACC_PRIVATE Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -675,11 +675,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 - 0: getstatic #x // Field sLoadedClasses:Ljava/util/Set; - 3: aload_0 - 4: invokeinterface #x, 2 // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z - 9: pop - 10: return + x: getstatic #x // Field sLoadedClasses:Ljava/util/Set; + x: aload_0 + x: invokeinterface #x, 2 // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z + x: pop + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -694,21 +694,21 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHo flags: (0x0008) ACC_STATIC Code: stack=2, locals=0, args_size=0 - 0: new #x // class java/util/HashSet - 3: dup - 4: invokespecial #x // Method java/util/HashSet."<init>":()V - 7: putstatic #x // Field sLoadedClasses:Ljava/util/Set; - 10: return + x: new #x // class java/util/HashSet + x: dup + x: invokespecial #x // Method java/util/HashSet."<init>":()V + x: putstatic #x // Field sLoadedClasses:Ljava/util/Set; + x: return LineNumberTable: } SourceFile: "TinyFrameworkClassLoadHook.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class Compiled from "TinyFrameworkClassWithInitializer.java" @@ -728,9 +728,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -741,26 +741,26 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn flags: (0x0008) ACC_STATIC Code: stack=2, locals=0, args_size=0 - 0: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer - 2: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded - 4: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V - 7: iconst_1 - 8: putstatic #x // Field sInitialized:Z - 11: return + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer + x: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: iconst_1 + x: putstatic #x // Field sInitialized:Z + x: return LineNumberTable: } SourceFile: "TinyFrameworkClassWithInitializer.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestClassLoadHook( value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded" ) - 1: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class Compiled from "TinyFrameworkExceptionTester.java" @@ -776,9 +776,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -789,18 +789,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=0 - 0: new #x // class java/lang/IllegalStateException - 3: dup - 4: ldc #x // String Inner exception - 6: invokespecial #x // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V - 9: athrow - 10: astore_0 - 11: new #x // class java/lang/RuntimeException - 14: dup - 15: ldc #x // String Outer exception - 17: aload_0 - 18: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V - 21: athrow + x: new #x // class java/lang/IllegalStateException + x: dup + x: ldc #x // String Inner exception + x: invokespecial #x // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V + x: athrow + x: astore_0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Outer exception + x: aload_0 + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V + x: athrow Exception table: from to target type 0 10 10 Class java/lang/Exception @@ -814,12 +814,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTe } SourceFile: "TinyFrameworkExceptionTester.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class Compiled from "TinyFrameworkForTextPolicy.java" @@ -843,25 +843,25 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x000a) ACC_PRIVATE, ACC_STATIC Code: stack=2, locals=0, args_size=0 - 0: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy - 2: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded - 4: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V - 7: return + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return public com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: iconst_1 - 6: putfield #x // Field stub:I - 9: aload_0 - 10: iconst_2 - 11: putfield #x // Field keep:I - 14: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_1 + x: putfield #x // Field stub:I + x: aload_0 + x: iconst_2 + x: putfield #x // Field keep:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -872,10 +872,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: iload_1 - 2: invokevirtual #x // Method addOneInner:(I)I - 5: ireturn + x: aload_0 + x: iload_1 + x: invokevirtual #x // Method addOneInner:(I)I + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -887,16 +887,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=2, args_size=2 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy - 2: ldc #x // String addOneInner - 4: ldc #x // String (I)I - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: iload_1 - 16: iconst_1 - 17: iadd - 18: ireturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String addOneInner + x: ldc #x // String (I)I + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iload_1 + x: iconst_1 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -908,10 +908,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: iload_1 - 1: iconst_2 - 2: iadd - 3: ireturn + x: iload_1 + x: iconst_2 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -923,10 +923,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 - 0: iload_0 - 1: iconst_3 - 2: iadd - 3: ireturn + x: iload_0 + x: iconst_3 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -937,27 +937,27 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy - 2: ldc #x // String unsupportedMethod - 4: ldc #x // String ()Ljava/lang/String; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V - 18: new #x // class java/lang/RuntimeException - 21: dup - 22: ldc #x // String Unreachable - 24: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - 27: athrow + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String unsupportedMethod + x: ldc #x // String ()Ljava/lang/String; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Unreachable + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow public java.lang.String visibleButUsesUnsupportedMethod(); descriptor: ()Ljava/lang/String; flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; - 4: areturn + x: aload_0 + x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -965,9 +965,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPoli } SourceFile: "TinyFrameworkForTextPolicy.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class Compiled from "TinyFrameworkNative.java" @@ -983,9 +983,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -996,18 +996,18 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 - 0: iload_0 - 1: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I - 4: ireturn + x: iload_0 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I + x: ireturn public static int nativeAddTwo_should_be_like_this(int); descriptor: (I)I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 - 0: iload_0 - 1: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I - 4: ireturn + x: iload_0 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1018,20 +1018,20 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=4, args_size=2 - 0: lload_0 - 1: lload_2 - 2: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J - 5: lreturn + x: lload_0 + x: lload_2 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J + x: lreturn public static long nativeLongPlus_should_be_like_this(long, long); descriptor: (JJ)J flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=4, args_size=2 - 0: lload_0 - 1: lload_2 - 2: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J - 5: lreturn + x: lload_0 + x: lload_2 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J + x: lreturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1040,14 +1040,14 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub - 1: #x(#x=s#x) + x: #x(#x=s#x) android.hosttest.annotation.HostSideTestNativeSubstitutionClass( value="TinyFrameworkNative_host" ) @@ -1065,15 +1065,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host - 2: ldc #x // String <init> - 4: ldc #x // String ()V - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: aload_0 - 16: invokespecial #x // Method java/lang/Object."<init>":()V - 19: return + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String <init> + x: ldc #x // String ()V + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1084,16 +1084,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host - 2: ldc #x // String nativeAddTwo - 4: ldc #x // String (I)I - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: iload_0 - 16: iconst_2 - 17: iadd - 18: ireturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeAddTwo + x: ldc #x // String (I)I + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iload_0 + x: iconst_2 + x: iadd + x: ireturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1104,16 +1104,16 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=4, args_size=2 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host - 2: ldc #x // String nativeLongPlus - 4: ldc #x // String (JJ)J - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: lload_0 - 16: lload_2 - 17: ladd - 18: lreturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeLongPlus + x: ldc #x // String (JJ)J + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: lload_0 + x: lload_2 + x: ladd + x: lreturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1122,10 +1122,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host } SourceFile: "TinyFrameworkNative_host.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassKeep ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1.class Compiled from "TinyFrameworkNestedClasses.java" @@ -1145,12 +1145,12 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex flags: (0x0000) Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: aload_1 - 2: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; - 5: aload_0 - 6: invokespecial #x // Method java/lang/Object."<init>":()V - 9: return + x: aload_0 + x: aload_1 + x: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1162,15 +1162,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 - 2: ldc #x // String get - 4: ldc #x // String ()Ljava/lang/Integer; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: iconst_1 - 16: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; - 19: areturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iconst_1 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1181,15 +1181,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 ex flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 - 2: ldc #x // String get - 4: ldc #x // String ()Ljava/lang/Object; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: aload_0 - 16: invokevirtual #x // Method get:()Ljava/lang/Integer; - 19: areturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1201,7 +1201,7 @@ EnclosingMethod: #x.#x // com.android.hoststubgen.test.tinyframe Signature: #x // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>; SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2.class @@ -1218,9 +1218,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex flags: (0x0000) Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1231,15 +1231,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 - 2: ldc #x // String get - 4: ldc #x // String ()Ljava/lang/Integer; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: iconst_2 - 16: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; - 19: areturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iconst_2 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1250,15 +1250,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 ex flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 - 2: ldc #x // String get - 4: ldc #x // String ()Ljava/lang/Object; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: aload_0 - 16: invokevirtual #x // Method get:()Ljava/lang/Integer; - 19: areturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1270,7 +1270,7 @@ EnclosingMethod: #x.#x // com.android.hoststubgen.test.tinyframe Signature: #x // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>; SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3.class @@ -1291,12 +1291,12 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex flags: (0x0000) Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: aload_1 - 2: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; - 5: aload_0 - 6: invokespecial #x // Method java/lang/Object."<init>":()V - 9: return + x: aload_0 + x: aload_1 + x: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1308,15 +1308,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 - 2: ldc #x // String get - 4: ldc #x // String ()Ljava/lang/Integer; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: iconst_3 - 16: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; - 19: areturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iconst_3 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1327,15 +1327,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 ex flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 - 2: ldc #x // String get - 4: ldc #x // String ()Ljava/lang/Object; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: aload_0 - 16: invokevirtual #x // Method get:()Ljava/lang/Integer; - 19: areturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1347,7 +1347,7 @@ EnclosingMethod: #x.#x // com.android.hoststubgen.test.tinyframew Signature: #x // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>; SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4.class @@ -1364,9 +1364,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex flags: (0x0000) Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1377,15 +1377,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 - 2: ldc #x // String get - 4: ldc #x // String ()Ljava/lang/Integer; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: iconst_4 - 16: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; - 19: areturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iconst_4 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1396,15 +1396,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 ex flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 - 2: ldc #x // String get - 4: ldc #x // String ()Ljava/lang/Object; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: aload_0 - 16: invokevirtual #x // Method get:()Ljava/lang/Integer; - 19: areturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1416,7 +1416,7 @@ EnclosingMethod: #x.#x // com.android.hoststubgen.test.tinyframew Signature: #x // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>; SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class @@ -1437,12 +1437,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: iload_1 - 6: putfield #x // Field value:I - 9: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iload_1 + x: putfield #x // Field value:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1453,9 +1453,9 @@ InnerClasses: public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class @@ -1480,15 +1480,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: aload_1 - 2: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; - 5: aload_0 - 6: invokespecial #x // Method java/lang/Object."<init>":()V - 9: aload_0 - 10: iconst_5 - 11: putfield #x // Field value:I - 14: return + x: aload_0 + x: aload_1 + x: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_5 + x: putfield #x // Field value:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1499,12 +1499,12 @@ InnerClasses: public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class @@ -1521,9 +1521,9 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat flags: (0x0000) Code: stack=1, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1534,15 +1534,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 - 2: ldc #x // String get - 4: ldc #x // String ()Ljava/lang/Integer; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: bipush 7 - 17: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; - 20: areturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: bipush 7 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1553,15 +1553,15 @@ class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$Stat flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC Code: stack=4, locals=1, args_size=1 - 0: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 - 2: ldc #x // String get - 4: ldc #x // String ()Ljava/lang/Object; - 6: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; - 9: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; - 12: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V - 15: aload_0 - 16: invokevirtual #x // Method get:()Ljava/lang/Integer; - 19: areturn + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1574,7 +1574,7 @@ EnclosingMethod: #x.#x // com.android.hoststubgen.test.tinyframew Signature: #x // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>; SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class @@ -1595,12 +1595,12 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: bipush 6 - 7: putfield #x // Field value:I - 10: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: bipush 6 + x: putfield #x // Field value:I + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1611,10 +1611,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=0, args_size=0 - 0: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 - 3: dup - 4: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V - 7: areturn + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + x: dup + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V + x: areturn LineNumberTable: Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; } @@ -1623,12 +1623,12 @@ InnerClasses: #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class @@ -1645,10 +1645,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=2, args_size=2 - 0: aload_0 - 1: iload_1 - 2: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V - 5: return + x: aload_0 + x: iload_1 + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1660,9 +1660,9 @@ InnerClasses: public static #x= #x of #x; // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class @@ -1689,15 +1689,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 - 0: aload_0 - 1: invokespecial #x // Method java/lang/Object."<init>":()V - 4: aload_0 - 5: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 - 8: dup - 9: aload_0 - 10: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V - 13: putfield #x // Field mSupplier:Ljava/util/function/Supplier; - 16: return + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + x: dup + x: aload_0 + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + x: putfield #x // Field mSupplier:Ljava/util/function/Supplier; + x: return LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1708,11 +1708,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0001) ACC_PUBLIC Code: stack=3, locals=1, args_size=1 - 0: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 - 3: dup - 4: aload_0 - 5: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V - 8: areturn + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + x: dup + x: aload_0 + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + x: areturn LineNumberTable: LocalVariableTable: Start Length Slot Name Signature @@ -1724,10 +1724,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=0, args_size=0 - 0: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 - 3: dup - 4: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V - 7: areturn + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + x: dup + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V + x: areturn LineNumberTable: Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; @@ -1736,11 +1736,11 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClass flags: (0x0008) ACC_STATIC Code: stack=2, locals=0, args_size=0 - 0: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 - 3: dup - 4: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V - 7: putstatic #x // Field sSupplier:Ljava/util/function/Supplier; - 10: return + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + x: dup + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V + x: putstatic #x // Field sSupplier:Ljava/util/function/Supplier; + x: return LineNumberTable: } InnerClasses: @@ -1755,12 +1755,12 @@ InnerClasses: #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 SourceFile: "TinyFrameworkNestedClasses.java" RuntimeVisibleAnnotations: - 0: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass - 1: #x() + x: #x() com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass RuntimeInvisibleAnnotations: - 0: #x() + x: #x() android.hosttest.annotation.HostSideTestWholeClassStub NestMembers: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt new file mode 100644 index 000000000000..43ceec42660d --- /dev/null +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt @@ -0,0 +1,837 @@ +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class + Compiled from "TinyFrameworkCallerCheck.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl + minor version: 0 + major version: 61 + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 4 + private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); + descriptor: ()V + flags: (0x0002) ACC_PRIVATE + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static int getOneStub(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub +} +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class + Compiled from "TinyFrameworkCallerCheck.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 3, attributes: 5 + public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static int getOne_withCheck(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static int getOne_noCheck(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +NestMembers: + com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class + Compiled from "TinyFrameworkClassAnnotations.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 5, attributes: 3 + public int stub; + descriptor: I + flags: (0x0001) ACC_PUBLIC + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub + + public int addOne(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub + + public int addTwo(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static int nativeAddThree(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public java.lang.String visibleButUsesUnsupportedMethod(); + descriptor: ()Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub +} +SourceFile: "TinyFrameworkClassAnnotations.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub + x: #x(#x=s#x) + android.hosttest.annotation.HostSideTestClassLoadHook( + value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded" + ) +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class + Compiled from "TinyFrameworkClassClassWideAnnotations.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + super_class: #x // java/lang/Object + interfaces: 0, fields: 3, methods: 8, attributes: 3 + public int stub; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + public int keep; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + public int remove; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public int addOne(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public int addOneInner(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public void toBeRemoved(java.lang.String); + descriptor: (Ljava/lang/String;)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public int addTwo(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static int nativeAddThree(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public java.lang.String unsupportedMethod(); + descriptor: ()Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public java.lang.String visibleButUsesUnsupportedMethod(); + descriptor: ()Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +SourceFile: "TinyFrameworkClassClassWideAnnotations.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class + Compiled from "TinyFrameworkClassLoadHook.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 3, attributes: 3 + public static final java.util.Set<java.lang.Class<?>> sLoadedClasses; + descriptor: Ljava/util/Set; + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + Signature: #x // Ljava/util/Set<Ljava/lang/Class<*>;>; + + private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook(); + descriptor: ()V + flags: (0x0002) ACC_PRIVATE + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static void onClassLoaded(java.lang.Class<?>); + descriptor: (Ljava/lang/Class;)V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + Signature: #x // (Ljava/lang/Class<*>;)V + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +SourceFile: "TinyFrameworkClassLoadHook.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class + Compiled from "TinyFrameworkClassWithInitializer.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 3 + public static boolean sInitialized; + descriptor: Z + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +SourceFile: "TinyFrameworkClassWithInitializer.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x(#x=s#x) + android.hosttest.annotation.HostSideTestClassLoadHook( + value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded" + ) + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class + Compiled from "TinyFrameworkExceptionTester.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 3 + public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static int testException(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +SourceFile: "TinyFrameworkExceptionTester.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class + Compiled from "TinyFrameworkForTextPolicy.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 5, attributes: 2 + public int stub; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public int addOne(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public int addTwo(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static int nativeAddThree(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public java.lang.String visibleButUsesUnsupportedMethod(); + descriptor: ()Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +SourceFile: "TinyFrameworkForTextPolicy.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class + Compiled from "TinyFrameworkNative.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 5, attributes: 3 + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static native int nativeAddTwo(int); + descriptor: (I)I + flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + + public static int nativeAddTwo_should_be_like_this(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static native long nativeLongPlus(long, long); + descriptor: (JJ)J + flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + + public static long nativeLongPlus_should_be_like_this(long, long); + descriptor: (JJ)J + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=4, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +SourceFile: "TinyFrameworkNative.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub + x: #x(#x=s#x) + android.hosttest.annotation.HostSideTestNativeSubstitutionClass( + value="TinyFrameworkNative_host" + ) +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class + Compiled from "TinyFrameworkNestedClasses.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 1, attributes: 4 + public int value; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass(int); + descriptor: (I)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +InnerClasses: + public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class + Compiled from "TinyFrameworkNestedClasses.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass + super_class: #x // java/lang/Object + interfaces: 0, fields: 2, methods: 1, attributes: 5 + public int value; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0; + descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses); + descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +InnerClasses: + public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class + Compiled from "TinyFrameworkNestedClasses.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 5 + public int value; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public static java.util.function.Supplier<java.lang.Integer> getSupplier_static(); + descriptor: ()Ljava/util/function/Supplier; + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; +} +InnerClasses: + public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class + Compiled from "TinyFrameworkNestedClasses.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass + super_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass + interfaces: 0, fields: 0, methods: 1, attributes: 4 + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int); + descriptor: (I)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=2, args_size=2 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +InnerClasses: + public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + public static #x= #x of #x; // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class + Compiled from "TinyFrameworkNestedClasses.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + super_class: #x // java/lang/Object + interfaces: 0, fields: 2, methods: 4, attributes: 5 + public final java.util.function.Supplier<java.lang.Integer> mSupplier; + descriptor: Ljava/util/function/Supplier; + flags: (0x0011) ACC_PUBLIC, ACC_FINAL + Signature: #x // Ljava/util/function/Supplier<Ljava/lang/Integer;>; + + public static final java.util.function.Supplier<java.lang.Integer> sSupplier; + descriptor: Ljava/util/function/Supplier; + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + Signature: #x // Ljava/util/function/Supplier<Ljava/lang/Integer;>; + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public java.util.function.Supplier<java.lang.Integer> getSupplier(); + descriptor: ()Ljava/util/function/Supplier; + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; + + public static java.util.function.Supplier<java.lang.Integer> getSupplier_static(); + descriptor: ()Ljava/util/function/Supplier; + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow +} +InnerClasses: + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + public static #x= #x of #x; // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +NestMembers: + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt new file mode 100644 index 000000000000..874789e23607 --- /dev/null +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt @@ -0,0 +1,2348 @@ +## Class: android/hosttest/annotation/HostSideTestClassLoadHook.class + Compiled from "HostSideTestClassLoadHook.java" +public interface android.hosttest.annotation.HostSideTestClassLoadHook extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestClassLoadHook + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 2, attributes: 2 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class android/hosttest/annotation/HostSideTestClassLoadHook + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public abstract java.lang.String value(); + descriptor: ()Ljava/lang/String; + flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT +} +SourceFile: "HostSideTestClassLoadHook.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass + x: #x(#x=[e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.TYPE] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: android/hosttest/annotation/HostSideTestKeep.class + Compiled from "HostSideTestKeep.java" +public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestKeep + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 1, attributes: 2 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class android/hosttest/annotation/HostSideTestKeep + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return +} +SourceFile: "HostSideTestKeep.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass + x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: android/hosttest/annotation/HostSideTestNativeSubstitutionClass.class + Compiled from "HostSideTestNativeSubstitutionClass.java" +public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestNativeSubstitutionClass + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 2, attributes: 2 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class android/hosttest/annotation/HostSideTestNativeSubstitutionClass + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public abstract java.lang.String value(); + descriptor: ()Ljava/lang/String; + flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT +} +SourceFile: "HostSideTestNativeSubstitutionClass.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass + x: #x(#x=[e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.TYPE] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: android/hosttest/annotation/HostSideTestRemove.class + Compiled from "HostSideTestRemove.java" +public interface android.hosttest.annotation.HostSideTestRemove extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestRemove + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 1, attributes: 2 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class android/hosttest/annotation/HostSideTestRemove + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return +} +SourceFile: "HostSideTestRemove.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass + x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: android/hosttest/annotation/HostSideTestStub.class + Compiled from "HostSideTestStub.java" +public interface android.hosttest.annotation.HostSideTestStub extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestStub + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 1, attributes: 2 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class android/hosttest/annotation/HostSideTestStub + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return +} +SourceFile: "HostSideTestStub.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass + x: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: android/hosttest/annotation/HostSideTestSubstitute.class + Compiled from "HostSideTestSubstitute.java" +public interface android.hosttest.annotation.HostSideTestSubstitute extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestSubstitute + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 2, attributes: 2 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class android/hosttest/annotation/HostSideTestSubstitute + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public abstract java.lang.String suffix(); + descriptor: ()Ljava/lang/String; + flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT +} +SourceFile: "HostSideTestSubstitute.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass + x: #x(#x=[e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.METHOD] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: android/hosttest/annotation/HostSideTestThrow.class + Compiled from "HostSideTestThrow.java" +public interface android.hosttest.annotation.HostSideTestThrow extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestThrow + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 1, attributes: 2 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class android/hosttest/annotation/HostSideTestThrow + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return +} +SourceFile: "HostSideTestThrow.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass + x: #x(#x=[e#x.#x,e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: android/hosttest/annotation/HostSideTestWholeClassKeep.class + Compiled from "HostSideTestWholeClassKeep.java" +public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestWholeClassKeep + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 1, attributes: 2 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class android/hosttest/annotation/HostSideTestWholeClassKeep + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return +} +SourceFile: "HostSideTestWholeClassKeep.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass + x: #x(#x=[e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.TYPE] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: android/hosttest/annotation/HostSideTestWholeClassStub.class + Compiled from "HostSideTestWholeClassStub.java" +public interface android.hosttest.annotation.HostSideTestWholeClassStub extends java.lang.annotation.Annotation + minor version: 0 + major version: 61 + flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION + this_class: #x // android/hosttest/annotation/HostSideTestWholeClassStub + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 1, attributes: 2 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class android/hosttest/annotation/HostSideTestWholeClassStub + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return +} +SourceFile: "HostSideTestWholeClassStub.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass + x: #x(#x=[e#x.#x]) + java.lang.annotation.Target( + value=[Ljava/lang/annotation/ElementType;.TYPE] + ) + x: #x(#x=e#x.#x) + java.lang.annotation.Retention( + value=Ljava/lang/annotation/RetentionPolicy;.CLASS + ) +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class + Compiled from "TinyFrameworkCallerCheck.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl + minor version: 0 + major version: 61 + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 4, attributes: 4 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl(); + descriptor: ()V + flags: (0x0002) ACC_PRIVATE + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl; + + public static int getOneKeep(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + x: ldc #x // String getOneKeep + x: ldc #x // String ()I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + x: ldc #x // String getOneKeep + x: ldc #x // String ()I + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iconst_1 + x: ireturn + LineNumberTable: + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestKeep + + public static int getOneStub(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl + x: ldc #x // String getOneStub + x: ldc #x // String ()I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iconst_1 + x: ireturn + LineNumberTable: + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub +} +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class + Compiled from "TinyFrameworkCallerCheck.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 4, attributes: 5 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck; + + public static int getOne_withCheck(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + x: ldc #x // String getOne_withCheck + x: ldc #x // String ()I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I + x: ireturn + LineNumberTable: + + public static int getOne_noCheck(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck + x: ldc #x // String getOne_noCheck + x: ldc #x // String ()I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I + x: ireturn + LineNumberTable: +} +InnerClasses: + private static #x= #x of #x; // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck +SourceFile: "TinyFrameworkCallerCheck.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +NestMembers: + com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class + Compiled from "TinyFrameworkClassAnnotations.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + super_class: #x // java/lang/Object + interfaces: 0, fields: 2, methods: 8, attributes: 3 + public int stub; + descriptor: I + flags: (0x0001) ACC_PUBLIC + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub + + public int keep; + descriptor: I + flags: (0x0001) ACC_PUBLIC + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestKeep + + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_1 + x: putfield #x // Field stub:I + x: aload_0 + x: iconst_2 + x: putfield #x // Field keep:I + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub + + public int addOne(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String addOne + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: iload_1 + x: invokevirtual #x // Method addOneInner:(I)I + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 11 6 1 value I + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub + + public int addOneInner(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String addOneInner + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String addOneInner + x: ldc #x // String (I)I + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iload_1 + x: iconst_1 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 26 4 1 value I + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestKeep + + public int addTwo(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String addTwo + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iload_1 + x: iconst_2 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + 11 4 1 value I + + public static int nativeAddThree(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String nativeAddThree + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iload_0 + x: iconst_3 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 4 0 value I + + public java.lang.String unsupportedMethod(); + descriptor: ()Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String unsupportedMethod + x: ldc #x // String ()Ljava/lang/String; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String unsupportedMethod + x: ldc #x // String ()Ljava/lang/String; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Unreachable + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestThrow + + public java.lang.String visibleButUsesUnsupportedMethod(); + descriptor: ()Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations + x: ldc #x // String visibleButUsesUnsupportedMethod + x: ldc #x // String ()Ljava/lang/String; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations; + RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub +} +SourceFile: "TinyFrameworkClassAnnotations.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestStub + x: #x(#x=s#x) + android.hosttest.annotation.HostSideTestClassLoadHook( + value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded" + ) +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class + Compiled from "TinyFrameworkClassClassWideAnnotations.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + super_class: #x // java/lang/Object + interfaces: 0, fields: 3, methods: 9, attributes: 3 + public int stub; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + public int keep; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + public int remove; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_1 + x: putfield #x // Field stub:I + x: aload_0 + x: iconst_2 + x: putfield #x // Field keep:I + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; + + public int addOne(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + x: ldc #x // String addOne + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: iload_1 + x: invokevirtual #x // Method addOneInner:(I)I + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; + 11 6 1 value I + + public int addOneInner(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + x: ldc #x // String addOneInner + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iload_1 + x: iconst_1 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; + 11 4 1 value I + + public void toBeRemoved(java.lang.String); + descriptor: (Ljava/lang/String;)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + x: ldc #x // String toBeRemoved + x: ldc #x // String (Ljava/lang/String;)V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: new #x // class java/lang/RuntimeException + x: dup + x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V + x: athrow + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 8 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; + 11 8 1 foo Ljava/lang/String; + + public int addTwo(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + x: ldc #x // String addTwo + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iload_1 + x: iconst_2 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; + 11 4 1 value I + + public static int nativeAddThree(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + x: ldc #x // String nativeAddThree + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iload_0 + x: iconst_3 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 4 0 value I + + public java.lang.String unsupportedMethod(); + descriptor: ()Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + x: ldc #x // String unsupportedMethod + x: ldc #x // String ()Ljava/lang/String; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String This value shouldn\'t be seen on the host side. + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 3 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; + + public java.lang.String visibleButUsesUnsupportedMethod(); + descriptor: ()Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations + x: ldc #x // String visibleButUsesUnsupportedMethod + x: ldc #x // String ()Ljava/lang/String; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations; +} +SourceFile: "TinyFrameworkClassClassWideAnnotations.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class + Compiled from "TinyFrameworkClassLoadHook.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 3, attributes: 3 + public static final java.util.Set<java.lang.Class<?>> sLoadedClasses; + descriptor: Ljava/util/Set; + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + Signature: #x // Ljava/util/Set<Ljava/lang/Class<*>;>; + + private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook(); + descriptor: ()V + flags: (0x0002) ACC_PRIVATE + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook; + + public static void onClassLoaded(java.lang.Class<?>); + descriptor: (Ljava/lang/Class;)V + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook + x: ldc #x // String onClassLoaded + x: ldc #x // String (Ljava/lang/Class;)V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: getstatic #x // Field sLoadedClasses:Ljava/util/Set; + x: aload_0 + x: invokeinterface #x, 2 // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z + x: pop + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 11 0 clazz Ljava/lang/Class; + LocalVariableTypeTable: + Start Length Slot Name Signature + 11 11 0 clazz Ljava/lang/Class<*>; + Signature: #x // (Ljava/lang/Class<*>;)V + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook + x: ldc #x // String <clinit> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: new #x // class java/util/HashSet + x: dup + x: invokespecial #x // Method java/util/HashSet."<init>":()V + x: putstatic #x // Field sLoadedClasses:Ljava/util/Set; + x: return + LineNumberTable: +} +SourceFile: "TinyFrameworkClassLoadHook.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class + Compiled from "TinyFrameworkClassWithInitializer.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 3 + public static boolean sInitialized; + descriptor: Z + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer; + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer + x: ldc #x // String <clinit> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer + x: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: iconst_1 + x: putstatic #x // Field sInitialized:Z + x: return + LineNumberTable: +} +SourceFile: "TinyFrameworkClassWithInitializer.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x(#x=s#x) + android.hosttest.annotation.HostSideTestClassLoadHook( + value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded" + ) + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class + Compiled from "TinyFrameworkExceptionTester.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 3, attributes: 3 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester; + + public static int testException(); + descriptor: ()I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=1, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester + x: ldc #x // String testException + x: ldc #x // String ()I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: new #x // class java/lang/IllegalStateException + x: dup + x: ldc #x // String Inner exception + x: invokespecial #x // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V + x: athrow + x: astore_0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Outer exception + x: aload_0 + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V + x: athrow + Exception table: + from to target type + 11 21 21 Class java/lang/Exception + StackMapTable: number_of_entries = 1 + frame_type = 85 /* same_locals_1_stack_item */ + stack = [ class java/lang/Exception ] + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 22 11 0 e Ljava/lang/Exception; +} +SourceFile: "TinyFrameworkExceptionTester.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class + Compiled from "TinyFrameworkForTextPolicy.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + super_class: #x // java/lang/Object + interfaces: 0, fields: 2, methods: 8, attributes: 2 + public int stub; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + public int keep; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_1 + x: putfield #x // Field stub:I + x: aload_0 + x: iconst_2 + x: putfield #x // Field keep:I + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy; + + public int addOne(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String addOne + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: iload_1 + x: invokevirtual #x // Method addOneInner:(I)I + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy; + 11 6 1 value I + + public int addOneInner(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String addOneInner + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String addOneInner + x: ldc #x // String (I)I + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iload_1 + x: iconst_1 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy; + 26 4 1 value I + + public int addTwo(int); + descriptor: (I)I + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String addTwo + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iload_1 + x: iconst_2 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 4 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy; + 11 4 1 value I + + public static int nativeAddThree(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String nativeAddThree + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iload_0 + x: iconst_3 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 4 0 value I + + public java.lang.String unsupportedMethod(); + descriptor: ()Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String unsupportedMethod + x: ldc #x // String ()Ljava/lang/String; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String unsupportedMethod + x: ldc #x // String ()Ljava/lang/String; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Unreachable + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + + public java.lang.String visibleButUsesUnsupportedMethod(); + descriptor: ()Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy + x: ldc #x // String visibleButUsesUnsupportedMethod + x: ldc #x // String ()Ljava/lang/String; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokevirtual #x // Method unsupportedMethod:()Ljava/lang/String; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy; +} +SourceFile: "TinyFrameworkForTextPolicy.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class + Compiled from "TinyFrameworkNative.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 6, attributes: 3 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; + + public static int nativeAddTwo(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=1, locals=1, args_size=1 + x: iload_0 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I + x: ireturn + + public static int nativeAddTwo_should_be_like_this(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative + x: ldc #x // String nativeAddTwo_should_be_like_this + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iload_0 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 arg I + + public static long nativeLongPlus(long, long); + descriptor: (JJ)J + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=4, args_size=2 + x: lload_0 + x: lload_2 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J + x: lreturn + + public static long nativeLongPlus_should_be_like_this(long, long); + descriptor: (JJ)J + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=4, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative + x: ldc #x // String nativeLongPlus_should_be_like_this + x: ldc #x // String (JJ)J + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: lload_0 + x: lload_2 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J + x: lreturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 6 0 arg1 J + 11 6 2 arg2 J +} +SourceFile: "TinyFrameworkNative.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub + x: #x(#x=s#x) + android.hosttest.annotation.HostSideTestNativeSubstitutionClass( + value="TinyFrameworkNative_host" + ) +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.class + Compiled from "TinyFrameworkNative_host.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 4, attributes: 3 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String <init> + x: ldc #x // String ()V + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host; + + public static int nativeAddTwo(int); + descriptor: (I)I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeAddTwo + x: ldc #x // String (I)I + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeAddTwo + x: ldc #x // String (I)I + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iload_0 + x: iconst_2 + x: iadd + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 4 0 arg I + + public static long nativeLongPlus(long, long); + descriptor: (JJ)J + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=4, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeLongPlus + x: ldc #x // String (JJ)J + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeLongPlus + x: ldc #x // String (JJ)J + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: lload_0 + x: lload_2 + x: ladd + x: lreturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 4 0 arg1 J + 26 4 2 arg2 J +} +SourceFile: "TinyFrameworkNative_host.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassKeep +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1.class + Compiled from "TinyFrameworkNestedClasses.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer> + minor version: 0 + major version: 61 + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + super_class: #x // java/lang/Object + interfaces: 1, fields: 1, methods: 4, attributes: 6 + final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0; + descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC + + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses); + descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + flags: (0x0000) + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + x: ldc #x // String <init> + x: ldc #x // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: aload_1 + x: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1; + 11 10 1 this$0 Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + + public java.lang.Integer get(); + descriptor: ()Ljava/lang/Integer; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iconst_1 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1; + + public java.lang.Object get(); + descriptor: ()Ljava/lang/Object; + flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1; +} +InnerClasses: + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 +EnclosingMethod: #x.#x // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses +Signature: #x // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>; +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2.class + Compiled from "TinyFrameworkNestedClasses.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer> + minor version: 0 + major version: 61 + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 4, attributes: 6 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2(); + descriptor: ()V + flags: (0x0000) + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2; + + public java.lang.Integer get(); + descriptor: ()Ljava/lang/Integer; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iconst_2 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2; + + public java.lang.Object get(); + descriptor: ()Ljava/lang/Object; + flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2; +} +InnerClasses: + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 +EnclosingMethod: #x.#x // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses +Signature: #x // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>; +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3.class + Compiled from "TinyFrameworkNestedClasses.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer> + minor version: 0 + major version: 61 + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + super_class: #x // java/lang/Object + interfaces: 1, fields: 1, methods: 4, attributes: 6 + final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0; + descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC + + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses); + descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + flags: (0x0000) + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + x: ldc #x // String <init> + x: ldc #x // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: aload_1 + x: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3; + 11 10 1 this$0 Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + + public java.lang.Integer get(); + descriptor: ()Ljava/lang/Integer; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iconst_3 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3; + + public java.lang.Object get(); + descriptor: ()Ljava/lang/Object; + flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3; +} +InnerClasses: + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 +EnclosingMethod: #x.#x // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier +Signature: #x // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>; +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4.class + Compiled from "TinyFrameworkNestedClasses.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer> + minor version: 0 + major version: 61 + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 4, attributes: 6 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4(); + descriptor: ()V + flags: (0x0000) + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4; + + public java.lang.Integer get(); + descriptor: ()Ljava/lang/Integer; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iconst_4 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4; + + public java.lang.Object get(); + descriptor: ()Ljava/lang/Object; + flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4; +} +InnerClasses: + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 +EnclosingMethod: #x.#x // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier_static +Signature: #x // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>; +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class + Compiled from "TinyFrameworkNestedClasses.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 4 + public int value; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass(int); + descriptor: (I)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass + x: ldc #x // String <init> + x: ldc #x // String (I)V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iload_1 + x: putfield #x // Field value:I + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 10 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass; + 11 10 1 x I +} +InnerClasses: + public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class + Compiled from "TinyFrameworkNestedClasses.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass + super_class: #x // java/lang/Object + interfaces: 0, fields: 2, methods: 2, attributes: 5 + public int value; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0; + descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC + + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses); + descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass + x: ldc #x // String <init> + x: ldc #x // String (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: aload_1 + x: putfield #x // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: iconst_5 + x: putfield #x // Field value:I + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 15 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass; + 11 15 1 this$0 Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; +} +InnerClasses: + public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class + Compiled from "TinyFrameworkNestedClasses.java" +class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer> + minor version: 0 + major version: 61 + flags: (0x0020) ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + super_class: #x // java/lang/Object + interfaces: 1, fields: 0, methods: 4, attributes: 6 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1(); + descriptor: ()V + flags: (0x0000) + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1; + + public java.lang.Integer get(); + descriptor: ()Ljava/lang/Integer; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Integer; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: bipush 7 + x: invokestatic #x // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1; + + public java.lang.Object get(); + descriptor: ()Ljava/lang/Object; + flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + x: ldc #x // String get + x: ldc #x // String ()Ljava/lang/Object; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: aload_0 + x: invokevirtual #x // Method get:()Ljava/lang/Integer; + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 5 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1; +} +InnerClasses: + public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 +EnclosingMethod: #x.#x // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass.getSupplier_static +Signature: #x // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>; +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class + Compiled from "TinyFrameworkNestedClasses.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 3, attributes: 5 + public int value; + descriptor: I + flags: (0x0001) ACC_PUBLIC + + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: bipush 6 + x: putfield #x // Field value:I + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 11 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass; + + public static java.util.function.Supplier<java.lang.Integer> getSupplier_static(); + descriptor: ()Ljava/util/function/Supplier; + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass + x: ldc #x // String getSupplier_static + x: ldc #x // String ()Ljava/util/function/Supplier; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + x: dup + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V + x: areturn + LineNumberTable: + Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; +} +InnerClasses: + public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class + Compiled from "TinyFrameworkNestedClasses.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass + super_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass + interfaces: 0, fields: 0, methods: 2, attributes: 4 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int); + descriptor: (I)V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass + x: ldc #x // String <init> + x: ldc #x // String (I)V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: iload_1 + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 6 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass; + 11 6 1 x I +} +InnerClasses: + public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + public static #x= #x of #x; // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses +## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class + Compiled from "TinyFrameworkNestedClasses.java" +public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + super_class: #x // java/lang/Object + interfaces: 0, fields: 2, methods: 4, attributes: 5 + public final java.util.function.Supplier<java.lang.Integer> mSupplier; + descriptor: Ljava/util/function/Supplier; + flags: (0x0011) ACC_PUBLIC, ACC_FINAL + Signature: #x // Ljava/util/function/Supplier<Ljava/lang/Integer;>; + + public static final java.util.function.Supplier<java.lang.Integer> sSupplier; + descriptor: Ljava/util/function/Supplier; + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + Signature: #x // Ljava/util/function/Supplier<Ljava/lang/Integer;>; + + public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: aload_0 + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + x: dup + x: aload_0 + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + x: putfield #x // Field mSupplier:Ljava/util/function/Supplier; + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 17 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + + public java.util.function.Supplier<java.lang.Integer> getSupplier(); + descriptor: ()Ljava/util/function/Supplier; + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + x: ldc #x // String getSupplier + x: ldc #x // String ()Ljava/util/function/Supplier; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + x: dup + x: aload_0 + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V + x: areturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 9 0 this Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses; + Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; + + public static java.util.function.Supplier<java.lang.Integer> getSupplier_static(); + descriptor: ()Ljava/util/function/Supplier; + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + x: ldc #x // String getSupplier_static + x: ldc #x // String ()Ljava/util/function/Supplier; + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + x: dup + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V + x: areturn + LineNumberTable: + Signature: #x // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>; + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + x: ldc #x // String <clinit> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: new #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + x: dup + x: invokespecial #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V + x: putstatic #x // Field sSupplier:Ljava/util/function/Supplier; + x: return + LineNumberTable: +} +InnerClasses: + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + public static #x= #x of #x; // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + public static #x= #x of #x; // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + public static #x= #x of #x; // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + public #x= #x of #x; // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses + #x; // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 +SourceFile: "TinyFrameworkNestedClasses.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass +RuntimeInvisibleAnnotations: + x: #x() + android.hosttest.annotation.HostSideTestWholeClassStub +NestMembers: + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1 + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4 + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3 + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2 + com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1 diff --git a/tools/hoststubgen/scripts/dump-jar b/tools/hoststubgen/scripts/dump-jar index 93729fb22caa..992665ed58ee 100755 --- a/tools/hoststubgen/scripts/dump-jar +++ b/tools/hoststubgen/scripts/dump-jar @@ -93,6 +93,7 @@ filter_output() { if (( $simple )) ; then # For "simple output" mode, # - Normalize the constant numbers (replace with "#x") + # - Normalize byte code offsets and other similar numbers. (e.g. "0:" -> "x:") # - Remove the constant pool # - Remove the line number table # - Some other transient lines @@ -100,6 +101,7 @@ filter_output() { # `/PATTERN-1/,/PATTERN-1/{//!d}` is a trick to delete lines between two patterns, without # the start and the end lines. sed -e 's/#[0-9][0-9]*/#x/g' \ + -e 's/^\( *\)[0-9][0-9]*:/\1x:/' \ -e '/^Constant pool:/,/^[^ ]/{//!d}' \ -e '/^ *line *[0-9][0-9]*: *[0-9][0-9]*$/d' \ -e '/SHA-256 checksum/d' \ diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh index 6bc0ddb1a8dc..7600942c99e6 100755 --- a/tools/hoststubgen/scripts/run-all-tests.sh +++ b/tools/hoststubgen/scripts/run-all-tests.sh @@ -34,6 +34,7 @@ run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh run ./hoststubgen/test-framework/run-test-without-atest.sh run ./hoststubgen/test-tiny-framework/run-test-manually.sh +run atest tiny-framework-dump-test run ./scripts/build-framework-hostside-jars-and-extract.sh # This script is already broken on goog/master diff --git a/tools/lint/fix/soong_lint_fix.py b/tools/lint/fix/soong_lint_fix.py index 4a2e37e51d71..2e82beb24b56 100644 --- a/tools/lint/fix/soong_lint_fix.py +++ b/tools/lint/fix/soong_lint_fix.py @@ -14,6 +14,7 @@ import argparse import json +import functools import os import shutil import subprocess @@ -28,6 +29,7 @@ SOONG_UI = "build/soong/soong_ui.bash" PATH_PREFIX = "out/soong/.intermediates" PATH_SUFFIX = "android_common/lint" FIX_ZIP = "suggested-fixes.zip" +MODULE_JAVA_DEPS = "out/soong/module_bp_java_deps.json" class SoongModule: @@ -49,11 +51,26 @@ class SoongModule: print(f"Found module {partial_path}/{self._name}.") self._path = f"{PATH_PREFIX}/{partial_path}/{self._name}/{PATH_SUFFIX}" + def find_java_deps(self, module_java_deps): + """Finds the dependencies of a Java module in the loaded module_bp_java_deps.json. + + Returns: + A list of module names. + """ + if self._name not in module_java_deps: + raise Exception(f"Module {self._name} not found!") + + return module_java_deps[self._name]["dependencies"] + @property def name(self): return self._name @property + def path(self): + return self._path + + @property def lint_report(self): return f"{self._path}/lint-report.txt" @@ -62,52 +79,25 @@ class SoongModule: return f"{self._path}/{FIX_ZIP}" -class SoongLintFix: +class SoongLintWrapper: """ - This class creates a command line tool that will apply lint fixes to the - platform via the necessary combination of soong and shell commands. + This class wraps the necessary calls to Soong and/or shell commands to lint + platform modules and apply suggested fixes if desired. - It breaks up these operations into a few "private" methods that are - intentionally exposed so experimental code can tweak behavior. - - The entry point, `run`, will apply lint fixes using the intermediate - `suggested-fixes` directory that soong creates during its invocation of - lint. - - Basic usage: - ``` - from soong_lint_fix import SoongLintFix - - opts = SoongLintFixOptions() - opts.parse_args(sys.argv) - SoongLintFix(opts).run() - ``` + It breaks up these operations into a few methods that are available to + sub-classes (see SoongLintFix for an example). """ - def __init__(self, opts): - self._opts = opts + def __init__(self, check=None, lint_module=None): + self._check = check + self._lint_module = lint_module self._kwargs = None - self._modules = [] - - def run(self): - """ - Run the script - """ - self._setup() - self._find_modules() - self._lint() - - if not self._opts.no_fix: - self._fix() - - if self._opts.print: - self._print() def _setup(self): env = os.environ.copy() - if self._opts.check: - env["ANDROID_LINT_CHECK"] = self._opts.check - if self._opts.lint_module: - env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._opts.lint_module + if self._check: + env["ANDROID_LINT_CHECK"] = self._check + if self._lint_module: + env["ANDROID_LINT_CHECK_EXTRA_MODULES"] = self._lint_module self._kwargs = { "env": env, @@ -117,7 +107,10 @@ class SoongLintFix: os.chdir(ANDROID_BUILD_TOP) - print("Refreshing soong modules...") + @functools.cached_property + def _module_info(self): + """Returns the JSON content of module-info.json.""" + print("Refreshing Soong modules...") try: os.mkdir(ANDROID_PRODUCT_OUT) except OSError: @@ -125,19 +118,54 @@ class SoongLintFix: subprocess.call(f"{SOONG_UI} --make-mode {PRODUCT_OUT}/module-info.json", **self._kwargs) print("done.") - - def _find_modules(self): with open(f"{ANDROID_PRODUCT_OUT}/module-info.json") as f: - module_info = json.load(f) + return json.load(f) - for module_name in self._opts.modules: - module = SoongModule(module_name) - module.find(module_info) - self._modules.append(module) + def _find_module(self, module_name): + """Returns a SoongModule from a module name. - def _lint(self): + Ensures that the module is known to Soong. + """ + module = SoongModule(module_name) + module.find(self._module_info) + return module + + def _find_modules(self, module_names): + modules = [] + for module_name in module_names: + modules.append(self._find_module(module_name)) + return modules + + @functools.cached_property + def _module_java_deps(self): + """Returns the JSON content of module_bp_java_deps.json.""" + print("Refreshing Soong Java deps...") + subprocess.call(f"{SOONG_UI} --make-mode {MODULE_JAVA_DEPS}", **self._kwargs) + print("done.") + + with open(f"{MODULE_JAVA_DEPS}") as f: + return json.load(f) + + def _find_module_java_deps(self, module): + """Returns a list a dependencies for a module. + + Args: + module: A SoongModule. + + Returns: + A list of SoongModule. + """ + deps = [] + dep_names = module.find_java_deps(self._module_java_deps) + for dep_name in dep_names: + dep = SoongModule(dep_name) + dep.find(self._module_info) + deps.append(dep) + return deps + + def _lint(self, modules): print("Cleaning up any old lint results...") - for module in self._modules: + for module in modules: try: os.remove(f"{module.lint_report}") os.remove(f"{module.suggested_fixes}") @@ -145,13 +173,13 @@ class SoongLintFix: pass print("done.") - target = " ".join([ module.lint_report for module in self._modules ]) + target = " ".join([ module.lint_report for module in modules ]) print(f"Generating {target}") subprocess.call(f"{SOONG_UI} --make-mode {target}", **self._kwargs) print("done.") - def _fix(self): - for module in self._modules: + def _fix(self, modules): + for module in modules: print(f"Copying suggested fixes for {module.name} to the tree...") with zipfile.ZipFile(f"{module.suggested_fixes}") as zip: for name in zip.namelist(): @@ -161,13 +189,40 @@ class SoongLintFix: shutil.copyfileobj(src, dst) print("done.") - def _print(self): - for module in self._modules: + def _print(self, modules): + for module in modules: print(f"### lint-report.txt {module.name} ###", end="\n\n") with open(module.lint_report, "r") as f: print(f.read()) +class SoongLintFix(SoongLintWrapper): + """ + Basic usage: + ``` + from soong_lint_fix import SoongLintFix + + opts = SoongLintFixOptions() + opts.parse_args() + SoongLintFix(opts).run() + ``` + """ + def __init__(self, opts): + super().__init__(check=opts.check, lint_module=opts.lint_module) + self._opts = opts + + def run(self): + self._setup() + modules = self._find_modules(self._opts.modules) + self._lint(modules) + + if not self._opts.no_fix: + self._fix(modules) + + if self._opts.print: + self._print(modules) + + class SoongLintFixOptions: """Options for SoongLintFix""" diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt index cbbf91b49ded..758de4dfccf8 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt @@ -131,7 +131,7 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { priority = 6, severity = Severity.ERROR, implementation = Implementation( - EnforcePermissionDetector::class.java, + EnforcePermissionHelperDetector::class.java, EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) ) ) diff --git a/tools/lint/utils/enforce_permission_counter.py b/tools/lint/utils/enforce_permission_counter.py index b5c2ffe9885e..a4c00f79b63f 100644 --- a/tools/lint/utils/enforce_permission_counter.py +++ b/tools/lint/utils/enforce_permission_counter.py @@ -16,57 +16,38 @@ import re import soong_lint_fix -# Libraries that constitute system_server. -# It is non-trivial to keep in sync with services/Android.bp as some -# module are post-processed (e.g, services.core). -TARGETS = [ - "services.core.unboosted", - "services.accessibility", - "services.appprediction", - "services.appwidget", - "services.autofill", - "services.backup", - "services.companion", - "services.contentcapture", - "services.contentsuggestions", - "services.coverage", - "services.devicepolicy", - "services.midi", - "services.musicsearch", - "services.net", - "services.people", - "services.print", - "services.profcollect", - "services.restrictions", - "services.searchui", - "services.smartspace", - "services.systemcaptions", - "services.translation", - "services.texttospeech", - "services.usage", - "services.usb", - "services.voiceinteraction", - "services.wallpapereffectsgeneration", - "services.wifi", -] +CHECK = "AnnotatedAidlCounter" +LINT_MODULE = "AndroidUtilsLintChecker" - -class EnforcePermissionMigratedCounter: +class EnforcePermissionMigratedCounter(soong_lint_fix.SoongLintWrapper): """Wrapper around lint_fix to count the number of AIDL methods annotated.""" - def run(self): - opts = soong_lint_fix.SoongLintFixOptions() - opts.check = "AnnotatedAidlCounter" - opts.lint_module = "AndroidUtilsLintChecker" - opts.no_fix = True - opts.modules = TARGETS - self.linter = soong_lint_fix.SoongLintFix(opts) - self.linter.run() - self.parse_lint_reports() + def __init__(self): + super().__init__(check=CHECK, lint_module=LINT_MODULE) + + def run(self): + self._setup() + + # Analyze the dependencies of the "services" module and the module + # "services.core.unboosted". + service_module = self._find_module("services") + dep_modules = self._find_module_java_deps(service_module) + \ + [self._find_module("services.core.unboosted")] + + # Skip dependencies that are not services. Skip the "services.core" + # module which is analyzed via "services.core.unboosted". + modules = [] + for module in dep_modules: + if "frameworks/base/services" not in module.path: + continue + if module.name == "services.core": + continue + modules.append(module) + + self._lint(modules) - def parse_lint_reports(self): counts = { "unannotated": 0, "enforced": 0, "notRequired": 0 } - for module in self.linter._modules: + for module in modules: with open(module.lint_report, "r") as f: content = f.read() keys = dict(re.findall(r'(\w+)=(\d+)', content)) |