diff options
230 files changed, 4603 insertions, 1722 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/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 c9f06397abbf..f340e16383e4 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); } @@ -42903,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"; @@ -43071,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"; @@ -54454,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; 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/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/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/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/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/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/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/View.java b/core/java/android/view/View.java index 5eaded218a0b..552263a16937 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; 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/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/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/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/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/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/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/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/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/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/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/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt index b0e847f69cd5..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, failTestOnFlicker = 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 f847e400bebd..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, failTestOnFlicker = 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 31e52e2f2ae8..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, failTestOnFlicker = 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 0870073c5050..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, failTestOnFlicker = 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 1bb6c4596cd7..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, failTestOnFlicker = 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 bb084d6834b0..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, failTestOnFlicker = 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 bc5857f06120..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, failTestOnFlicker = 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 e4faa4a9116f..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, failTestOnFlicker = 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 7431974951a7..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, failTestOnFlicker = 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 c2b060975604..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, failTestOnFlicker = 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 7d1072dc19c2..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, failTestOnFlicker = 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 79760d1f23d7..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, failTestOnFlicker = 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 ff3729ab96c0..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, failTestOnFlicker = 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 ffbcfe43f374..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, failTestOnFlicker = 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 5e937c093eb7..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, failTestOnFlicker = 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 89db8a0c7a76..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, failTestOnFlicker = 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 bf4ee7a9bb84..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, failTestOnFlicker = 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 7e96f066c1c1..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, failTestOnFlicker = 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 a84deac7698b..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, failTestOnFlicker = 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 afcc743e650d..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, failTestOnFlicker = 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 2f96f5f99a66..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, failTestOnFlicker = 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 efbeb769508f..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, failTestOnFlicker = 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 232fccc12b1c..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, failTestOnFlicker = 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 d7f2845553f3..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, failTestOnFlicker = 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 019c946068dd..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, failTestOnFlicker = 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 567886103c61..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, failTestOnFlicker = 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 b3c4ea2c7c65..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, failTestOnFlicker = 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 f88180f40196..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, failTestOnFlicker = 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 391cb9b7c90c..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, failTestOnFlicker = 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 4af133ddaa21..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, failTestOnFlicker = false) - @PlatinumTest(focusArea = "sysui") @Presubmit @Test 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/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/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/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/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/Android.bp b/packages/SystemUI/compose/core/Android.bp index 510fa1e1fa80..42d088f218a1 100644 --- a/packages/SystemUI/compose/core/Android.bp +++ b/packages/SystemUI/compose/core/Android.bp @@ -34,7 +34,9 @@ android_library { "androidx.compose.runtime_runtime", "androidx.compose.material3_material3", + "androidx.compose.material3_material3-window-size-class", "androidx.savedstate_savedstate", + "androidx.window_window", ], kotlincflags: ["-Xjvm-default=all"], diff --git a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt index b4e90d63c6b8..06618704e085 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/theme/PlatformTheme.kt @@ -29,6 +29,8 @@ import com.android.compose.theme.typography.TypefaceNames import com.android.compose.theme.typography.TypefaceTokens import com.android.compose.theme.typography.TypographyTokens import com.android.compose.theme.typography.platformTypography +import com.android.compose.windowsizeclass.LocalWindowSizeClass +import com.android.compose.windowsizeclass.calculateWindowSizeClass /** The Material 3 theme that should wrap all Platform Composables. */ @Composable @@ -51,10 +53,12 @@ fun PlatformTheme( remember(typefaceNames) { platformTypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames)))) } + val windowSizeClass = calculateWindowSizeClass() MaterialTheme(colorScheme, typography = typography) { CompositionLocalProvider( LocalAndroidColorScheme provides androidColorScheme, + LocalWindowSizeClass provides windowSizeClass, ) { content() } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt new file mode 100644 index 000000000000..4674d6e5f25a --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.windowsizeclass + +import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.toComposeRect +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.window.layout.WindowMetricsCalculator + +val LocalWindowSizeClass = + staticCompositionLocalOf<WindowSizeClass> { + throw IllegalStateException( + "No WindowSizeClass configured. Make sure to use LocalWindowSizeClass in a Composable" + + " surrounded by a PlatformTheme {}." + ) + } + +@OptIn(ExperimentalMaterial3WindowSizeClassApi::class) +@Composable +fun calculateWindowSizeClass(): WindowSizeClass { + // Observe view configuration changes and recalculate the size class on each change. + LocalConfiguration.current + val density = LocalDensity.current + val context = LocalContext.current + val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context) + val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() } + return WindowSizeClass.calculateFromSize(size) +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index d0f2ce84655d..a61e95931222 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -14,40 +14,49 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class) - package com.android.systemui.bouncer.ui.composable import android.app.AlertDialog import android.app.Dialog import android.content.DialogInterface import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.snap import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel @@ -103,99 +112,234 @@ private fun SceneScope.BouncerScene( dialogFactory: BouncerSceneDialogFactory, modifier: Modifier = Modifier, ) { - val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() - val authMethodViewModel: AuthMethodBouncerViewModel? by - viewModel.authMethodViewModel.collectAsState() - val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() - var dialog: Dialog? by remember { mutableStateOf(null) } val backgroundColor = MaterialTheme.colorScheme.surface + val windowSizeClass = LocalWindowSizeClass.current Box(modifier) { Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) { drawRect(color = backgroundColor) } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(60.dp), - modifier = - Modifier.element(Bouncer.Elements.Content) - .fillMaxSize() - .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp) - ) { - Crossfade( - targetState = message, - label = "Bouncer message", - animationSpec = if (message.isUpdateAnimated) tween() else snap(), - ) { message -> - Text( - text = message.text, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, + val childModifier = Modifier.element(Bouncer.Elements.Content).fillMaxSize() + + when (windowSizeClass.widthSizeClass) { + WindowWidthSizeClass.Expanded -> + SideBySide( + viewModel = viewModel, + dialogFactory = dialogFactory, + modifier = childModifier, ) - } + WindowWidthSizeClass.Medium -> + Stacked( + viewModel = viewModel, + dialogFactory = dialogFactory, + modifier = childModifier, + ) + else -> + Bouncer( + viewModel = viewModel, + dialogFactory = dialogFactory, + modifier = childModifier, + ) + } + } +} - Box(Modifier.weight(1f)) { - when (val nonNullViewModel = authMethodViewModel) { - is PinBouncerViewModel -> - PinBouncer( - viewModel = nonNullViewModel, - modifier = Modifier.align(Alignment.Center), - ) - is PasswordBouncerViewModel -> - PasswordBouncer( - viewModel = nonNullViewModel, - modifier = Modifier.align(Alignment.Center), - ) - is PatternBouncerViewModel -> - PatternBouncer( - viewModel = nonNullViewModel, - modifier = - Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false) - .align(Alignment.BottomCenter), - ) - else -> Unit - } - } +/** + * Renders the contents of the actual bouncer UI, the area that takes user input to do an + * authentication attempt, including all messaging UI (directives, reasoning, errors, etc.). + */ +@Composable +private fun Bouncer( + viewModel: BouncerViewModel, + dialogFactory: BouncerSceneDialogFactory, + modifier: Modifier = Modifier, +) { + val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() + val authMethodViewModel: AuthMethodBouncerViewModel? by + viewModel.authMethodViewModel.collectAsState() + val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() + var dialog: Dialog? by remember { mutableStateOf(null) } - Button( - onClick = viewModel::onEmergencyServicesButtonClicked, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.tertiaryContainer, - contentColor = MaterialTheme.colorScheme.onTertiaryContainer, - ), - ) { - Text( - text = stringResource(com.android.internal.R.string.lockscreen_emergency_call), - style = MaterialTheme.typography.bodyMedium, - ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(60.dp), + modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp) + ) { + Crossfade( + targetState = message, + label = "Bouncer message", + animationSpec = if (message.isUpdateAnimated) tween() else snap(), + ) { message -> + Text( + text = message.text, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + ) + } + + Box(Modifier.weight(1f)) { + when (val nonNullViewModel = authMethodViewModel) { + is PinBouncerViewModel -> + PinBouncer( + viewModel = nonNullViewModel, + modifier = Modifier.align(Alignment.Center), + ) + is PasswordBouncerViewModel -> + PasswordBouncer( + viewModel = nonNullViewModel, + modifier = Modifier.align(Alignment.Center), + ) + is PatternBouncerViewModel -> + PatternBouncer( + viewModel = nonNullViewModel, + modifier = + Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false) + .align(Alignment.BottomCenter), + ) + else -> Unit } + } + + Button( + onClick = viewModel::onEmergencyServicesButtonClicked, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + contentColor = MaterialTheme.colorScheme.onTertiaryContainer, + ), + ) { + Text( + text = stringResource(com.android.internal.R.string.lockscreen_emergency_call), + style = MaterialTheme.typography.bodyMedium, + ) + } - if (dialogMessage != null) { - if (dialog == null) { - dialog = - dialogFactory().apply { - setMessage(dialogMessage) - setButton( - DialogInterface.BUTTON_NEUTRAL, - context.getString(R.string.ok), - ) { _, _ -> - viewModel.onThrottlingDialogDismissed() - } - setCancelable(false) - setCanceledOnTouchOutside(false) - show() + if (dialogMessage != null) { + if (dialog == null) { + dialog = + dialogFactory().apply { + setMessage(dialogMessage) + setButton( + DialogInterface.BUTTON_NEUTRAL, + context.getString(R.string.ok), + ) { _, _ -> + viewModel.onThrottlingDialogDismissed() } - } - } else { - dialog?.dismiss() - dialog = null + setCancelable(false) + setCanceledOnTouchOutside(false) + show() + } } + } else { + dialog?.dismiss() + dialog = null } } } +/** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */ +@Composable +private fun UserSwitcher( + modifier: Modifier = Modifier, +) { + Box(modifier) { + Text( + text = "TODO: the user switcher goes here", + modifier = Modifier.align(Alignment.Center) + ) + } +} + +/** + * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap + * anywhere on the background to flip their positions. + */ +@Composable +private fun SideBySide( + viewModel: BouncerViewModel, + dialogFactory: BouncerSceneDialogFactory, + modifier: Modifier = Modifier, +) { + val layoutDirection = LocalLayoutDirection.current + val isLeftToRight = layoutDirection == LayoutDirection.Ltr + val (isUserSwitcherFirst, setUserSwitcherFirst) = + rememberSaveable(isLeftToRight) { mutableStateOf(isLeftToRight) } + + Row( + modifier = + modifier.pointerInput(Unit) { + detectTapGestures( + onDoubleTap = { offset -> + // Depending on where the user double tapped, switch the elements such that + // the bouncer contents element is closer to the side that was double + // tapped. + setUserSwitcherFirst(offset.x > size.width / 2) + } + ) + }, + ) { + val animatedOffset by + animateFloatAsState( + targetValue = + if (isUserSwitcherFirst) { + // When the user switcher is first, both elements have their natural + // placement so they are not offset in any way. + 0f + } else if (isLeftToRight) { + // Since the user switcher is not first, the elements have to be swapped + // horizontally. In the case of LTR locales, this means pushing the user + // switcher to the right, hence the positive number. + 1f + } else { + // Since the user switcher is not first, the elements have to be swapped + // horizontally. In the case of RTL locales, this means pushing the user + // switcher to the left, hence the negative number. + -1f + }, + label = "offset", + ) + + UserSwitcher( + modifier = + Modifier.fillMaxHeight().weight(1f).graphicsLayer { + translationX = size.width * animatedOffset + }, + ) + Bouncer( + viewModel = viewModel, + dialogFactory = dialogFactory, + modifier = + Modifier.fillMaxHeight().weight(1f).graphicsLayer { + // A negative sign is used to make sure this is offset in the direction that's + // opposite of the direction that the user switcher is pushed in. + translationX = -size.width * animatedOffset + }, + ) + } +} + +/** Arranges the bouncer contents and user switcher contents one on top of the other. */ +@Composable +private fun Stacked( + viewModel: BouncerViewModel, + dialogFactory: BouncerSceneDialogFactory, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier, + ) { + UserSwitcher( + modifier = Modifier.fillMaxWidth().weight(1f), + ) + Bouncer( + viewModel = viewModel, + dialogFactory = dialogFactory, + modifier = Modifier.fillMaxWidth().weight(1f), + ) + } +} + interface BouncerSceneDialogFactory { operator fun invoke(): AlertDialog } 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/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/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/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/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/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 ede0339a9539..10f1d7c025e7 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -80,12 +80,6 @@ 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 = @@ -303,6 +297,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 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/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/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/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/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index ea5ca276a8cf..fb6713962b73 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; @@ -103,7 +101,6 @@ 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; @@ -181,22 +178,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 +221,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 +239,6 @@ public class NotificationLockscreenUserManagerImpl implements mDeviceProvisionedController = deviceProvisionedController; mSecureSettings = secureSettings; mKeyguardStateController = keyguardStateController; - mFeatureFlags = featureFlags; dumpManager.registerDumpable(this); } 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/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/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/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/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/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/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/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 39accfb5ffcc..0bc79a9f07d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -49,8 +49,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; @@ -112,7 +110,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() { @@ -295,21 +292,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); @@ -368,8 +356,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/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/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/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/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/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/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/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index 390c45b6a524..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" 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/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/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/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/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/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/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/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/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/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/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 95cbff9eeb12..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 @@ -69,7 +69,8 @@ public class HostTestUtils { String methodDescriptor, String callbackMethod ) { - callStaticMethodByName(callbackMethod, methodClass, methodName, methodDescriptor); + callStaticMethodByName(callbackMethod, "method call hook", methodClass, + methodName, methodDescriptor); } /** @@ -167,31 +168,38 @@ public class HostTestUtils { logPrintStream.println("! Class loaded: " + loadedClass.getCanonicalName() + " calling hook " + callbackMethod); - callStaticMethodByName(callbackMethod, loadedClass); + callStaticMethodByName(callbackMethod, "class load hook", loadedClass); } - private static void callStaticMethodByName(String classAndMethodName, Object... args) { + private static void callStaticMethodByName(String classAndMethodName, + String description, Object... args) { // Forward the call to callbackMethod. final int lastPeriod = classAndMethodName.lastIndexOf("."); - final String className = classAndMethodName.substring(0, lastPeriod); - final String methodName = classAndMethodName.substring(lastPeriod + 1); - 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\"", + "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]; @@ -204,25 +212,23 @@ public class HostTestUtils { 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" + "Unable to find %s: class %s doesn't have method %s" + " (method must take exactly one parameter of type Class," + " and public static)", - className, - methodName), e); + 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, 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); } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt index 7d7852ab162e..f75062b3a878 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt @@ -32,6 +32,10 @@ fun normalizeTextLine(s: String): String { 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 @@ -42,6 +46,10 @@ fun <T> addLists(a: List<T>, b: List<T>): List<T> { 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 |