diff options
447 files changed, 14693 insertions, 3515 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 08a09e1b1a73..b1f587e45a6d 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -334,7 +334,7 @@ java_aconfig_library { aconfig_declarations { name: "android.app.flags-aconfig", package: "android.app", - srcs: ["core/java/android/app/activity_manager.aconfig"], + srcs: ["core/java/android/app/*.aconfig"], } java_aconfig_library { 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/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 955858b9273f..7fd25b2cb5fb 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -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); @@ -42905,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"; @@ -43073,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"; @@ -54456,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; @@ -55029,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/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 1d88e002b4bd..8748e6939ccd 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3223,14 +3223,14 @@ package android.companion.virtual { 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("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent(); method public int getLockState(); @@ -3238,15 +3238,15 @@ package android.companion.virtual { 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 @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 @@ -3257,12 +3257,12 @@ 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("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); @@ -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 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/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/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/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/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/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..08d32c3b5cbc 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -13,3 +13,10 @@ flag { description: "Feature flag to enable the archiving feature." bug: "278553670" } + +flag { + name: "stay_stopped" + namespace: "backstage_power" + description: "Feature flag to improve stopped state enforcement" + bug: "296644915" +} 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/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/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/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/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/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/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/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/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/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java index 7a87c3a82e43..d503904c2e3c 100644 --- a/core/java/com/android/internal/display/BrightnessSynchronizer.java +++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java @@ -16,11 +16,9 @@ package com.android.internal.display; -import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; -import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.net.Uri; @@ -56,7 +54,8 @@ public class BrightnessSynchronizer { private static final int MSG_RUN_UPDATE = 1; // The tolerance within which we consider brightness values approximately equal to eachother. - public static final float EPSILON = 0.0001f; + // This value is approximately 1/3 of the smallest possible brightness value. + public static final float EPSILON = 0.001f; private static int sBrightnessUpdateCount = 1; @@ -285,74 +284,6 @@ public class BrightnessSynchronizer { } /** - * Converts between the int brightness setting and the float brightness system. The int - * brightness setting is between 0-255 and matches the brightness slider - e.g. 128 is 50% on - * the slider. Accounts for special values such as OFF and invalid values. Accounts for - * brightness limits; the maximum value here represents the max value allowed on the slider. - */ - @VisibleForTesting - @SuppressLint("AndroidFrameworkRequiresPermission") - public float brightnessIntSettingToFloat(int brightnessInt) { - if (brightnessInt == PowerManager.BRIGHTNESS_OFF) { - return PowerManager.BRIGHTNESS_OFF_FLOAT; - } else if (brightnessInt == PowerManager.BRIGHTNESS_INVALID) { - return PowerManager.BRIGHTNESS_INVALID_FLOAT; - } else { - final float minInt = PowerManager.BRIGHTNESS_OFF + 1; - final float maxInt = PowerManager.BRIGHTNESS_ON; - - // Normalize to the range [0, 1] - float userPerceptionBrightness = MathUtils.norm(minInt, maxInt, brightnessInt); - - // Convert from user-perception to linear scale - float linearBrightness = BrightnessUtils.convertGammaToLinear(userPerceptionBrightness); - - // Interpolate to the range [0, currentlyAllowedMax] - final Display display = mContext.getDisplay(); - if (display == null) { - return PowerManager.BRIGHTNESS_INVALID_FLOAT; - } - final BrightnessInfo info = display.getBrightnessInfo(); - return MathUtils.lerp(info.brightnessMinimum, info.brightnessMaximum, linearBrightness); - } - } - - /** - * Translates specified value from the float brightness system to the setting int brightness - * system. The value returned is between 0-255 and matches the brightness slider - e.g. 128 is - * 50% on the slider. Accounts for special values such as OFF and invalid values. Accounts for - * brightness limits; the maximum value here represents the max value currently allowed on - * the slider. - */ - @VisibleForTesting - @SuppressLint("AndroidFrameworkRequiresPermission") - public int brightnessFloatToIntSetting(float brightnessFloat) { - if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) { - return PowerManager.BRIGHTNESS_OFF; - } else if (Float.isNaN(brightnessFloat)) { - return PowerManager.BRIGHTNESS_INVALID; - } else { - // Normalize to the range [0, 1] - final Display display = mContext.getDisplay(); - if (display == null) { - return PowerManager.BRIGHTNESS_INVALID; - } - final BrightnessInfo info = display.getBrightnessInfo(); - float linearBrightness = - MathUtils.norm(info.brightnessMinimum, info.brightnessMaximum, brightnessFloat); - - // Convert from linear to user-perception scale - float userPerceptionBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness); - - // Interpolate to the range [0, 255] - final float minInt = PowerManager.BRIGHTNESS_OFF + 1; - final float maxInt = PowerManager.BRIGHTNESS_ON; - float intBrightness = MathUtils.lerp(minInt, maxInt, userPerceptionBrightness); - return Math.round(intBrightness); - } - } - - /** * Encapsulates a brightness change event and contains logic for synchronizing the appropriate * settings for the specified brightness change. */ @@ -490,14 +421,14 @@ public class BrightnessSynchronizer { if (mSourceType == TYPE_INT) { return (int) mBrightness; } - return brightnessFloatToIntSetting(mBrightness); + return brightnessFloatToInt(mBrightness); } private float getBrightnessAsFloat() { if (mSourceType == TYPE_FLOAT) { return mBrightness; } - return brightnessIntSettingToFloat((int) mBrightness); + return brightnessIntToFloat((int) mBrightness); } private String toStringLabel(int type, float brightness) { 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 7eeac29e21f7..4d8eeac6d5ab 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -316,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/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 9ed41553a4b4..3795fc8594ae 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -102,6 +102,7 @@ cc_library_shared_for_libandroid_runtime { 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/res/res/values/config.xml b/core/res/res/values/config.xml index c00a776fc3da..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.media.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> 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/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/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/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/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/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 664fbb22b13b..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 @@ -43,7 +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.TestRemoteTransition +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 @@ -180,7 +180,8 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java) + 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) @@ -198,7 +199,8 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java) + 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) @@ -216,7 +218,8 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java) + 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) @@ -230,7 +233,8 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java) + val wct = + getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java) assertThat(wct.hierarchyOps).hasSize(1) wct.assertReorderAt(index = 0, homeTask) } @@ -246,7 +250,8 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition())) - val wct = getLatestWct(type = TRANSIT_OPEN, handlerClass = OneShotRemoteHandler::class.java) + 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) 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 d542139ff9fd..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 @@ -62,9 +62,7 @@ import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.TestRemoteTransition; 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; @@ -75,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; 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/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java index 0df42b3cc5e4..39ab23877c68 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRemoteTransition.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.wm.shell; +package com.android.wm.shell.transition; import android.os.IBinder; import android.os.RemoteException; 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/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/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/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/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 104f3d2b2621..ee05f2d9101b 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -363,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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index b5b873c8231f..9bfc4be0f30d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -634,7 +634,7 @@ <!-- started from MediaProjectionManager --> <activity - android:name=".media.MediaProjectionPermissionActivity" + android:name=".mediaprojection.permission.MediaProjectionPermissionActivity" android:exported="true" android:theme="@style/Theme.SystemUI.MediaProjectionAlertDialog" android:finishOnCloseSystemDialogs="true" @@ -643,7 +643,7 @@ android:visibleToInstantApps="true"/> <activity - android:name=".media.MediaProjectionAppSelectorActivity" + android:name=".mediaprojection.appselector.MediaProjectionAppSelectorActivity" android:theme="@style/Theme.SystemUI.MediaProjectionAppSelector" android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true" diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 5c2f979ac639..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" 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/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/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/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-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/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/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/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 b2287d876a48..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; @@ -420,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; @@ -457,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); @@ -487,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; @@ -519,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, @@ -1081,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/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index f3a463ba44a4..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()) ) 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 3b70555ad32a..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 @@ -303,6 +296,11 @@ object Flags { @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 @@ -771,6 +769,10 @@ object Flags { // 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/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/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/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/media/MediaProjectionCaptureTarget.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt index fbf9294a0a37..11d0be5fc8bf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionCaptureTarget.kt @@ -14,20 +14,17 @@ * limitations under the License. */ -package com.android.systemui.media +package com.android.systemui.mediaprojection import android.os.IBinder import android.os.Parcel import android.os.Parcelable /** - * Class that represents an area that should be captured. - * Currently it has only a launch cookie that represents a task but - * we potentially could add more identifiers e.g. for a pair of tasks. + * Class that represents an area that should be captured. Currently it has only a launch cookie that + * represents a task but we potentially could add more identifiers e.g. for a pair of tasks. */ -data class MediaProjectionCaptureTarget( - val launchCookie: IBinder? -): Parcelable { +data class MediaProjectionCaptureTarget(val launchCookie: IBinder?) : Parcelable { constructor(parcel: Parcel) : this(parcel.readStrongBinder()) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt index 9e616e2355e2..f1cade7512e2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionServiceHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.media +package com.android.systemui.mediaprojection import android.content.Context import android.media.projection.IMediaProjection diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt index 88bc064c60f7..b5d3e913cadb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.media +package com.android.systemui.mediaprojection.appselector import android.app.ActivityOptions import android.content.Intent @@ -46,13 +46,11 @@ import com.android.internal.app.chooser.TargetInfo import com.android.internal.widget.RecyclerView import com.android.internal.widget.RecyclerViewAccessibilityDelegate import com.android.internal.widget.ResolverDrawerLayout -import com.android.systemui.res.R -import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent -import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController -import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler -import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorView +import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget +import com.android.systemui.mediaprojection.MediaProjectionServiceHelper import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.AsyncActivityLauncher import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index 33d9cc36c9b0..72aea040ba05 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -23,8 +23,6 @@ import android.os.UserHandle import androidx.lifecycle.DefaultLifecycleObserver import com.android.launcher3.icons.IconFactory import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.media.MediaProjectionAppSelectorActivity -import com.android.systemui.media.MediaProjectionPermissionActivity import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader import com.android.systemui.mediaprojection.appselector.data.AppIconLoader @@ -37,6 +35,7 @@ import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRece import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider import com.android.systemui.mediaprojection.devicepolicy.MediaProjectionDevicePolicyModule import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile +import com.android.systemui.mediaprojection.permission.MediaProjectionPermissionActivity import com.android.systemui.statusbar.phone.ConfigurationControllerImpl import com.android.systemui.statusbar.policy.ConfigurationController import dagger.Binds diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt index 64006fe4c265..8b437c322549 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseScreenSharePermissionDialog.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.screenrecord +package com.android.systemui.mediaprojection.permission import android.content.Context import android.os.Bundle diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index 4de6278400b3..2b56d0cf9f83 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.media; +package com.android.systemui.mediaprojection.permission; import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT; import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT; @@ -22,8 +22,8 @@ import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL; import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; -import static com.android.systemui.screenrecord.ScreenShareOptionKt.ENTIRE_SCREEN; -import static com.android.systemui.screenrecord.ScreenShareOptionKt.SINGLE_APP; +import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.ENTIRE_SCREEN; +import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP; import android.annotation.Nullable; import android.app.Activity; @@ -51,13 +51,13 @@ import android.text.style.StyleSpan; import android.util.Log; import android.view.Window; -import com.android.systemui.res.R; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionServiceHelper; +import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver; import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; -import com.android.systemui.screenrecord.MediaProjectionPermissionDialog; -import com.android.systemui.screenrecord.ScreenShareOption; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.Utils; diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt index 47e28d86e01d..2f10ad3e6486 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.screenrecord +package com.android.systemui.mediaprojection.permission import android.content.Context import android.media.projection.MediaProjectionConfig diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt index ebf0dd28fbf4..37e8d9f26ee3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.screenrecord +package com.android.systemui.mediaprojection.permission import androidx.annotation.IntDef import androidx.annotation.StringRes 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/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/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index 92490e8fbd43..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,14 +42,14 @@ 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 */ @@ -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/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/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt index 21aaa94f9e10..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 @@ -32,6 +32,8 @@ 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 @@ -61,6 +63,11 @@ abstract class QSPipelineModule { ): InstalledTilesComponentRepository @Binds + abstract fun provideDisabledByPolicyInteractor( + impl: DisabledByPolicyInteractorImpl + ): DisabledByPolicyInteractor + + @Binds @IntoMap @ClassKey(QSPipelineCoreStartable::class) abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable 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 00c23582f726..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 @@ -41,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 @@ -131,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, @@ -139,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>> = @@ -333,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/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/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/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 7cdb6553c47e..3501b6bc045e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -41,10 +41,10 @@ import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.LongRunning; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.media.MediaProjectionCaptureTarget; +import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget; +import com.android.systemui.res.R; import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java index b80a01212ca0..3aab3bf62809 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java @@ -51,7 +51,7 @@ import android.util.Size; import android.view.Surface; import android.view.WindowManager; -import com.android.systemui.media.MediaProjectionCaptureTarget; +import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget; import java.io.Closeable; import java.io.File; diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index f0ce8a465d0d..f2e94e94757f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -18,7 +18,7 @@ package com.android.systemui.screenrecord; import static android.app.Activity.RESULT_OK; -import static com.android.systemui.media.MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET; +import static com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET; import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL; import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC; import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL; @@ -41,8 +41,8 @@ import android.widget.TextView; import androidx.annotation.Nullable; +import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget; import com.android.systemui.res.R; -import com.android.systemui.media.MediaProjectionCaptureTarget; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.phone.SystemUIDialog; diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index b5b7043e37a0..a1d5d98ba9e9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -32,10 +32,14 @@ import android.widget.ArrayAdapter import android.widget.Spinner import android.widget.Switch import androidx.annotation.LayoutRes -import com.android.systemui.res.R -import com.android.systemui.media.MediaProjectionAppSelectorActivity -import com.android.systemui.media.MediaProjectionCaptureTarget +import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget +import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity +import com.android.systemui.mediaprojection.permission.BaseScreenSharePermissionDialog +import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN +import com.android.systemui.mediaprojection.permission.SINGLE_APP +import com.android.systemui.mediaprojection.permission.ScreenShareOption import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider /** Dialog to select screen recording options */ @@ -58,6 +62,7 @@ class ScreenRecordPermissionDialog( private lateinit var tapsView: View private lateinit var audioSwitch: Switch private lateinit var options: Spinner + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setDialogTitle(R.string.screenrecord_permission_dialog_title) @@ -177,6 +182,7 @@ class ScreenRecordPermissionDialog( ) private const val DELAY_MS: Long = 3000 private const val INTERVAL_MS: Long = 1000 + private fun createOptionList(): List<ScreenShareOption> { return listOf( ScreenShareOption( 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/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 93bb4356a1d6..664103fa05d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -442,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, @@ -1314,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, @@ -1326,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(); } @@ -1772,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/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/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/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/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 f7ff39c8869b..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; @@ -109,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; @@ -149,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, @@ -187,7 +180,6 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation); mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect( mVibratorOptional, resources); - mSystemBarAttributesListener = systemBarAttributesListener; mActivityStarter = activityStarter; } @@ -457,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 05bedede01a3..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,9 +21,6 @@ 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; @@ -33,12 +30,6 @@ 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; @@ -92,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; @@ -208,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; @@ -222,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; @@ -329,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); @@ -464,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; @@ -497,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 @@ -554,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; @@ -714,7 +684,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { BrightnessSliderController.Factory brightnessSliderFactory, ScreenOffAnimationController screenOffAnimationController, WallpaperController wallpaperController, - OngoingCallController ongoingCallController, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, LockscreenShadeTransitionController lockscreenShadeTransitionController, FeatureFlags featureFlags, @@ -820,7 +789,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationIconAreaController = notificationIconAreaController; mBrightnessSliderFactory = brightnessSliderFactory; mWallpaperController = wallpaperController; - mOngoingCallController = ongoingCallController; mStatusBarSignalPolicy = statusBarSignalPolicy; mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mFeatureFlags = featureFlags; @@ -854,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); @@ -1192,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); @@ -1712,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 @@ -1785,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; @@ -1794,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() { @@ -1850,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/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/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/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/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 92c8a395a696..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) 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/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/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/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/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 index 5630b9d3b292..2e6b50b637dd 100644 --- 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 @@ -1,6 +1,6 @@ package com.android.systemui.qs.pipeline.domain.interactor -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 @@ -21,7 +21,7 @@ import org.junit.runner.RunWith import org.mockito.MockitoAnnotations @RoboPilotTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class RestoreReconciliationInteractorTest : SysuiTestCase() { 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/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/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java index 59b595393749..a2aed988a423 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java @@ -41,7 +41,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; -import com.android.systemui.media.MediaProjectionCaptureTarget; +import com.android.systemui.mediaprojection.MediaProjectionCaptureTarget; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.SysuiStatusBarStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt index d470d24489af..3ae1f35b8134 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt @@ -22,11 +22,13 @@ import android.testing.TestableLooper import android.view.View import android.widget.Spinner import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN +import com.android.systemui.mediaprojection.permission.SINGLE_APP import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat @@ -35,8 +37,8 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) 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/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/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 0b171b2a23e4..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; @@ -535,7 +533,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mBrightnessSliderFactory, mScreenOffAnimationController, mWallpaperController, - mOngoingCallController, mStatusBarHideIconsForBouncerManager, mLockscreenTransitionController, mFeatureFlags, 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/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/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/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 65b8b555cf2b..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,15 +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; @@ -176,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) @@ -299,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; @@ -335,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), @@ -352,7 +383,9 @@ public class BubblesTest extends SysuiTestCase { mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager, - mShadeWindowLogger); + () -> mShadeInteractor, + mShadeWindowLogger + ); mNotificationShadeWindowController.fetchWindowRootView(); mNotificationShadeWindowController.attach(); 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/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/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/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/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/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index b56b47f9c727..9dd0dca47f0e 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -121,7 +121,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController @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; 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 a2e4d2cc6929..8a2aa616f8e6 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -50,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; @@ -105,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; @@ -114,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 { @@ -178,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; @@ -270,7 +273,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub soundEffectListener, runningAppsChangedCallback, params, - DisplayManagerGlobal.getInstance()); + DisplayManagerGlobal.getInstance(), + Flags.virtualCamera() ? new VirtualCameraController(context) : null); } @VisibleForTesting @@ -289,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(); @@ -324,6 +329,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } else { mPermissionDialogComponent = null; } + mVirtualCameraController = virtualCameraController; try { token.linkToDeath(this, 0); } catch (RemoteException e) { @@ -564,6 +570,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } finally { Binder.restoreCallingIdentity(ident); } + if (mVirtualCameraController != null) { + mVirtualCameraController.close(); + } } @Override @@ -920,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") 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/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/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/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index debf828abf0a..99a5398aa7ee 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -33,7 +33,6 @@ import android.util.Spline; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; -import com.android.internal.display.BrightnessUtils; import com.android.internal.util.Preconditions; import com.android.server.display.utils.Plog; import com.android.server.display.whitebalance.DisplayWhiteBalanceController; diff --git a/core/java/com/android/internal/display/BrightnessUtils.java b/services/core/java/com/android/server/display/BrightnessUtils.java index 82b506bed80b..84fa0cccbd10 100644 --- a/core/java/com/android/internal/display/BrightnessUtils.java +++ b/services/core/java/com/android/server/display/BrightnessUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.internal.display; +package com.android.server.display; import android.util.MathUtils; diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java index e38c2c58f453..5ba042c51a45 100644 --- a/services/core/java/com/android/server/display/RampAnimator.java +++ b/services/core/java/com/android/server/display/RampAnimator.java @@ -20,8 +20,6 @@ import android.animation.ValueAnimator; import android.util.FloatProperty; import android.view.Choreographer; -import com.android.internal.display.BrightnessUtils; - /** * A custom animator that progressively updates a property value at * a given variable rate until it reaches a particular target value. 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 e46edd9ab07a..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 @@ -24,6 +24,7 @@ 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; @@ -178,8 +179,12 @@ 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); 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/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/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/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/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/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/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/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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index dfc9b8b030f2..097656cac7f7 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -198,7 +198,6 @@ import com.android.internal.accessibility.util.AccessibilityStatsLogUtils; import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AssistUtils; -import com.android.internal.display.BrightnessUtils; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; @@ -218,6 +217,7 @@ import com.android.server.GestureLauncherService; import com.android.server.LocalServices; import com.android.server.SystemServiceManager; import com.android.server.UiThread; +import com.android.server.display.BrightnessUtils; import com.android.server.input.InputManagerInternal; import com.android.server.input.KeyboardMetricsCollector; import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent; 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/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 88eaafaee370..3fd832376d2b 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -2115,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/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 184de58c8446..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; @@ -301,12 +302,9 @@ class RecentTasks { } // Always update the reordering time when this is called to ensure that the timeout - // is reset. Extend this duration when running in tests. - final long timeout = ActivityManager.isRunningInUserTestHarness() - ? mFreezeTaskListTimeoutMs * 10 - : mFreezeTaskListTimeoutMs; + // is reset mService.mH.removeCallbacks(mResetFreezeTaskListOnTimeoutRunnable); - mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, timeout); + mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, mFreezeTaskListTimeoutMs); } /** @@ -506,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 3faf8b9ff8a8..3db7765963f7 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -9646,15 +9646,4 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); } } - - /** - * Resets the spatial ordering of recents for testing purposes. - */ - void resetFreezeRecentTaskListReordering() { - if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS, - "resetFreezeRecentTaskListReordering()")) { - throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission"); - } - mAtmService.getRecentTasks().resetFreezeTaskListReorderingOnTimeout(); - } } diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index fa9a65fda853..8fad9509af44 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -142,8 +142,6 @@ public class WindowManagerShellCommand extends ShellCommand { return runReset(pw); case "disable-blur": return runSetBlurDisabled(pw); - case "reset-freeze-recent-tasks": - return runResetFreezeRecentTaskListReordering(pw); case "shell": return runWmShellCommand(pw); default: @@ -254,11 +252,6 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } - private int runResetFreezeRecentTaskListReordering(PrintWriter pw) throws RemoteException { - mInternal.resetFreezeRecentTaskListReordering(); - return 0; - } - private void printInitialDisplayDensity(PrintWriter pw , int displayId) { try { final int initialDensity = mInterface.getInitialDisplayDensity(displayId); @@ -1499,8 +1492,6 @@ public class WindowManagerShellCommand extends ShellCommand { printLetterboxHelp(pw); printMultiWindowConfigHelp(pw); - pw.println(" reset-freeze-recent-tasks"); - pw.println(" Resets the spatial ordering of the recent tasks list"); pw.println(" reset [-d DISPLAY_ID]"); pw.println(" Reset all override settings."); if (!IS_USER) { 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/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/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/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java index 7a4327cc0598..2fd6e5fb6892 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java @@ -19,7 +19,6 @@ package com.android.server.display; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.isA; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -28,18 +27,15 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.ContextWrapper; import android.database.ContentObserver; -import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.net.Uri; import android.os.Handler; -import android.os.PowerManager; import android.os.UserHandle; import android.os.test.TestLooper; import android.provider.Settings; import android.test.mock.MockContentResolver; import android.view.Display; -import android.view.DisplayAdjustments; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; @@ -63,7 +59,6 @@ public class BrightnessSynchronizerTest { private static final float EPSILON = 0.00001f; private static final Uri BRIGHTNESS_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS); - private static final float BRIGHTNESS_MAX = 0.6f; private Context mContext; private MockContentResolver mContentResolverSpy; @@ -71,7 +66,6 @@ public class BrightnessSynchronizerTest { private DisplayListener mDisplayListener; private ContentObserver mContentObserver; private TestLooper mTestLooper; - private BrightnessSynchronizer mSynchronizer; @Mock private DisplayManager mDisplayManagerMock; @Captor private ArgumentCaptor<DisplayListener> mDisplayListenerCaptor; @@ -80,17 +74,7 @@ public class BrightnessSynchronizerTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - - Display display = mock(Display.class); - when(display.getDisplayAdjustments()).thenReturn(new DisplayAdjustments()); - BrightnessInfo info = new BrightnessInfo(PowerManager.BRIGHTNESS_INVALID_FLOAT, - PowerManager.BRIGHTNESS_MIN, BRIGHTNESS_MAX, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, BRIGHTNESS_MAX, - BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE); - when(display.getBrightnessInfo()).thenReturn(info); - - mContext = spy(new ContextWrapper( - ApplicationProvider.getApplicationContext().createDisplayContext(display))); + mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mContentResolverSpy = spy(new MockContentResolver(mContext)); mContentResolverSpy.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); when(mContext.getContentResolver()).thenReturn(mContentResolverSpy); @@ -144,12 +128,13 @@ public class BrightnessSynchronizerTest { @Test public void testSetSameIntValue_nothingUpdated() { putFloatSetting(0.5f); + putIntSetting(128); start(); - putIntSetting(fToI(0.5f)); + putIntSetting(128); advanceTime(10); verify(mDisplayManagerMock, times(0)).setBrightness( - eq(Display.DEFAULT_DISPLAY), eq(0.5f)); + eq(Display.DEFAULT_DISPLAY), eq(iToF(128))); } @Test @@ -169,13 +154,14 @@ public class BrightnessSynchronizerTest { // Verify that this update did not get sent to float, because synchronizer // is still waiting for confirmation of its first value. verify(mDisplayManagerMock, times(0)).setBrightness( - Display.DEFAULT_DISPLAY, iToF(20)); + eq(Display.DEFAULT_DISPLAY), eq(iToF(20))); // Send the confirmation of the initial change. This should trigger the new value to // finally be processed and we can verify that the new value (20) is sent. putIntSetting(fToI(0.4f)); advanceTime(10); - verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY, iToF(20)); + verify(mDisplayManagerMock).setBrightness( + eq(Display.DEFAULT_DISPLAY), eq(iToF(20))); } @@ -197,7 +183,8 @@ public class BrightnessSynchronizerTest { advanceTime(200); // Verify that the new value gets sent because the timeout expired. - verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY, iToF(20)); + verify(mDisplayManagerMock).setBrightness( + eq(Display.DEFAULT_DISPLAY), eq(iToF(20))); // Send a confirmation of the initial event, BrightnessSynchronizer should treat this as a // new event because the timeout had already expired @@ -209,14 +196,14 @@ public class BrightnessSynchronizerTest { // Verify we sent what would have been the confirmation as a new event to displaymanager. // We do both fToI and iToF because the conversions are not symmetric. - verify(mDisplayManagerMock).setBrightness(Display.DEFAULT_DISPLAY, - iToF(fToI(0.4f))); + verify(mDisplayManagerMock).setBrightness( + eq(Display.DEFAULT_DISPLAY), eq(iToF(fToI(0.4f)))); } - private void start() { - mSynchronizer = new BrightnessSynchronizer(mContext, mTestLooper.getLooper(), + private BrightnessSynchronizer start() { + BrightnessSynchronizer bs = new BrightnessSynchronizer(mContext, mTestLooper.getLooper(), mClock::now); - mSynchronizer.startSynchronizing(); + bs.startSynchronizing(); verify(mDisplayManagerMock).registerDisplayListener(mDisplayListenerCaptor.capture(), isA(Handler.class), eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS)); mDisplayListener = mDisplayListenerCaptor.getValue(); @@ -224,6 +211,7 @@ public class BrightnessSynchronizerTest { verify(mContentResolverSpy).registerContentObserver(eq(BRIGHTNESS_URI), eq(false), mContentObserverCaptor.capture(), eq(UserHandle.USER_ALL)); mContentObserver = mContentObserverCaptor.getValue(); + return bs; } private int getIntSetting() throws Exception { @@ -253,11 +241,11 @@ public class BrightnessSynchronizerTest { } private int fToI(float brightness) { - return mSynchronizer.brightnessFloatToIntSetting(brightness); + return BrightnessSynchronizer.brightnessFloatToInt(brightness); } private float iToF(int brightness) { - return mSynchronizer.brightnessIntSettingToFloat(brightness); + return BrightnessSynchronizer.brightnessIntToFloat(brightness); } private void advanceTime(long timeMs) { 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 306de525e53c..2396905aecbf 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -71,7 +71,6 @@ import android.graphics.Rect; import android.hardware.Sensor; import android.hardware.SensorManager; import android.hardware.display.BrightnessConfiguration; -import android.hardware.display.BrightnessInfo; import android.hardware.display.Curve; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; @@ -95,7 +94,6 @@ import android.os.Process; import android.os.RemoteException; import android.view.ContentRecordingSession; import android.view.Display; -import android.view.DisplayAdjustments; import android.view.DisplayCutout; import android.view.DisplayEventReceiver; import android.view.DisplayInfo; @@ -103,6 +101,7 @@ import android.view.Surface; import android.view.SurfaceControl; import android.window.DisplayWindowPolicyController; +import androidx.test.InstrumentationRegistry; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; @@ -333,11 +332,7 @@ public class DisplayManagerServiceTest { LocalServices.removeServiceForTest(UserManagerInternal.class); LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); // TODO: b/287945043 - Display display = mock(Display.class); - when(display.getDisplayAdjustments()).thenReturn(new DisplayAdjustments()); - when(display.getBrightnessInfo()).thenReturn(mock(BrightnessInfo.class)); - mContext = spy(new ContextWrapper( - ApplicationProvider.getApplicationContext().createDisplayContext(display))); + mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResources = Mockito.spy(mContext.getResources()); manageDisplaysPermission(/* granted= */ false); when(mContext.getResources()).thenReturn(mResources); @@ -1912,6 +1907,7 @@ public class DisplayManagerServiceTest { @Test public void testSettingTwoBrightnessConfigurationsOnMultiDisplay() { + Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); // get the first two internal displays 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 c3322eca6104..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 @@ -118,7 +118,8 @@ public class HdrClamperTest { mClock.fastForward(3000); mTestHandler.timeAdvance(); assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); - assertEquals(0.1f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); // (1-0.6) / 4 + // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4 + assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); } @Test @@ -146,7 +147,8 @@ public class HdrClamperTest { mClock.fastForward(1000); mTestHandler.timeAdvance(); assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); - assertEquals(0.2f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); // (1-0.6) / 2 + // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 2 + assertEquals(0.047137f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE); } @Test @@ -173,7 +175,8 @@ public class HdrClamperTest { mClock.fastForward(3000); mTestHandler.timeAdvance(); assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); - assertEquals(0.1f, mHdrClamper.getTransitionRate(), 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() 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/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/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 1e6306cb4071..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; @@ -70,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; @@ -116,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; @@ -303,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()); @@ -314,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; @@ -1427,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()); @@ -1448,7 +1451,7 @@ 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()); @@ -1513,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()); @@ -1534,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()); @@ -1555,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()); @@ -1576,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()); @@ -1616,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); @@ -1624,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)) @@ -1648,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); @@ -1691,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); @@ -1791,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()) @@ -1812,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 90d9452fb9c3..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; @@ -51,6 +52,9 @@ public class VirtualDeviceTest { 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; @@ -101,9 +105,10 @@ public class VirtualDeviceTest { 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); @@ -116,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/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/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/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/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/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/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/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) ) ) |