diff options
177 files changed, 3244 insertions, 1942 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index c43aa7518f32..217101e77bb7 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -13,71 +13,142 @@ // limitations under the License. aconfig_srcjars = [ - ":android.app.usage.flags-aconfig-java{.generated_srcjars}", + // !!! KEEP THIS LIST ALPHABETICAL !!! + ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}", + ":android.app.flags-aconfig-java{.generated_srcjars}", ":android.app.smartspace.flags-aconfig-java{.generated_srcjars}", + ":android.app.usage.flags-aconfig-java{.generated_srcjars}", + ":android.appwidget.flags-aconfig-java{.generated_srcjars}", + ":android.chre.flags-aconfig-java{.generated_srcjars}", ":android.companion.flags-aconfig-java{.generated_srcjars}", + ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}", + ":android.content.flags-aconfig-java{.generated_srcjars}", ":android.content.pm.flags-aconfig-java{.generated_srcjars}", ":android.content.res.flags-aconfig-java{.generated_srcjars}", + ":android.credentials.flags-aconfig-java{.generated_srcjars}", + ":android.database.sqlite-aconfig-java{.generated_srcjars}", + ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}", ":android.hardware.flags-aconfig-java{.generated_srcjars}", ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}", + ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}", ":android.location.flags-aconfig-java{.generated_srcjars}", + ":android.media.tv.flags-aconfig-java{.generated_srcjars}", + ":android.multiuser.flags-aconfig-java{.generated_srcjars}", ":android.net.vcn.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", ":android.os.flags-aconfig-java{.generated_srcjars}", ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", + ":android.permission.flags-aconfig-java{.generated_srcjars}", + ":android.provider.flags-aconfig-java{.generated_srcjars}", ":android.security.flags-aconfig-java{.generated_srcjars}", ":android.server.app.flags-aconfig-java{.generated_srcjars}", + ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", ":android.service.chooser.flags-aconfig-java{.generated_srcjars}", + ":android.service.controls.flags-aconfig-java{.generated_srcjars}", ":android.service.dreams.flags-aconfig-java{.generated_srcjars}", ":android.service.notification.flags-aconfig-java{.generated_srcjars}", - ":android.view.flags-aconfig-java{.generated_srcjars}", + ":android.service.voice.flags-aconfig-java{.generated_srcjars}", + ":android.speech.flags-aconfig-java{.generated_srcjars}", + ":android.tracing.flags-aconfig-java{.generated_srcjars}", ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}", + ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}", + ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", + ":android.view.flags-aconfig-java{.generated_srcjars}", + ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}", + ":android.webkit.flags-aconfig-java{.generated_srcjars}", + ":android.widget.flags-aconfig-java{.generated_srcjars}", ":audio-framework-aconfig", ":camera_platform_flags_core_java_lib{.generated_srcjars}", - ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", - ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}", ":com.android.hardware.input-aconfig-java{.generated_srcjars}", ":com.android.input.flags-aconfig-java{.generated_srcjars}", - ":com.android.text.flags-aconfig-java{.generated_srcjars}", - ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}", - ":telecom_flags_core_java_lib{.generated_srcjars}", - ":telephony_flags_core_java_lib{.generated_srcjars}", - ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}", - ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}", - ":android.widget.flags-aconfig-java{.generated_srcjars}", - ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}", - ":sdk_sandbox_flags_lib{.generated_srcjars}", - ":android.permission.flags-aconfig-java{.generated_srcjars}", - ":android.database.sqlite-aconfig-java{.generated_srcjars}", - ":hwui_flags_java_lib{.generated_srcjars}", - ":framework_graphics_flags_java_lib{.generated_srcjars}", - ":display_flags_lib{.generated_srcjars}", ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}", - ":android.multiuser.flags-aconfig-java{.generated_srcjars}", - ":android.app.flags-aconfig-java{.generated_srcjars}", - ":android.credentials.flags-aconfig-java{.generated_srcjars}", - ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", - ":com.android.server.flags.services-aconfig-java{.generated_srcjars}", - ":android.service.controls.flags-aconfig-java{.generated_srcjars}", - ":android.service.voice.flags-aconfig-java{.generated_srcjars}", - ":android.media.tv.flags-aconfig-java{.generated_srcjars}", - ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", + ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}", ":com.android.net.flags-aconfig-java{.generated_srcjars}", + ":com.android.server.flags.services-aconfig-java{.generated_srcjars}", + ":com.android.text.flags-aconfig-java{.generated_srcjars}", + ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", ":device_policy_aconfig_flags_lib{.generated_srcjars}", - ":surfaceflinger_flags_java_lib{.generated_srcjars}", - ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}", - ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}", - ":android.tracing.flags-aconfig-java{.generated_srcjars}", - ":android.appwidget.flags-aconfig-java{.generated_srcjars}", - ":android.webkit.flags-aconfig-java{.generated_srcjars}", - ":android.provider.flags-aconfig-java{.generated_srcjars}", - ":android.chre.flags-aconfig-java{.generated_srcjars}", - ":android.speech.flags-aconfig-java{.generated_srcjars}", + ":display_flags_lib{.generated_srcjars}", + ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}", + ":framework_graphics_flags_java_lib{.generated_srcjars}", + ":hwui_flags_java_lib{.generated_srcjars}", ":power_flags_lib{.generated_srcjars}", - ":android.content.flags-aconfig-java{.generated_srcjars}", - ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}", + ":sdk_sandbox_flags_lib{.generated_srcjars}", + ":surfaceflinger_flags_java_lib{.generated_srcjars}", + ":telecom_flags_core_java_lib{.generated_srcjars}", + ":telephony_flags_core_java_lib{.generated_srcjars}", + // !!! KEEP THIS LIST ALPHABETICAL !!! ] +stubs_defaults { + name: "framework-minus-apex-aconfig-declarations", + aconfig_declarations: [ + "android.app.flags-aconfig", + "android.app.smartspace.flags-aconfig", + "android.app.usage.flags-aconfig", + "android.appwidget.flags-aconfig", + "android.companion.flags-aconfig", + "android.companion.virtual.flags-aconfig", + "android.content.pm.flags-aconfig", + "android.content.res.flags-aconfig", + "android.credentials.flags-aconfig", + "android.database.sqlite-aconfig", + "android.hardware.biometrics.flags-aconfig", + "android.hardware.flags-aconfig", + "android.hardware.radio.flags-aconfig", + "android.hardware.usb.flags-aconfig", + "android.location.flags-aconfig", + "android.media.audio-aconfig", + "android.media.audiopolicy-aconfig", + "android.media.midi-aconfig", + "android.media.tv.flags-aconfig", + "android.multiuser.flags-aconfig", + "android.net.vcn.flags-aconfig", + "android.nfc.flags-aconfig", + "android.os.flags-aconfig", + "android.os.vibrator.flags-aconfig", + "android.permission.flags-aconfig", + "android.provider.flags-aconfig", + "android.security.flags-aconfig", + "android.server.app.flags-aconfig", + "android.service.autofill.flags-aconfig", + "android.service.chooser.flags-aconfig", + "android.service.controls.flags-aconfig", + "android.service.dreams.flags-aconfig", + "android.service.notification.flags-aconfig", + "android.service.voice.flags-aconfig", + "android.speech.flags-aconfig", + "android.tracing.flags-aconfig", + "android.view.accessibility.flags-aconfig", + "android.view.contentcapture.flags-aconfig", + "android.view.contentprotection.flags-aconfig", + "android.view.flags-aconfig", + "android.view.inputmethod.flags-aconfig", + "android.webkit.flags-aconfig", + "android.widget.flags-aconfig", + "camera_platform_flags", + "chre_flags", + "com.android.hardware.input.input-aconfig", + "com.android.input.flags-aconfig", + "com.android.media.flags.bettertogether-aconfig", + "com.android.net.flags-aconfig", + "com.android.server.flags.services-aconfig", + "com.android.text.flags-aconfig", + "com.android.window.flags.window-aconfig", + "device_policy_aconfig_flags", + "display_flags", + "fold_lock_setting_flags", + "framework-jobscheduler-job.flags-aconfig", + "framework_graphics_flags", + "hwui_flags", + "power_flags", + "sdk_sandbox_flags", + "surfaceflinger_flags", + "telecom_flags", + "telephony_flags", + ], +} + filegroup { name: "framework-minus-apex-aconfig-srcjars", srcs: aconfig_srcjars, diff --git a/Ravenwood.bp b/Ravenwood.bp index d13c4d78190c..0877bcedb609 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -97,6 +97,7 @@ android_ravenwood_libgroup { "framework-minus-apex.ravenwood", "hoststubgen-helper-runtime.ravenwood", "hoststubgen-helper-framework-runtime.ravenwood", + "core-libart-for-host", "all-updatable-modules-system-stubs", "junit", "truth", diff --git a/TEST_MAPPING b/TEST_MAPPING index d59775f4060b..c904eb46d88e 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -138,15 +138,13 @@ } ], "postsubmit-ravenwood": [ - // TODO(ravenwood) promote it to presubmit - // TODO: Enable it once the infra knows how to run it. -// { -// "name": "CtsUtilTestCasesRavenwood", -// "file_patterns": [ -// "*Ravenwood*", -// "*ravenwood*" -// ] -// } + { + "name": "CtsUtilTestCasesRavenwood", + "host": true, + "file_patterns": [ + "[Rr]avenwood" + ] + } ], "postsubmit-managedprofile-stress": [ { diff --git a/api/Android.bp b/api/Android.bp index 1686943d08ca..126176dab3f3 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -269,6 +269,7 @@ packages_to_document = [ // classpath (or sources) somehow. stubs_defaults { name: "android-non-updatable-stubs-defaults", + defaults: ["framework-minus-apex-aconfig-declarations"], srcs: [":android-non-updatable-stub-sources"], sdk_version: "none", system_modules: "none", diff --git a/core/api/current.txt b/core/api/current.txt index 43ba6dc5b30e..f052b85a9f44 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5317,7 +5317,6 @@ package android.app { ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean); ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean); ctor public AutomaticZenRule(android.os.Parcel); - method @FlaggedApi("android.app.modes_api") public boolean canUpdate(); method public int describeContents(); method public android.net.Uri getConditionId(); method @Nullable public android.content.ComponentName getConfigurationActivity(); @@ -13676,11 +13675,8 @@ package android.content.res { @FlaggedApi("android.content.res.font_scale_converter_public") public interface FontScaleConverter { method public float convertDpToSp(float); method public float convertSpToDp(float); - } - - @FlaggedApi("android.content.res.font_scale_converter_public") public class FontScaleConverterFactory { - method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float); - method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread public static boolean isNonLinearFontScalingActive(float); + method @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float); + method @AnyThread public static boolean isNonLinearFontScalingActive(float); } public class ObbInfo implements android.os.Parcelable { @@ -43082,6 +43078,8 @@ package android.telephony { 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 @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_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"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b8c9fc66cb57..2596f9c6c39c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -133,6 +133,7 @@ package android { field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String GET_APP_METADATA = "android.permission.GET_APP_METADATA"; field public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS"; + field @FlaggedApi("android.app.bic_client") public static final String GET_BACKGROUND_INSTALLED_PACKAGES = "android.permission.GET_BACKGROUND_INSTALLED_PACKAGES"; field @FlaggedApi("android.app.get_binding_uid_importance") public static final String GET_BINDING_UID_IMPORTANCE = "android.permission.GET_BINDING_UID_IMPORTANCE"; field public static final String GET_HISTORICAL_APP_OPS_STATS = "android.permission.GET_HISTORICAL_APP_OPS_STATS"; field public static final String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"; @@ -4221,11 +4222,14 @@ package android.content.pm { field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR; field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1 field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0 + field public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; // 0xffffffff field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2 field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1 field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0 + field public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1; // 0xffffffff field public static final int SHOW_IN_SHARING_SURFACES_NO = 2; // 0x2 field public static final int SHOW_IN_SHARING_SURFACES_SEPARATE = 1; // 0x1 + field public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = -1; // 0xffffffff field public static final int SHOW_IN_SHARING_SURFACES_WITH_PARENT = 0; // 0x0 } @@ -17161,6 +17165,7 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") 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_ENTITLEMENT = 2; // 0x2 field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index bbe03a3d11a2..0d1d8d733139 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -284,16 +284,6 @@ package android.app { method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int); } - public final class AutomaticZenRule implements android.os.Parcelable { - method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields(); - field @FlaggedApi("android.app.modes_api") public static final int FIELD_INTERRUPTION_FILTER = 2; // 0x2 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_NAME = 1; // 0x1 - } - - @FlaggedApi("android.app.modes_api") public static final class AutomaticZenRule.Builder { - method @FlaggedApi("android.app.modes_api") @NonNull public android.app.AutomaticZenRule.Builder setUserModifiedFields(int); - } - public class BroadcastOptions extends android.app.ComponentOptions { ctor public BroadcastOptions(); ctor public BroadcastOptions(@NonNull android.os.Bundle); @@ -1177,6 +1167,7 @@ package android.content.pm { method public int getShowInLauncher(); field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2 field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1 + field public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1; // 0xffffffff field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0 } @@ -3021,47 +3012,8 @@ package android.service.notification { method @Deprecated public boolean isBound(); } - @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable { - method public int getUserModifiedFields(); - field public static final int FIELD_DIM_WALLPAPER = 4; // 0x4 - field public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 16; // 0x10 - field public static final int FIELD_DISABLE_TAP_TO_WAKE = 32; // 0x20 - field public static final int FIELD_DISABLE_TILT_TO_WAKE = 64; // 0x40 - field public static final int FIELD_DISABLE_TOUCH = 128; // 0x80 - field public static final int FIELD_GRAYSCALE = 1; // 0x1 - field public static final int FIELD_MAXIMIZE_DOZE = 512; // 0x200 - field public static final int FIELD_MINIMIZE_RADIO_USAGE = 256; // 0x100 - field public static final int FIELD_NIGHT_MODE = 8; // 0x8 - field public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 2; // 0x2 - } - - @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder { - method @NonNull public android.service.notification.ZenDeviceEffects.Builder setUserModifiedFields(int); - } - - public final class ZenPolicy implements android.os.Parcelable { - method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields(); - field @FlaggedApi("android.app.modes_api") public static final int FIELD_ALLOW_CHANNELS = 8; // 0x8 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_CALLS = 2; // 0x2 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_CONVERSATIONS = 4; // 0x4 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_MESSAGES = 1; // 0x1 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 128; // 0x80 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 32; // 0x20 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 256; // 0x100 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 64; // 0x40 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 512; // 0x200 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_AMBIENT = 32768; // 0x8000 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_BADGE = 16384; // 0x4000 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1024; // 0x400 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_LIGHTS = 2048; // 0x800 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 65536; // 0x10000 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_PEEK = 4096; // 0x1000 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 8192; // 0x2000 - } - public static final class ZenPolicy.Builder { ctor public ZenPolicy.Builder(@Nullable android.service.notification.ZenPolicy); - method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder setUserModifiedFields(int); } } diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 5b354fc3b9ed..d57a4e583a1a 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -23,7 +23,6 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.app.NotificationManager.InterruptionFilter; import android.content.ComponentName; import android.net.Uri; @@ -113,8 +112,8 @@ public final class AutomaticZenRule implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface Type {} - /** Used to track which rule variables have been modified by the user. - * Should be checked against the bitmask {@link #getUserModifiedFields()}. + /** + * Enum for the user-modifiable fields in this object. * @hide */ @IntDef(flag = true, prefix = { "FIELD_" }, value = { @@ -128,13 +127,11 @@ public final class AutomaticZenRule implements Parcelable { * @hide */ @FlaggedApi(Flags.FLAG_MODES_API) - @TestApi public static final int FIELD_NAME = 1 << 0; /** * @hide */ @FlaggedApi(Flags.FLAG_MODES_API) - @TestApi public static final int FIELD_INTERRUPTION_FILTER = 1 << 1; private boolean enabled; @@ -153,7 +150,6 @@ public final class AutomaticZenRule implements Parcelable { private int mIconResId; private String mTriggerDescription; private boolean mAllowManualInvocation; - private @ModifiableField int mUserModifiedFields; // Bitwise representation /** * The maximum string length for any string contained in this automatic zen rule. This pertains @@ -256,7 +252,6 @@ public final class AutomaticZenRule implements Parcelable { mIconResId = source.readInt(); mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH); mType = source.readInt(); - mUserModifiedFields = source.readInt(); } } @@ -307,8 +302,7 @@ public final class AutomaticZenRule implements Parcelable { * Returns whether this rule's name has been modified by the user. * @hide */ - // TODO: b/310620812 - Replace with mUserModifiedFields & FIELD_NAME once - // FLAG_MODES_API is inlined. + // TODO: b/310620812 - Consider removing completely. Seems not be used anywhere except tests. public boolean isModified() { return mModified; } @@ -506,32 +500,6 @@ public final class AutomaticZenRule implements Parcelable { return type; } - /** - * Gets the bitmask representing which fields are user modified. Bits are set using - * {@link ModifiableField}. - * @hide - */ - @FlaggedApi(Flags.FLAG_MODES_API) - @TestApi - public @ModifiableField int getUserModifiedFields() { - return mUserModifiedFields; - } - - /** - * Returns {@code true} if the {@link AutomaticZenRule} can be updated. - * When this returns {@code false}, calls to - * {@link NotificationManager#updateAutomaticZenRule(String, AutomaticZenRule)}) with this rule - * will ignore changes to user-configurable fields. - */ - @FlaggedApi(Flags.FLAG_MODES_API) - public boolean canUpdate() { - // The rule is considered updateable if its bitmask has no user modifications, and - // the bitmasks of the policy and device effects have no modification. - return mUserModifiedFields == 0 - && (mZenPolicy == null || mZenPolicy.getUserModifiedFields() == 0) - && (mDeviceEffects == null || mDeviceEffects.getUserModifiedFields() == 0); - } - @Override public int describeContents() { return 0; @@ -560,7 +528,6 @@ public final class AutomaticZenRule implements Parcelable { dest.writeInt(mIconResId); dest.writeString(mTriggerDescription); dest.writeInt(mType); - dest.writeInt(mUserModifiedFields); } } @@ -582,16 +549,14 @@ public final class AutomaticZenRule implements Parcelable { .append(",allowManualInvocation=").append(mAllowManualInvocation) .append(",iconResId=").append(mIconResId) .append(",triggerDescription=").append(mTriggerDescription) - .append(",type=").append(mType) - .append(",userModifiedFields=") - .append(modifiedFieldsToString(mUserModifiedFields)); + .append(",type=").append(mType); } return sb.append(']').toString(); } - @FlaggedApi(Flags.FLAG_MODES_API) - private String modifiedFieldsToString(int bitmask) { + /** @hide */ + public static String fieldsToString(@ModifiableField int bitmask) { ArrayList<String> modified = new ArrayList<>(); if ((bitmask & FIELD_NAME) != 0) { modified.add("FIELD_NAME"); @@ -623,8 +588,7 @@ public final class AutomaticZenRule implements Parcelable { && other.mAllowManualInvocation == mAllowManualInvocation && other.mIconResId == mIconResId && Objects.equals(other.mTriggerDescription, mTriggerDescription) - && other.mType == mType - && other.mUserModifiedFields == mUserModifiedFields; + && other.mType == mType; } return finalEquals; } @@ -634,8 +598,7 @@ public final class AutomaticZenRule implements Parcelable { if (Flags.modesApi()) { return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime, - mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType, - mUserModifiedFields); + mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType); } return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, configurationActivity, mZenPolicy, mModified, creationTime, mPkg); @@ -704,7 +667,6 @@ public final class AutomaticZenRule implements Parcelable { private boolean mAllowManualInvocation; private long mCreationTime; private String mPkg; - private @ModifiableField int mUserModifiedFields; public Builder(@NonNull AutomaticZenRule rule) { mName = rule.getName(); @@ -721,7 +683,6 @@ public final class AutomaticZenRule implements Parcelable { mAllowManualInvocation = rule.isManualInvocationAllowed(); mCreationTime = rule.getCreationTime(); mPkg = rule.getPackageName(); - mUserModifiedFields = rule.mUserModifiedFields; } public Builder(@NonNull String name, @NonNull Uri conditionId) { @@ -848,19 +809,6 @@ public final class AutomaticZenRule implements Parcelable { return this; } - /** - * Sets the bitmask representing which fields have been user-modified. - * This method should not be used outside of tests. The value of userModifiedFields - * should be set based on what values are changed when a rule is populated or updated.. - * @hide - */ - @FlaggedApi(Flags.FLAG_MODES_API) - @TestApi - public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) { - mUserModifiedFields = userModifiedFields; - return this; - } - public @NonNull AutomaticZenRule build() { AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity, mConditionId, mPolicy, mInterruptionFilter, mEnabled); @@ -871,7 +819,6 @@ public final class AutomaticZenRule implements Parcelable { rule.mIconResId = mIconResId; rule.mAllowManualInvocation = mAllowManualInvocation; rule.setPackageName(mPkg); - rule.mUserModifiedFields = mUserModifiedFields; return rule; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 476232cb40b3..ed0cfbe3d9c3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5652,7 +5652,7 @@ public class Notification implements Parcelable pillColor = Colors.flattenAlpha( getColors(p).getTertiaryFixedDimAccentColor(), bgColor); textColor = Colors.flattenAlpha( - getColors(p).getOnTertiaryAccentTextColor(), pillColor); + getColors(p).getOnTertiaryFixedAccentTextColor(), pillColor); } contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java index 57749d43eb37..269c6c27821c 100644 --- a/core/java/android/content/pm/UserProperties.java +++ b/core/java/android/content/pm/UserProperties.java @@ -123,6 +123,7 @@ public final class UserProperties implements Parcelable { * @hide */ @IntDef(prefix = "SHOW_IN_LAUNCHER_", value = { + SHOW_IN_LAUNCHER_UNKNOWN, SHOW_IN_LAUNCHER_WITH_PARENT, SHOW_IN_LAUNCHER_SEPARATE, SHOW_IN_LAUNCHER_NO, @@ -131,6 +132,13 @@ public final class UserProperties implements Parcelable { public @interface ShowInLauncher { } /** + * Indicates that the show in launcher value for this profile is unknown or unsupported. + * @hide + */ + @TestApi + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1; + /** * Suggests that the launcher should show this user's apps in the main tab. * That is, either this user is a full user, so its apps should be presented accordingly, or, if * this user is a profile, then its apps should be shown alongside its parent's apps. @@ -157,6 +165,7 @@ public final class UserProperties implements Parcelable { * @hide */ @IntDef(prefix = "SHOW_IN_SETTINGS_", value = { + SHOW_IN_SETTINGS_UNKNOWN, SHOW_IN_SETTINGS_WITH_PARENT, SHOW_IN_SETTINGS_SEPARATE, SHOW_IN_SETTINGS_NO, @@ -165,6 +174,12 @@ public final class UserProperties implements Parcelable { public @interface ShowInSettings { } /** + * Indicates that the show in settings value for this profile is unknown or unsupported. + * @hide + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_SETTINGS_UNKNOWN = -1; + /** * Suggests that the Settings app should show this user's apps in the main tab. * That is, either this user is a full user, so its apps should be presented accordingly, or, if * this user is a profile, then its apps should be shown alongside its parent's apps. @@ -309,6 +324,7 @@ public final class UserProperties implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "SHOW_IN_QUIET_MODE_", value = { + SHOW_IN_QUIET_MODE_UNKNOWN, SHOW_IN_QUIET_MODE_PAUSED, SHOW_IN_QUIET_MODE_HIDDEN, SHOW_IN_QUIET_MODE_DEFAULT, @@ -318,6 +334,12 @@ public final class UserProperties implements Parcelable { } /** + * Indicates that the show in quiet mode value for this profile is unknown. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1; + + /** * Indicates that the profile should still be visible in quiet mode but should be shown as * paused (e.g. by greying out its icons). */ @@ -347,6 +369,7 @@ public final class UserProperties implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "SHOW_IN_SHARING_SURFACES_", value = { + SHOW_IN_SHARING_SURFACES_UNKNOWN, SHOW_IN_SHARING_SURFACES_SEPARATE, SHOW_IN_SHARING_SURFACES_WITH_PARENT, SHOW_IN_SHARING_SURFACES_NO, @@ -356,6 +379,12 @@ public final class UserProperties implements Parcelable { } /** + * Indicates that the show in launcher value for this profile is unknown or unsupported. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = SHOW_IN_LAUNCHER_UNKNOWN; + + /** * Indicates that the profile data and apps should be shown in sharing surfaces intermixed with * parent user's data and apps. */ @@ -379,7 +408,8 @@ public final class UserProperties implements Parcelable { * * @hide */ - @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_STRATEGY_"}, value = { + @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_"}, value = { + CROSS_PROFILE_CONTENT_SHARING_UNKNOWN, CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION, CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT }) @@ -388,6 +418,13 @@ public final class UserProperties implements Parcelable { } /** + * Signifies that cross-profile content sharing strategy, both to and from this profile, is + * unknown/unsupported. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; + + /** * Signifies that cross-profile content sharing strategy, both to and from this profile, should * not be delegated to any other user/profile. * For ex: diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java index 088949e7eec2..f4312a905110 100644 --- a/core/java/android/content/res/FontScaleConverter.java +++ b/core/java/android/content/res/FontScaleConverter.java @@ -17,7 +17,9 @@ package android.content.res; +import android.annotation.AnyThread; import android.annotation.FlaggedApi; +import android.annotation.Nullable; /** * A converter for non-linear font scaling. Converts font sizes given in "sp" dimensions to a @@ -40,4 +42,35 @@ public interface FontScaleConverter { * Converts a dimension in "dp" back to "sp". */ float convertDpToSp(float dp); + + /** + * Returns true if non-linear font scaling curves would be in effect for the given scale, false + * if the scaling would follow a linear curve or for no scaling. + * + * <p>Example usage: {@code + * isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)} + */ + @AnyThread + static boolean isNonLinearFontScalingActive(float fontScale) { + return FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale); + } + + /** + * Finds a matching FontScaleConverter for the given fontScale factor. + * + * Generally you shouldn't need this; you can use {@link + * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do + * the scaling conversion for you. Dimens and resources loaded from XML will also be + * automatically converted. But for UI frameworks or other situations where you need to do the + * conversion without an Android Context, you can use this method. + * + * @param fontScale the scale factor, usually from {@link Configuration#fontScale}. + * + * @return a converter for the given scale, or null if non-linear scaling should not be used. + */ + @Nullable + @AnyThread + static FontScaleConverter forScale(float fontScale) { + return FontScaleConverterFactory.forScale(fontScale); + } } diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java index 5d31cc0f0243..cbe4c62d7069 100644 --- a/core/java/android/content/res/FontScaleConverterFactory.java +++ b/core/java/android/content/res/FontScaleConverterFactory.java @@ -17,7 +17,6 @@ package android.content.res; import android.annotation.AnyThread; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.MathUtils; @@ -32,8 +31,9 @@ import com.android.internal.annotations.VisibleForTesting; * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do the * scaling conversion for you. But for UI frameworks or other situations where you need to do the * conversion without an Android Context, you can use this class. + * + * @hide */ -@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) public class FontScaleConverterFactory { private static final float SCALE_KEY_MULTIPLIER = 100f; @@ -124,7 +124,6 @@ public class FontScaleConverterFactory { * <p>Example usage: * <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code> */ - @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) @AnyThread public static boolean isNonLinearFontScalingActive(float fontScale) { return fontScale >= sMinScaleBeforeCurvesApplied; @@ -137,7 +136,6 @@ public class FontScaleConverterFactory { * * @return a converter for the given scale, or null if non-linear scaling should not be used. */ - @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) @Nullable @AnyThread public static FontScaleConverter forScale(float fontScale) { diff --git a/core/java/android/hardware/radio/TEST_MAPPING b/core/java/android/hardware/radio/TEST_MAPPING new file mode 100644 index 000000000000..ee4eeb634c84 --- /dev/null +++ b/core/java/android/hardware/radio/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "frameworks/base/core/tests/BroadcastRadioTests" + } + ] +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7d84bb390853..04d8a4900042 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3236,9 +3236,17 @@ public final class Settings { private static final String NAME_EQ_PLACEHOLDER = "name=?"; + // Cached values of queried settings. + // Key is the setting's name, value is the setting's value. // Must synchronize on 'this' to access mValues and mValuesVersion. private final ArrayMap<String, String> mValues = new ArrayMap<>(); + // Cached values for queried prefixes. + // Key is the prefix, value is all of the settings under the prefix, mapped from a setting's + // name to a setting's value. The name string doesn't include the prefix. + // Must synchronize on 'this' to access. + private final ArrayMap<String, ArrayMap<String, String>> mPrefixToValues = new ArrayMap<>(); + private final Uri mUri; @UnsupportedAppUsage private final ContentProviderHolder mProviderHolder; @@ -3583,15 +3591,13 @@ public final class Settings { || applicationInfo.isSignedWithPlatformKey(); } - private ArrayMap<String, String> getStringsForPrefixStripPrefix( - ContentResolver cr, String prefix, String[] names) { + private Map<String, String> getStringsForPrefixStripPrefix( + ContentResolver cr, String prefix, List<String> names) { String namespace = prefix.substring(0, prefix.length() - 1); ArrayMap<String, String> keyValues = new ArrayMap<>(); int substringLength = prefix.length(); - int currentGeneration = -1; boolean needsGenerationTracker = false; - synchronized (NameValueCache.this) { final GenerationTracker generationTracker = mGenerationTrackers.get(prefix); if (generationTracker != null) { @@ -3605,40 +3611,22 @@ public final class Settings { // generation tracker and request a new one generationTracker.destroy(); mGenerationTrackers.remove(prefix); - for (int i = mValues.size() - 1; i >= 0; i--) { - String key = mValues.keyAt(i); - if (key.startsWith(prefix)) { - mValues.remove(key); - } - } + mPrefixToValues.remove(prefix); needsGenerationTracker = true; } else { - boolean prefixCached = mValues.containsKey(prefix); - if (prefixCached) { - if (DEBUG) { - Log.i(TAG, "Cache hit for prefix:" + prefix); - } - if (names.length > 0) { + final ArrayMap<String, String> cachedSettings = mPrefixToValues.get(prefix); + if (cachedSettings != null) { + if (!names.isEmpty()) { for (String name : names) { - // mValues can contain "null" values, need to use containsKey. - if (mValues.containsKey(name)) { + // The cache can contain "null" values, need to use containsKey. + if (cachedSettings.containsKey(name)) { keyValues.put( - name.substring(substringLength), - mValues.get(name)); + name, + cachedSettings.get(name)); } } } else { - for (int i = 0; i < mValues.size(); ++i) { - String key = mValues.keyAt(i); - // Explicitly exclude the prefix as it is only there to - // signal that the prefix has been cached. - if (key.startsWith(prefix) && !key.equals(prefix)) { - String value = mValues.valueAt(i); - keyValues.put( - key.substring(substringLength), - value); - } - } + keyValues.putAll(cachedSettings); } return keyValues; } @@ -3648,7 +3636,6 @@ public final class Settings { needsGenerationTracker = true; } } - if (mCallListCommand == null) { // No list command specified, return empty map return keyValues; @@ -3693,20 +3680,23 @@ public final class Settings { } // All flags for the namespace - Map<String, String> flagsToValues = + HashMap<String, String> flagsToValues = (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, java.util.HashMap.class); + if (flagsToValues == null) { + return keyValues; + } // Only the flags requested by the caller - if (names.length > 0) { + if (!names.isEmpty()) { for (String name : names) { // flagsToValues can contain "null" values, need to use containsKey. - if (flagsToValues.containsKey(name)) { + final String key = Config.createCompositeName(namespace, name); + if (flagsToValues.containsKey(key)) { keyValues.put( - name.substring(substringLength), - flagsToValues.get(name)); + name, + flagsToValues.get(key)); } } } else { - keyValues.ensureCapacity(keyValues.size() + flagsToValues.size()); for (Map.Entry<String, String> flag : flagsToValues.entrySet()) { keyValues.put( flag.getKey().substring(substringLength), @@ -3740,10 +3730,18 @@ public final class Settings { if (DEBUG) { Log.i(TAG, "Updating cache for prefix:" + prefix); } - // cache the complete list of flags for the namespace - mValues.putAll(flagsToValues); - // Adding the prefix as a signal that the prefix is cached. - mValues.put(prefix, null); + // Cache the complete list of flags for the namespace for bulk queries. + // In this cached list, the setting's name doesn't include the prefix. + ArrayMap<String, String> namesToValues = + new ArrayMap<>(flagsToValues.size() + 1); + for (Map.Entry<String, String> flag : flagsToValues.entrySet()) { + namesToValues.put( + flag.getKey().substring(substringLength), + flag.getValue()); + } + // Legacy behavior, we return <"", null> when queried with name = "" + namesToValues.put("", null); + mPrefixToValues.put(prefix, namesToValues); } } return keyValues; @@ -19907,16 +19905,9 @@ public final class Settings { @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public static Map<String, String> getStrings(@NonNull ContentResolver resolver, @NonNull String namespace, @NonNull List<String> names) { - String[] compositeNames = new String[names.size()]; - for (int i = 0, size = names.size(); i < size; ++i) { - compositeNames[i] = createCompositeName(namespace, names.get(i)); - } - String prefix = createPrefix(namespace); - ArrayMap<String, String> keyValues = sNameValueCache.getStringsForPrefixStripPrefix( - resolver, prefix, compositeNames); - return keyValues; + return sNameValueCache.getStringsForPrefixStripPrefix(resolver, prefix, names); } /** @@ -20238,7 +20229,7 @@ public final class Settings { } } - private static String createCompositeName(@NonNull String namespace, @NonNull String name) { + static String createCompositeName(@NonNull String namespace, @NonNull String name) { Preconditions.checkNotNull(namespace); Preconditions.checkNotNull(name); var sb = new StringBuilder(namespace.length() + 1 + name.length()); diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING index 8b4a99e38299..d5ac7a7de461 100644 --- a/core/java/android/provider/TEST_MAPPING +++ b/core/java/android/provider/TEST_MAPPING @@ -28,5 +28,10 @@ } ] } + ], + "postsubmit": [ + { + "name": "CtsDeviceConfigTestCases" + } ] } diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java index 03ebae5c5199..90049e6a934a 100644 --- a/core/java/android/service/notification/ZenDeviceEffects.java +++ b/core/java/android/service/notification/ZenDeviceEffects.java @@ -20,7 +20,6 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.app.Flags; import android.os.Parcel; import android.os.Parcelable; @@ -37,8 +36,8 @@ import java.util.Objects; @FlaggedApi(Flags.FLAG_MODES_API) public final class ZenDeviceEffects implements Parcelable { - /** Used to track which rule variables have been modified by the user. - * Should be checked against the bitmask {@link #getUserModifiedFields()}. + /** + * Enum for the user-modifiable fields in this object. * @hide */ @IntDef(flag = true, prefix = { "FIELD_" }, value = { @@ -59,52 +58,42 @@ public final class ZenDeviceEffects implements Parcelable { /** * @hide */ - @TestApi public static final int FIELD_GRAYSCALE = 1 << 0; /** * @hide */ - @TestApi public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 1 << 1; /** * @hide */ - @TestApi public static final int FIELD_DIM_WALLPAPER = 1 << 2; /** * @hide */ - @TestApi public static final int FIELD_NIGHT_MODE = 1 << 3; /** * @hide */ - @TestApi public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 1 << 4; /** * @hide */ - @TestApi public static final int FIELD_DISABLE_TAP_TO_WAKE = 1 << 5; /** * @hide */ - @TestApi public static final int FIELD_DISABLE_TILT_TO_WAKE = 1 << 6; /** * @hide */ - @TestApi public static final int FIELD_DISABLE_TOUCH = 1 << 7; /** * @hide */ - @TestApi public static final int FIELD_MINIMIZE_RADIO_USAGE = 1 << 8; /** * @hide */ - @TestApi public static final int FIELD_MAXIMIZE_DOZE = 1 << 9; private final boolean mGrayscale; @@ -119,13 +108,10 @@ public final class ZenDeviceEffects implements Parcelable { private final boolean mMinimizeRadioUsage; private final boolean mMaximizeDoze; - private final @ModifiableField int mUserModifiedFields; // Bitwise representation - private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay, boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness, boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch, - boolean minimizeRadioUsage, boolean maximizeDoze, - @ModifiableField int userModifiedFields) { + boolean minimizeRadioUsage, boolean maximizeDoze) { mGrayscale = grayscale; mSuppressAmbientDisplay = suppressAmbientDisplay; mDimWallpaper = dimWallpaper; @@ -136,7 +122,6 @@ public final class ZenDeviceEffects implements Parcelable { mDisableTouch = disableTouch; mMinimizeRadioUsage = minimizeRadioUsage; mMaximizeDoze = maximizeDoze; - mUserModifiedFields = userModifiedFields; } @Override @@ -153,15 +138,14 @@ public final class ZenDeviceEffects implements Parcelable { && this.mDisableTiltToWake == that.mDisableTiltToWake && this.mDisableTouch == that.mDisableTouch && this.mMinimizeRadioUsage == that.mMinimizeRadioUsage - && this.mMaximizeDoze == that.mMaximizeDoze - && this.mUserModifiedFields == that.mUserModifiedFields; + && this.mMaximizeDoze == that.mMaximizeDoze; } @Override public int hashCode() { return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode, mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch, - mMinimizeRadioUsage, mMaximizeDoze, mUserModifiedFields); + mMinimizeRadioUsage, mMaximizeDoze); } @Override @@ -177,11 +161,11 @@ public final class ZenDeviceEffects implements Parcelable { if (mDisableTouch) effects.add("disableTouch"); if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage"); if (mMaximizeDoze) effects.add("maximizeDoze"); - return "[" + String.join(", ", effects) + "]" - + " userModifiedFields: " + modifiedFieldsToString(mUserModifiedFields); + return "[" + String.join(", ", effects) + "]"; } - private String modifiedFieldsToString(int bitmask) { + /** @hide */ + public static String fieldsToString(@ModifiableField int bitmask) { ArrayList<String> modified = new ArrayList<>(); if ((bitmask & FIELD_GRAYSCALE) != 0) { modified.add("FIELD_GRAYSCALE"); @@ -312,7 +296,7 @@ public final class ZenDeviceEffects implements Parcelable { return new ZenDeviceEffects(in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), - in.readBoolean(), in.readInt()); + in.readBoolean()); } @Override @@ -321,16 +305,6 @@ public final class ZenDeviceEffects implements Parcelable { } }; - /** - * Gets the bitmask representing which fields are user modified. Bits are set using - * {@link ModifiableField}. - * @hide - */ - @TestApi - public @ModifiableField int getUserModifiedFields() { - return mUserModifiedFields; - } - @Override public int describeContents() { return 0; @@ -348,7 +322,6 @@ public final class ZenDeviceEffects implements Parcelable { dest.writeBoolean(mDisableTouch); dest.writeBoolean(mMinimizeRadioUsage); dest.writeBoolean(mMaximizeDoze); - dest.writeInt(mUserModifiedFields); } /** Builder class for {@link ZenDeviceEffects} objects. */ @@ -365,7 +338,6 @@ public final class ZenDeviceEffects implements Parcelable { private boolean mDisableTouch; private boolean mMinimizeRadioUsage; private boolean mMaximizeDoze; - private @ModifiableField int mUserModifiedFields; /** * Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled). @@ -388,7 +360,6 @@ public final class ZenDeviceEffects implements Parcelable { mDisableTouch = zenDeviceEffects.shouldDisableTouch(); mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage(); mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze(); - mUserModifiedFields = zenDeviceEffects.mUserModifiedFields; } /** @@ -510,24 +481,13 @@ public final class ZenDeviceEffects implements Parcelable { return this; } - /** - * Sets the bitmask representing which fields are user modified. See the FIELD_ constants. - * @hide - */ - @TestApi - @NonNull - public Builder setUserModifiedFields(@ModifiableField int userModifiedFields) { - mUserModifiedFields = userModifiedFields; - return this; - } - /** Builds a {@link ZenDeviceEffects} object based on the builder's state. */ @NonNull public ZenDeviceEffects build() { return new ZenDeviceEffects(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode, mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch, mMinimizeRadioUsage, - mMaximizeDoze, mUserModifiedFields); + mMaximizeDoze); } } } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index d4d602da0071..c479877fe98e 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -206,7 +206,7 @@ public class ZenModeConfig implements Parcelable { private static final String ALLOW_ATT_CONV = "convos"; private static final String ALLOW_ATT_CONV_FROM = "convosFrom"; private static final String ALLOW_ATT_CHANNELS = "priorityChannels"; - private static final String USER_MODIFIED_FIELDS = "policyUserModifiedFields"; + private static final String POLICY_USER_MODIFIED_FIELDS = "policyUserModifiedFields"; private static final String DISALLOW_TAG = "disallow"; private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects"; private static final String STATE_TAG = "state"; @@ -806,6 +806,9 @@ public class ZenModeConfig implements Parcelable { rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC); rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN); rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0); + rt.zenPolicyUserModifiedFields = safeInt(parser, POLICY_USER_MODIFIED_FIELDS, 0); + rt.zenDeviceEffectsUserModifiedFields = safeInt(parser, + DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0); Long deletionInstant = tryParseLong( parser.getAttributeValue(null, RULE_ATT_DELETION_INSTANT), null); if (deletionInstant != null) { @@ -858,6 +861,9 @@ public class ZenModeConfig implements Parcelable { } out.attributeInt(null, RULE_ATT_TYPE, rule.type); out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields); + out.attributeInt(null, POLICY_USER_MODIFIED_FIELDS, rule.zenPolicyUserModifiedFields); + out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS, + rule.zenDeviceEffectsUserModifiedFields); if (rule.deletionInstant != null) { out.attributeLong(null, RULE_ATT_DELETION_INSTANT, rule.deletionInstant.toEpochMilli()); @@ -924,7 +930,6 @@ public class ZenModeConfig implements Parcelable { builder.allowPriorityChannels(channels == ZenPolicy.STATE_ALLOW); policySet = true; } - builder.setUserModifiedFields(safeInt(parser, USER_MODIFIED_FIELDS, 0)); } if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) { @@ -1037,7 +1042,6 @@ public class ZenModeConfig implements Parcelable { if (Flags.modesApi()) { writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannels(), out); - out.attributeInt(null, USER_MODIFIED_FIELDS, policy.getUserModifiedFields()); } } @@ -1083,7 +1087,6 @@ public class ZenModeConfig implements Parcelable { .setShouldMinimizeRadioUsage( safeBoolean(parser, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, false)) .setShouldMaximizeDoze(safeBoolean(parser, DEVICE_EFFECT_MAXIMIZE_DOZE, false)) - .setUserModifiedFields(safeInt(parser, DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0)) .build(); return deviceEffects.hasEffects() ? deviceEffects : null; @@ -1108,8 +1111,6 @@ public class ZenModeConfig implements Parcelable { writeBooleanIfTrue(out, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, deviceEffects.shouldMinimizeRadioUsage()); writeBooleanIfTrue(out, DEVICE_EFFECT_MAXIMIZE_DOZE, deviceEffects.shouldMaximizeDoze()); - out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS, - deviceEffects.getUserModifiedFields()); } private static void writeBooleanIfTrue(TypedXmlSerializer out, String att, boolean value) @@ -2041,7 +2042,9 @@ public class ZenModeConfig implements Parcelable { public String triggerDescription; public String iconResName; public boolean allowManualInvocation; - public int userModifiedFields; + @AutomaticZenRule.ModifiableField public int userModifiedFields; + @ZenPolicy.ModifiableField public int zenPolicyUserModifiedFields; + @ZenDeviceEffects.ModifiableField public int zenDeviceEffectsUserModifiedFields; @Nullable public Instant deletionInstant; // Only set on deleted rules. public ZenRule() { } @@ -2076,6 +2079,8 @@ public class ZenModeConfig implements Parcelable { triggerDescription = source.readString(); type = source.readInt(); userModifiedFields = source.readInt(); + zenPolicyUserModifiedFields = source.readInt(); + zenDeviceEffectsUserModifiedFields = source.readInt(); if (source.readInt() == 1) { deletionInstant = Instant.ofEpochMilli(source.readLong()); } @@ -2083,15 +2088,21 @@ public class ZenModeConfig implements Parcelable { } /** - * @see AutomaticZenRule#canUpdate() + * Whether this ZenRule can be updated by an app. In general, rules that have been + * customized by the user cannot be further updated by an app, with some exceptions: + * <ul> + * <li>Non user-configurable fields, like type, icon, configurationActivity, etc. + * <li>Name, if the name was not specifically modified by the user (to support language + * switches). + * </ul> */ @FlaggedApi(Flags.FLAG_MODES_API) public boolean canBeUpdatedByApp() { // The rule is considered updateable if its bitmask has no user modifications, and // the bitmasks of the policy and device effects have no modification. return userModifiedFields == 0 - && (zenPolicy == null || zenPolicy.getUserModifiedFields() == 0) - && (zenDeviceEffects == null || zenDeviceEffects.getUserModifiedFields() == 0); + && zenPolicyUserModifiedFields == 0 + && zenDeviceEffectsUserModifiedFields == 0; } @Override @@ -2139,6 +2150,8 @@ public class ZenModeConfig implements Parcelable { dest.writeString(triggerDescription); dest.writeInt(type); dest.writeInt(userModifiedFields); + dest.writeInt(zenPolicyUserModifiedFields); + dest.writeInt(zenDeviceEffectsUserModifiedFields); if (deletionInstant != null) { dest.writeInt(1); dest.writeLong(deletionInstant.toEpochMilli()); @@ -2173,8 +2186,20 @@ public class ZenModeConfig implements Parcelable { .append(",allowManualInvocation=").append(allowManualInvocation) .append(",iconResName=").append(iconResName) .append(",triggerDescription=").append(triggerDescription) - .append(",type=").append(type) - .append(",userModifiedFields=").append(userModifiedFields); + .append(",type=").append(type); + if (userModifiedFields != 0) { + sb.append(",userModifiedFields=") + .append(AutomaticZenRule.fieldsToString(userModifiedFields)); + } + if (zenPolicyUserModifiedFields != 0) { + sb.append(",zenPolicyUserModifiedFields=") + .append(ZenPolicy.fieldsToString(zenPolicyUserModifiedFields)); + } + if (zenDeviceEffectsUserModifiedFields != 0) { + sb.append(",zenDeviceEffectsUserModifiedFields=") + .append(ZenDeviceEffects.fieldsToString( + zenDeviceEffectsUserModifiedFields)); + } if (deletionInstant != null) { sb.append(",deletionInstant=").append(deletionInstant); } @@ -2238,6 +2263,9 @@ public class ZenModeConfig implements Parcelable { && Objects.equals(other.triggerDescription, triggerDescription) && other.type == type && other.userModifiedFields == userModifiedFields + && other.zenPolicyUserModifiedFields == zenPolicyUserModifiedFields + && other.zenDeviceEffectsUserModifiedFields + == zenDeviceEffectsUserModifiedFields && Objects.equals(other.deletionInstant, deletionInstant); } @@ -2250,7 +2278,8 @@ public class ZenModeConfig implements Parcelable { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, zenDeviceEffects, modified, allowManualInvocation, iconResName, - triggerDescription, type, userModifiedFields, deletionInstant); + triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields, + zenDeviceEffectsUserModifiedFields, deletionInstant); } return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, modified); diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index 2cda7c882cdb..fb491d010f54 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -45,8 +45,8 @@ import java.util.Objects; */ public final class ZenPolicy implements Parcelable { - /** Used to track which rule variables have been modified by the user. - * Should be checked against the bitmask {@link #getUserModifiedFields()}. + /** + * Enum for the user-modifiable fields in this object. * @hide */ @IntDef(flag = true, prefix = { "FIELD_" }, value = { @@ -76,7 +76,6 @@ public final class ZenPolicy implements Parcelable { * the same time. * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_MESSAGES = 1 << 0; /** @@ -84,7 +83,6 @@ public final class ZenPolicy implements Parcelable { * the same time. * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_CALLS = 1 << 1; /** @@ -92,13 +90,11 @@ public final class ZenPolicy implements Parcelable { * set at the same time. * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_CONVERSATIONS = 1 << 2; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_ALLOW_CHANNELS = 1 << 3; /** @@ -109,73 +105,61 @@ public final class ZenPolicy implements Parcelable { /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 1 << 5; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 6; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 1 << 7; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 1 << 8; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 1 << 9; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1 << 10; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_LIGHTS = 1 << 11; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_PEEK = 1 << 12; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 1 << 13; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_BADGE = 1 << 14; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_AMBIENT = 1 << 15; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 1 << 16; @@ -186,7 +170,6 @@ public final class ZenPolicy implements Parcelable { private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET; @FlaggedApi(Flags.FLAG_MODES_API) private @ChannelType int mAllowChannels = CHANNEL_POLICY_UNSET; - private final @ModifiableField int mUserModifiedFields; // Bitwise representation /** @hide */ @IntDef(prefix = { "PRIORITY_CATEGORY_" }, value = { @@ -388,22 +371,19 @@ public final class ZenPolicy implements Parcelable { public ZenPolicy() { mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0)); mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0)); - mUserModifiedFields = 0; } /** @hide */ @FlaggedApi(Flags.FLAG_MODES_API) public ZenPolicy(List<Integer> priorityCategories, List<Integer> visualEffects, @PeopleType int priorityMessages, @PeopleType int priorityCalls, - @ConversationSenders int conversationSenders, @ChannelType int allowChannels, - @ModifiableField int userModifiedFields) { + @ConversationSenders int conversationSenders, @ChannelType int allowChannels) { mPriorityCategories = priorityCategories; mVisualEffects = visualEffects; mPriorityMessages = priorityMessages; mPriorityCalls = priorityCalls; mConversationSenders = conversationSenders; mAllowChannels = allowChannels; - mUserModifiedFields = userModifiedFields; } /** @@ -633,8 +613,6 @@ public final class ZenPolicy implements Parcelable { * is not set, it is (@link STATE_UNSET} and will not change the current set policy. */ public static final class Builder { - private @ModifiableField int mUserModifiedFields; - private ZenPolicy mZenPolicy; public Builder() { @@ -649,9 +627,6 @@ public final class ZenPolicy implements Parcelable { public Builder(@Nullable ZenPolicy policy) { if (policy != null) { mZenPolicy = policy.copy(); - if (Flags.modesApi()) { - mUserModifiedFields = policy.mUserModifiedFields; - } } else { mZenPolicy = new ZenPolicy(); } @@ -662,11 +637,10 @@ public final class ZenPolicy implements Parcelable { */ public @NonNull ZenPolicy build() { if (Flags.modesApi()) { - return new ZenPolicy(new ArrayList<Integer>(mZenPolicy.mPriorityCategories), - new ArrayList<Integer>(mZenPolicy.mVisualEffects), + return new ZenPolicy(new ArrayList<>(mZenPolicy.mPriorityCategories), + new ArrayList<>(mZenPolicy.mVisualEffects), mZenPolicy.mPriorityMessages, mZenPolicy.mPriorityCalls, - mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels, - mUserModifiedFields); + mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels); } else { return mZenPolicy.copy(); } @@ -1025,28 +999,6 @@ public final class ZenPolicy implements Parcelable { mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE; return this; } - - /** - * Sets the user modified fields bitmask. - * @hide - */ - @TestApi - @FlaggedApi(Flags.FLAG_MODES_API) - public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) { - mUserModifiedFields = userModifiedFields; - return this; - } - } - - /** - Gets the bitmask representing which fields are user modified. Bits are set using - * {@link ModifiableField}. - * @hide - */ - @TestApi - @FlaggedApi(Flags.FLAG_MODES_API) - public @ModifiableField int getUserModifiedFields() { - return mUserModifiedFields; } @Override @@ -1063,7 +1015,6 @@ public final class ZenPolicy implements Parcelable { dest.writeInt(mConversationSenders); if (Flags.modesApi()) { dest.writeInt(mAllowChannels); - dest.writeInt(mUserModifiedFields); } } @@ -1079,7 +1030,7 @@ public final class ZenPolicy implements Parcelable { trimList(source.readArrayList(Integer.class.getClassLoader(), Integer.class), NUM_VISUAL_EFFECTS), source.readInt(), source.readInt(), source.readInt(), - source.readInt(), source.readInt() + source.readInt() ); } else { policy = new ZenPolicy(); @@ -1114,14 +1065,12 @@ public final class ZenPolicy implements Parcelable { conversationTypeToString(mConversationSenders)); if (Flags.modesApi()) { sb.append(", allowChannels=").append(channelTypeToString(mAllowChannels)); - sb.append(", userModifiedFields=") - .append(modifiedFieldsToString(mUserModifiedFields)); } return sb.append('}').toString(); } - @FlaggedApi(Flags.FLAG_MODES_API) - private String modifiedFieldsToString(@ModifiableField int bitmask) { + /** @hide */ + public static String fieldsToString(@ModifiableField int bitmask) { ArrayList<String> modified = new ArrayList<>(); if ((bitmask & FIELD_MESSAGES) != 0) { modified.add("FIELD_MESSAGES"); @@ -1332,8 +1281,7 @@ public final class ZenPolicy implements Parcelable { && other.mPriorityMessages == mPriorityMessages && other.mConversationSenders == mConversationSenders; if (Flags.modesApi()) { - return eq && other.mAllowChannels == mAllowChannels - && other.mUserModifiedFields == mUserModifiedFields; + return eq && other.mAllowChannels == mAllowChannels; } return eq; } @@ -1342,7 +1290,7 @@ public final class ZenPolicy implements Parcelable { public int hashCode() { if (Flags.modesApi()) { return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, - mPriorityMessages, mConversationSenders, mAllowChannels, mUserModifiedFields); + mPriorityMessages, mConversationSenders, mAllowChannels); } return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages, mConversationSenders); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 34b467008232..57174de8a62c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -26,6 +26,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -1010,8 +1011,10 @@ public final class ViewRootImpl implements ViewParent, // Used to check if there were any view invalidations in // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME). private boolean mHasInvalidation = false; - // Used to check if it is in the touch boosting period. + // Used to check if it is in the frame rate boosting period. private boolean mIsFrameRateBoosting = false; + // Used to check if it is in touch boosting period. + private boolean mIsTouchBoosting = false; // Used to check if there is a message in the message queue // for idleness handling. private boolean mHasIdledMessage = false; @@ -6426,11 +6429,12 @@ public final class ViewRootImpl implements ViewParent, * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). */ mIsFrameRateBoosting = false; + mIsTouchBoosting = false; setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory, mLastPreferredFrameRateCategory)); break; case MSG_CHECK_INVALIDATION_IDLE: - if (!mHasInvalidation && !mIsFrameRateBoosting) { + if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) { mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; setPreferredFrameRateCategory(mPreferredFrameRateCategory); mHasIdledMessage = false; @@ -7453,7 +7457,7 @@ public final class ViewRootImpl implements ViewParent, // For the variable refresh rate project if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { // set the frame rate to the maximum value. - mIsFrameRateBoosting = true; + mIsTouchBoosting = true; setPreferredFrameRateCategory(mPreferredFrameRateCategory); } /** @@ -12202,8 +12206,16 @@ public final class ViewRootImpl implements ViewParent, return; } - int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning - ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; + int frameRateCategory = mIsTouchBoosting + ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory; + + // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT + // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction. + // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction + // (e.g., Window Initialization). + if (mIsFrameRateBoosting || mInsetsAnimationRunning) { + frameRateCategory = FRAME_RATE_CATEGORY_HIGH; + } try { if (mLastPreferredFrameRateCategory != frameRateCategory) { diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index bceb8726b1cb..feae173f3e61 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -421,8 +421,11 @@ public final class TransitionInfo implements Parcelable { final String perChangeLineStart = shouldPrettyPrint ? "\n" + innerPrefix : ""; StringBuilder sb = new StringBuilder(); sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType)) - .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack) - .append(" r=["); + .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack); + if (mOptions != null) { + sb.append(" opt=").append(mOptions); + } + sb.append(" r=["); for (int i = 0; i < mRoots.size(); ++i) { if (i > 0) { sb.append(','); @@ -1211,21 +1214,31 @@ public final class TransitionInfo implements Parcelable { @NonNull private static String typeToString(int mode) { - switch(mode) { - case ANIM_CUSTOM: return "ANIM_CUSTOM"; - case ANIM_CLIP_REVEAL: return "ANIM_CLIP_REVEAL"; - case ANIM_SCALE_UP: return "ANIM_SCALE_UP"; - case ANIM_THUMBNAIL_SCALE_UP: return "ANIM_THUMBNAIL_SCALE_UP"; - case ANIM_THUMBNAIL_SCALE_DOWN: return "ANIM_THUMBNAIL_SCALE_DOWN"; - case ANIM_OPEN_CROSS_PROFILE_APPS: return "ANIM_OPEN_CROSS_PROFILE_APPS"; - default: return "<unknown:" + mode + ">"; - } + return switch (mode) { + case ANIM_CUSTOM -> "CUSTOM"; + case ANIM_SCALE_UP -> "SCALE_UP"; + case ANIM_THUMBNAIL_SCALE_UP -> "THUMBNAIL_SCALE_UP"; + case ANIM_THUMBNAIL_SCALE_DOWN -> "THUMBNAIL_SCALE_DOWN"; + case ANIM_SCENE_TRANSITION -> "SCENE_TRANSITION"; + case ANIM_CLIP_REVEAL -> "CLIP_REVEAL"; + case ANIM_OPEN_CROSS_PROFILE_APPS -> "OPEN_CROSS_PROFILE_APPS"; + case ANIM_FROM_STYLE -> "FROM_STYLE"; + default -> "<" + mode + ">"; + }; } @Override public String toString() { - return "{ AnimationOptions type= " + typeToString(mType) + " package=" + mPackageName - + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}"; + final StringBuilder sb = new StringBuilder(32); + sb.append("{t=").append(typeToString(mType)); + if (mOverrideTaskTransition) { + sb.append(" overrideTask=true"); + } + if (!mTransitionBounds.isEmpty()) { + sb.append(" bounds=").append(mTransitionBounds); + } + sb.append('}'); + return sb.toString(); } /** Customized activity transition. */ diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index bb16ad2cb8de..2b96ee6705fd 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -69,11 +69,10 @@ flag { } flag { - name: "predictive_back_system_animations" + name: "predictive_back_system_anims" namespace: "systemui" description: "Predictive back for system animations" - bug: "319421778" - is_fixed_read_only: true + bug: "320510464" } flag { diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java index 04886598de35..342ba1b67e4d 100644 --- a/core/java/com/android/internal/util/RingBuffer.java +++ b/core/java/com/android/internal/util/RingBuffer.java @@ -19,7 +19,10 @@ package com.android.internal.util; import static com.android.internal.util.Preconditions.checkArgumentPositive; import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; import java.util.Arrays; +import java.util.function.IntFunction; +import java.util.function.Supplier; /** * A simple ring buffer structure with bounded capacity backed by an array. @@ -30,16 +33,35 @@ import java.util.Arrays; @android.ravenwood.annotation.RavenwoodKeepWholeClass public class RingBuffer<T> { + private final Supplier<T> mNewItem; // Array for storing events. private final T[] mBuffer; // Cursor keeping track of the logical end of the array. This cursor never // wraps and instead keeps track of the total number of append() operations. private long mCursor = 0; + /** + * @deprecated This uses reflection to create new instances. + * Use {@link #RingBuffer(Supplier, IntFunction, int)}} instead. + */ + @Deprecated public RingBuffer(Class<T> c, int capacity) { + this(() -> (T) createNewItem(c), cap -> (T[]) Array.newInstance(c, cap), capacity); + } + + private static Object createNewItem(Class c) { + try { + return c.getDeclaredConstructor().newInstance(); + } catch (IllegalAccessException | InstantiationException | NoSuchMethodException + | InvocationTargetException e) { + return null; + } + } + + public RingBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) { checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity"); - // Java cannot create generic arrays without a runtime hint. - mBuffer = (T[]) Array.newInstance(c, capacity); + mBuffer = newBacking.apply(capacity); + mNewItem = newItem; } public int size() { @@ -69,22 +91,11 @@ public class RingBuffer<T> { public T getNextSlot() { final int nextSlotIdx = indexOf(mCursor++); if (mBuffer[nextSlotIdx] == null) { - mBuffer[nextSlotIdx] = createNewItem(); + mBuffer[nextSlotIdx] = mNewItem.get(); } return mBuffer[nextSlotIdx]; } - /** - * @return a new object of type <T> or null if a new object could not be created. - */ - protected T createNewItem() { - try { - return (T) mBuffer.getClass().getComponentType().newInstance(); - } catch (IllegalAccessException | InstantiationException e) { - return null; - } - } - public T[] toArray() { // Only generic way to create a T[] from another T[] T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass()); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5f3f6419418a..4a82675715d8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7896,6 +7896,18 @@ <permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" android:protectionLevel="signature|role" /> + <!-- @SystemApi + @FlaggedApi("android.app.bic_client") + Allows app to call BackgroundInstallControlManager API to retrieve silently installed apps + for all users on device. + <p>Apps with a BackgroundInstallControlManager client will not be able to call any API without + this permission. + <p>Protection level: signature|role + @hide + --> + <permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" + android:protectionLevel="signature|role" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml index f3acab063bbf..fe12f6ee7836 100644 --- a/core/res/res/drawable-nodpi/platlogo.xml +++ b/core/res/res/drawable-nodpi/platlogo.xml @@ -1,5 +1,5 @@ <!-- -Copyright (C) 2021 The Android Open Source Project +Copyright (C) 2024 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,179 +20,103 @@ Copyright (C) 2021 The Android Open Source Project android:viewportWidth="512" android:viewportHeight="512"> <path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"> + android:pathData="M256.22,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98s-24.52,-54.37 -11.33,-77.21c13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21s-103.26,178.86 -120.65,208.98c-17.39,30.12 -34.83,48.42 -61.2,48.42Z" + android:strokeWidth="0"> <aapt:attr name="android:fillColor"> <gradient - android:startX="256" - android:startY="21.81" - android:endX="256" - android:endY="350.42" + android:startX="56.22" + android:startY="256" + android:endX="456.22" + android:endY="256" android:type="linear"> <item android:offset="0" android:color="#FF073042"/> <item android:offset="1" android:color="#FF073042"/> </gradient> </aapt:attr> </path> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z" - android:fillColor="#3ddc84"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z" - android:fillColor="#3ddc84"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z" - android:fillColor="#3ddc84"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M171.92,216.82h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M188.8,275.73h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M369.04,337.63h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M285.93,252.22h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M318.96,218.84h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M294.05,288.55h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M330.82,273.31h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M188.8,298.95h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M220.14,238.94h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M272.1,318.9h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M293.34,349.25h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M161.05,254.24h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M378.92,192h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M137.87,323.7h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0" - android:strokeWidth="56.561" - android:fillColor="#00000000" - android:strokeColor="#f86734"/> <path - android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z" + android:pathData="M198.85,168.57l2.03,-6.03c12.55,70.48 45.87,256.56 45.87,45.41 0,-0.88 0.65,-1.63 1.46,-1.63h11.45c0.81,0 1.46,0.75 1.46,1.63 -0.18,369.8 -62.28,-39.37 -62.28,-39.37Z" + android:strokeWidth="0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M186.69,167.97l-23.69,41.03c-1.36,2.36 -0.55,5.37 1.8,6.73 2.36,1.36 5.37,0.55 6.73,-1.8l23.99,-41.55c38.76,17.38 83.1,17.38 121.86,0l23.99,41.55c1.41,2.33 4.44,3.07 6.77,1.66 2.26,-1.37 3.04,-4.28 1.76,-6.59l-23.69,-41.03c40.68,-22.12 68.5,-63.31 72.57,-111.97H114.11c4.07,48.65 31.89,89.85 72.57,111.97Z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M248.46,168.59L259.2,168.59A1.92,1.92 0,0 1,261.12 170.5L261.12,195.83A1.92,1.92 0,0 1,259.2 197.75L248.46,197.75A1.92,1.92 0,0 1,246.54 195.83L246.54,170.5A1.92,1.92 0,0 1,248.46 168.59z" + android:strokeWidth="0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M248.32,152.92L259.34,152.92A1.78,1.78 0,0 1,261.12 154.7L261.12,158.43A1.78,1.78 0,0 1,259.34 160.21L248.32,160.21A1.78,1.78 0,0 1,246.54 158.43L246.54,154.7A1.78,1.78 0,0 1,248.32 152.92z" + android:strokeWidth="0" android:fillColor="#3ddc84"/> <path - android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z" + android:pathData="M159.03,176.91h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M188.8,275.73h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M373.41,158.93h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M112.1,129.34h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M285.93,252.22h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M318.96,218.84h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M294.05,288.55h4.04v4.04h-4.04z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z" + android:pathData="M330.82,263.31h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z" + android:pathData="M188.8,298.95h4.04v4.04h-4.04z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z" + android:pathData="M224.18,216.82h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z" + android:pathData="M272.1,318.9h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z" + android:pathData="M293.34,339.25h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z" + android:pathData="M165.09,236.28h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z" + android:pathData="M378.92,192h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z" + android:pathData="M204.28,314.86h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> + <path + android:pathData="M253.83,118.47c-6.04,0 -10.93,4.76 -10.93,10.62v13.1c0,1.13 0.92,2.05 2.05,2.05h0c1.13,0 2.05,-0.92 2.05,-2.05v-3.53c0,-2.27 1.84,-4.1 4.1,-4.1h5.47c2.27,0 4.1,1.84 4.1,4.1v3.53c0,1.13 0.92,2.05 2.05,2.05s2.05,-0.92 2.05,-2.05v-13.1c0,-5.86 -4.9,-10.62 -10.93,-10.62Z" + android:strokeWidth="0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M256,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98 -17.39,-30.12 -24.52,-54.37 -11.33,-77.21 13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21 -17.39,30.12 -103.26,178.86 -120.65,208.98 -17.39,30.12 -34.83,48.42 -61.2,48.42Z" + android:strokeWidth="55" + android:fillColor="#00000000" + android:strokeColor="#f86733"/> </vector> - diff --git a/core/tests/BroadcastRadioTests/TEST_MAPPING b/core/tests/BroadcastRadioTests/TEST_MAPPING new file mode 100644 index 000000000000..b085a27b25b8 --- /dev/null +++ b/core/tests/BroadcastRadioTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "BroadcastRadioTests" + } + ] +} diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java index 9d85b65d4dac..1925588e8904 100644 --- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java +++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java @@ -16,8 +16,6 @@ package android.app; -import static com.google.common.truth.Truth.assertThat; - import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; @@ -28,8 +26,6 @@ import android.net.Uri; import android.os.Parcel; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; -import android.service.notification.ZenDeviceEffects; -import android.service.notification.ZenPolicy; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -230,66 +226,4 @@ public class AutomaticZenRuleTest { assertThrows(IllegalArgumentException.class, () -> builder.setType(100)); } - - @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void testCanUpdate_nullPolicyAndDeviceEffects() { - AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name", - Uri.parse("uri://short")); - - AutomaticZenRule rule = builder.setUserModifiedFields(0) - .setZenPolicy(null) - .setDeviceEffects(null) - .build(); - - assertThat(rule.canUpdate()).isTrue(); - - rule = builder.setUserModifiedFields(1).build(); - assertThat(rule.canUpdate()).isFalse(); - } - - @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void testCanUpdate_policyModified() { - ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0); - ZenPolicy policy = policyBuilder.build(); - - AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name", - Uri.parse("uri://short")); - AutomaticZenRule rule = builder.setUserModifiedFields(0) - .setZenPolicy(policy) - .setDeviceEffects(null).build(); - - // Newly created ZenPolicy is not user modified. - assertThat(policy.getUserModifiedFields()).isEqualTo(0); - assertThat(rule.canUpdate()).isTrue(); - - policy = policyBuilder.setUserModifiedFields(1).build(); - assertThat(policy.getUserModifiedFields()).isEqualTo(1); - rule = builder.setZenPolicy(policy).build(); - assertThat(rule.canUpdate()).isFalse(); - } - - @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void testCanUpdate_deviceEffectsModified() { - ZenDeviceEffects.Builder deviceEffectsBuilder = - new ZenDeviceEffects.Builder().setUserModifiedFields(0); - ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build(); - - AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name", - Uri.parse("uri://short")); - AutomaticZenRule rule = builder.setUserModifiedFields(0) - .setZenPolicy(null) - .setDeviceEffects(deviceEffects).build(); - - // Newly created ZenDeviceEffects is not user modified. - assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(0); - assertThat(rule.canUpdate()).isTrue(); - - deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build(); - assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1); - rule = builder.setDeviceEffects(deviceEffects).build(); - assertThat(rule.canUpdate()).isFalse(); - } } diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt index 1617eda6a77c..e32a57b1aefe 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt @@ -48,7 +48,7 @@ class FontScaleConverterFactoryTest { @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() - private lateinit var defaultLookupTables: SparseArray<FontScaleConverter> + private var defaultLookupTables: SparseArray<FontScaleConverter>? = null @Before fun setup() { @@ -58,7 +58,9 @@ class FontScaleConverterFactoryTest { @After fun teardown() { // Restore the default tables (since some tests will have added extras to the cache) - FontScaleConverterFactory.sLookupTables = defaultLookupTables + if (defaultLookupTables != null) { + FontScaleConverterFactory.sLookupTables = defaultLookupTables!! + } } @Test diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index cf3eb12498ca..cfbda84f24e1 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -19,6 +19,7 @@ package android.view; import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR; import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; @@ -575,8 +576,13 @@ public class ViewRootImplTest { assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_HIGH_HINT); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index df99a163e3d2..4be75f83422e 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -542,6 +542,8 @@ applications that come with the platform <permission name="android.permission.GET_BINDING_UID_IMPORTANCE"/> <!-- Permission required for CTS test NotificationManagerZenTest --> <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> + <!-- Permission required for BinaryTransparencyService shell API and host test --> + <permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index bb433dbbd2ce..e7f6f0d61847 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -17,7 +17,7 @@ package com.android.wm.shell.back; import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME; -import static com.android.window.flags.Flags.predictiveBackSystemAnimations; +import static com.android.window.flags.Flags.predictiveBackSystemAnims; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; @@ -244,7 +244,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void setupAnimationDeveloperSettingsObserver( @NonNull ContentResolver contentResolver, @NonNull @ShellBackgroundThread final Handler backgroundHandler) { - if (predictiveBackSystemAnimations()) { + if (predictiveBackSystemAnims()) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore " + "developer settings flag is ignored and no content observer registered"); return; @@ -267,7 +267,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ @ShellBackgroundThread private void updateEnableAnimationFromFlags() { - boolean isEnabled = predictiveBackSystemAnimations() || isDeveloperSettingEnabled(); + boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled(); mEnableAnimations.set(isEnabled); ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index e48732801094..bb0dd95b042f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -769,8 +769,10 @@ public class StackAnimationController extends boolean swapped = false; for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) { View view = bubbleViews.get(newIndex); - final int oldIndex = mLayout.indexOfChild(view); - swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after); + if (view != null) { + final int oldIndex = mLayout.indexOfChild(view); + swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after); + } } if (!swapped) { // All bubbles were at the right position. Make sure badges and z order is correct. diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index eb4d4948dc53..ac2a9366a1f6 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -124,24 +124,19 @@ void CacheManager::trimMemory(TrimLevel mode) { // flush and submit all work to the gpu and wait for it to finish mGrContext->flushAndSubmit(GrSyncCpu::kYes); - switch (mode) { - case TrimLevel::BACKGROUND: - mGrContext->freeGpuResources(); - SkGraphics::PurgeAllCaches(); - mRenderThread.destroyRenderingContext(); - break; - case TrimLevel::UI_HIDDEN: - // Here we purge all the unlocked scratch resources and then toggle the resources cache - // limits between the background and max amounts. This causes the unlocked resources - // that have persistent data to be purged in LRU order. - mGrContext->setResourceCacheLimit(mBackgroundResourceBytes); - SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes); - mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly)); - mGrContext->setResourceCacheLimit(mMaxResourceBytes); - SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes); - break; - default: - break; + if (mode >= TrimLevel::BACKGROUND) { + mGrContext->freeGpuResources(); + SkGraphics::PurgeAllCaches(); + mRenderThread.destroyRenderingContext(); + } else if (mode == TrimLevel::UI_HIDDEN) { + // Here we purge all the unlocked scratch resources and then toggle the resources cache + // limits between the background and max amounts. This causes the unlocked resources + // that have persistent data to be purged in LRU order. + mGrContext->setResourceCacheLimit(mBackgroundResourceBytes); + SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes); + mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly)); + mGrContext->setResourceCacheLimit(mMaxResourceBytes); + SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes); } } diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index b002bbf20c08..ea136edf46be 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -1079,7 +1079,7 @@ public final class AudioFormat implements Parcelable { * @return one of the values that can be set in {@link Builder#setEncoding(int)} or * {@link AudioFormat#ENCODING_INVALID} if not set. */ - public int getEncoding() { + public @Encoding int getEncoding() { return mEncoding; } diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 5da4b9518a31..c2cb75709b45 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -9,6 +9,9 @@ package { android_library { name: "SettingsLib", + defaults: [ + "SettingsLintDefaults", + ], static_libs: [ "androidx.localbroadcastmanager_localbroadcastmanager", @@ -60,8 +63,15 @@ android_library { "src/**/*.java", "src/**/*.kt", ], +} + +// defaults for lint option +java_defaults { + name: "SettingsLintDefaults", lint: { - extra_check_modules: ["SettingsLibLintChecker"], + extra_check_modules: [ + "SettingsLibLintChecker", + ], }, } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index 1c830c1c5b06..74b556ea106e 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -162,6 +162,7 @@ internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionApp uid = checkNotNull(applicationInfo).uid, packageName = packageName) }) RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory) + InfoPageAdditionalContent(record, isAllowed) } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt index 916d83af3f8f..3f7a8526839f 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt @@ -77,6 +77,9 @@ interface TogglePermissionAppListModel<T : AppRecord> { * Sets whether the permission is allowed for the given app. */ fun setAllowed(record: T, newAllowed: Boolean) + + @Composable + fun InfoPageAdditionalContent(record: T, isAllowed: () -> Boolean?){} } interface TogglePermissionAppListProvider { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 071afaf18598..f7f06739d4b2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -117,6 +117,12 @@ public class BluetoothUtils { } } + if (cachedDevice.isHearingAidDevice()) { + return new Pair<>(getBluetoothDrawable(context, + com.android.internal.R.drawable.ic_bt_hearing_aid), + context.getString(R.string.bluetooth_talkback_hearing_aids)); + } + List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles(); int resId = 0; for (LocalBluetoothProfile profile : profiles) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 032a8381a7b5..560bc467ca8e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -412,6 +412,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> public void setHearingAidInfo(HearingAidInfo hearingAidInfo) { mHearingAidInfo = hearingAidInfo; + dispatchAttributesChanged(); } public HearingAidInfo getHearingAidInfo() { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index 7409eea2cd51..f7ec80b041e9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -16,6 +16,7 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -87,6 +88,14 @@ public class BluetoothUtilsTest { } @Test + public void getBtClassDrawableWithDescription_typeHearingAid_returnHearingAidDrawable() { + when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true); + BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedBluetoothDevice); + + verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_hearing_aid); + } + + @Test public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() { when(mBluetoothDevice.getMetadata( BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("false".getBytes()); diff --git a/packages/SettingsProvider/TEST_MAPPING b/packages/SettingsProvider/TEST_MAPPING index 890510ffebe3..0eed2b7490d4 100644 --- a/packages/SettingsProvider/TEST_MAPPING +++ b/packages/SettingsProvider/TEST_MAPPING @@ -11,5 +11,10 @@ } ] } + ], + "postsubmit": [ + { + "name": "CtsDeviceConfigTestCases" + } ] } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig index ecac5ee18582..edbc0b391b27 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig +++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig @@ -14,3 +14,11 @@ flag { bug: "311155098" is_fixed_read_only: true } + +flag { + name: "configurable_font_scale_default" + namespace: "large_screen_experiences_app_compat" + description: "Whether the font_scale is read from a device dependent configuration file" + bug: "319808237" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index e0651877df7e..3dfc4540d6e7 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -894,6 +894,9 @@ <!-- Permission required for Cts test - CtsNotificationTestCases --> <uses-permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" /> + <!-- Permission required for BinaryTransparencyService shell API and host test --> + <uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 7443e4ccf79e..168e6e003dc6 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -355,6 +355,8 @@ <uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" /> + <uses-permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" /> + <!-- Listen to (dis-)connection of external displays and enable / disable them. --> <uses-permission android:name="android.permission.MANAGE_DISPLAYS" /> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index c23a49c68363..b464498d10f6 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -343,3 +343,10 @@ flag { description: "Relocate Smartspace to bottom of the Lock Screen" bug: "316212788" } + +flag { + name: "pin_input_field_styled_focus_state" + namespace: "systemui" + description: "Enables styled focus states on pin input field if keyboard is connected" + bug: "316106516" +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index 900616f6af89..42fcd1363f11 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -16,23 +16,55 @@ package com.android.systemui.keyguard.ui.composable.section +import android.content.Context +import android.view.ViewGroup import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.android.compose.animation.scene.SceneScope +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.notifications.ui.composable.NotificationStack +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.statusbar.notification.stack.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject class NotificationSection @Inject constructor( + @Application context: Context, private val viewModel: NotificationsPlaceholderViewModel, + controller: NotificationStackScrollLayoutController, + sceneContainerFlags: SceneContainerFlags, + sharedNotificationContainer: SharedNotificationContainer, + stackScrollLayout: NotificationStackScrollLayout, + notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel, + ambientState: AmbientState, ) { + init { + if (sceneContainerFlags.flexiNotifsEnabled()) { + (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout) + sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout) + + NotificationStackAppearanceViewBinder.bind( + context, + sharedNotificationContainer, + notificationStackAppearanceViewModel, + ambientState, + controller, + ) + } + } + @Composable fun SceneScope.Notifications(modifier: Modifier = Modifier) { NotificationStack( viewModel = viewModel, - isScrimVisible = false, modifier = modifier, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 0b26ae96de54..e835d3e576d5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme @@ -43,8 +44,10 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.modifiers.height import com.android.systemui.notifications.ui.composable.Notifications.Form import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel +import kotlin.math.roundToInt object Notifications { object Elements { @@ -77,32 +80,52 @@ fun SceneScope.HeadsUpNotificationSpace( ) } -/** Adds the space where notification stack will appear in the scene. */ +/** Adds the space where notification stack should appear in the scene. */ @Composable fun SceneScope.NotificationStack( viewModel: NotificationsPlaceholderViewModel, - isScrimVisible: Boolean, + modifier: Modifier = Modifier, +) { + NotificationPlaceholder( + viewModel = viewModel, + form = Form.Stack, + modifier = modifier, + ) +} + +/** + * Adds the space where notification stack should appear in the scene, with a scrim and nested + * scrolling. + */ +@Composable +fun SceneScope.NotificationScrollingStack( + viewModel: NotificationsPlaceholderViewModel, modifier: Modifier = Modifier, ) { val cornerRadius by viewModel.cornerRadiusDp.collectAsState() - Box(modifier = modifier) { - if (isScrimVisible) { - Box( - modifier = - Modifier.element(Notifications.Elements.NotificationScrim) - .fillMaxSize() - .graphicsLayer { - shape = RoundedCornerShape(cornerRadius.dp) - clip = true - } - .background(MaterialTheme.colorScheme.surface) - ) - } + val contentHeight by viewModel.intrinsicContentHeight.collectAsState() + + val expansionFraction by viewModel.expandFraction.collectAsState(0f) + + Box( + modifier = + modifier + .verticalNestedScrollToScene() + .fillMaxWidth() + .element(Notifications.Elements.NotificationScrim) + .graphicsLayer { + shape = RoundedCornerShape(cornerRadius.dp) + clip = true + alpha = expansionFraction + } + .background(MaterialTheme.colorScheme.surface) + .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f)) + ) { NotificationPlaceholder( viewModel = viewModel, form = Form.Stack, - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxWidth().height { contentHeight.roundToInt() } ) } } @@ -159,6 +182,7 @@ private fun SceneScope.NotificationPlaceholder( debugLog(viewModel) { "STACK onSizeChanged: size=$size" } } .onPlaced { coordinates: LayoutCoordinates -> + viewModel.onContentTopChanged(coordinates.positionInWindow().y) debugLog(viewModel) { "STACK onPlaced:" + " size=${coordinates.size}" + diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index bded98d52481..747faabe514b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace +import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.Edge import com.android.systemui.scene.shared.model.SceneKey @@ -66,6 +67,7 @@ constructor( modifier: Modifier, ) { Box(modifier = modifier) { + Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim)) HeadsUpNotificationSpace( viewModel = notificationsViewModel, modifier = Modifier.padding(16.dp).fillMaxSize(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt index 6bb525aa00fb..0c2c5195becc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt @@ -3,12 +3,12 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder -import com.android.systemui.notifications.ui.composable.Notifications -import com.android.systemui.scene.ui.composable.Shade +import com.android.systemui.qs.ui.composable.QuickSettings +import com.android.systemui.shade.ui.composable.ShadeHeader fun TransitionBuilder.goneToShadeTransition() { spec = tween(durationMillis = 500) - translate(Shade.rootElementKey, Edge.Top, true) - fade(Notifications.Elements.NotificationScrim) + fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) } + translate(QuickSettings.Elements.Content, Edge.Top, true) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 9c0f1fe0ec68..085dcf44fa69 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -22,11 +22,9 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -47,7 +45,7 @@ import com.android.systemui.media.controls.ui.MediaCarouselController import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL -import com.android.systemui.notifications.ui.composable.NotificationStack +import com.android.systemui.notifications.ui.composable.NotificationScrollingStack import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Direction @@ -148,35 +146,27 @@ private fun SceneScope.ShadeScene( mediaHost: MediaHost, modifier: Modifier = Modifier, ) { + val localDensity = LocalDensity.current val layoutWidth = remember { mutableStateOf(0) } - Box(modifier.element(Shade.Elements.Scrim)) { - Spacer( - modifier = - Modifier.element(Shade.Elements.ScrimBackground) - .fillMaxSize() - .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim) - ) + Box( + modifier = + modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim), + ) + Box { Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = - Modifier.fillMaxSize() - .clickable(onClick = { viewModel.onContentClicked() }) - .padding( - start = Shade.Dimensions.HorizontalPadding, - end = Shade.Dimensions.HorizontalPadding, - bottom = 48.dp - ) + modifier = Modifier.fillMaxWidth().clickable(onClick = { viewModel.onContentClicked() }) ) { CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, + modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding) ) - Spacer(modifier = Modifier.height(16.dp)) QuickSettings( - modifier = Modifier.wrapContentHeight(), + modifier = Modifier.height(130.dp), viewModel.qsSceneAdapter, ) @@ -202,16 +192,15 @@ private fun SceneScope.ShadeScene( }, mediaHost = mediaHost, layoutWidth = layoutWidth.value, - layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(), + layoutHeight = with(localDensity) { mediaHeight.toPx() }.toInt(), carouselController = mediaCarouselController, ) } Spacer(modifier = Modifier.height(16.dp)) - NotificationStack( + NotificationScrollingStack( viewModel = viewModel.notifications, - isScrimVisible = true, - modifier = Modifier.weight(1f), + modifier = Modifier.fillMaxWidth().weight(1f), ) } } diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml index 8fe9656c1879..fc337fb19f43 100644 --- a/packages/SystemUI/compose/features/tests/AndroidManifest.xml +++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml @@ -30,11 +30,6 @@ android:enabled="false" tools:replace="android:authorities" tools:node="remove" /> - <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle" - android:authorities="com.android.systemui.test.keyguard.disabled" - android:enabled="false" - tools:replace="android:authorities" - tools:node="remove" /> <provider android:name="com.android.systemui.keyguard.CustomizationProvider" android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled" android:enabled="false" diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index e89316997fb2..c4bcb536de78 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -35,6 +35,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -107,7 +108,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener, mEmergencyButtonController, mFalsingCollector, featureFlags, - mSelectedUserInteractor) { + mSelectedUserInteractor, new FakeKeyboardRepository()) { @Override public void onResume(int reason) { super.onResume(reason); diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 78b854e39d13..c2efc05132ba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository import com.android.systemui.res.R import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED @@ -141,7 +142,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { postureController, featureFlags, mSelectedUserInteractor, - uiEventLogger + uiEventLogger, + FakeKeyboardRepository() ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index f7751753cc18..0959f1b2bcf6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -16,7 +16,6 @@ package com.android.keyguard -import android.telephony.PinResult import android.telephony.TelephonyManager import android.testing.TestableLooper import android.view.LayoutInflater @@ -28,9 +27,11 @@ import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository import com.android.systemui.res.R import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -39,7 +40,6 @@ import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.anyInt -import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -75,8 +75,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { `when`(messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java))) .thenReturn(keyguardMessageAreaController) `when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager) - `when`(telephonyManager.supplyIccLockPin(anyString())) - .thenReturn(mock(PinResult::class.java)) + `when`(telephonyManager.supplyIccLockPin(anyString())).thenReturn(mock()) simPinView = LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null) as KeyguardSimPinView @@ -97,7 +96,8 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { falsingCollector, emergencyButtonController, fakeFeatureFlags, - mSelectedUserInteractor + mSelectedUserInteractor, + FakeKeyboardRepository() ) underTest.init() underTest.onViewAttached() diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index 45a60199984b..1281e4409a83 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository import com.android.systemui.res.R import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any @@ -91,6 +92,7 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { emergencyButtonController, fakeFeatureFlags, mSelectedUserInteractor, + FakeKeyboardRepository() ) underTest.init() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt index 27d1eb741bb0..c86c7470909b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt @@ -17,14 +17,15 @@ package com.android.systemui.accessibility.data.repository import android.os.UserHandle +import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -51,6 +52,7 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { underTest = ColorCorrectionRepositoryImpl( testDispatcher, + scope.backgroundScope, settings, ) } @@ -58,83 +60,78 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { @Test fun isEnabled_initiallyGetsSettingsValue() = scope.runTest { + val actualValue by collectLastValue(underTest.isEnabled(testUser1)) + settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - 1, + SETTING_NAME, + ENABLED, testUser1.identifier ) - - underTest = - ColorCorrectionRepositoryImpl( - testDispatcher, - settings, - ) - - underTest.isEnabled(testUser1).launchIn(backgroundScope) runCurrent() - val actualValue: Boolean = underTest.isEnabled(testUser1).first() Truth.assertThat(actualValue).isTrue() } @Test fun isEnabled_settingUpdated_valueUpdated() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1)) settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse() settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.ENABLED, + SETTING_NAME, + ENABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue() settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse() + + Truth.assertThat(flowValues.size).isEqualTo(3) + Truth.assertThat(flowValues).containsExactly(false, true, false).inOrder() } @Test fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1)) + val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2)) + settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser1.identifier ) - underTest.isEnabled(testUser2).launchIn(backgroundScope) settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser2.identifier ) - runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse() - Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse() + + Truth.assertThat(lastValueUser1).isFalse() + Truth.assertThat(lastValueUser2).isFalse() settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.ENABLED, + SETTING_NAME, + ENABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue() - Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse() + + Truth.assertThat(lastValueUser1).isTrue() + Truth.assertThat(lastValueUser2).isFalse() } @Test @@ -146,10 +143,10 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { val actualValue = settings.getIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, + SETTING_NAME, testUser1.identifier ) - Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.ENABLED) + Truth.assertThat(actualValue).isEqualTo(ENABLED) } @Test @@ -161,9 +158,15 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { val actualValue = settings.getIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, + SETTING_NAME, testUser1.identifier ) - Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.DISABLED) + Truth.assertThat(actualValue).isEqualTo(DISABLED) } + + companion object { + private const val SETTING_NAME = ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED + private const val DISABLED = 0 + private const val ENABLED = 1 + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt index 423e124bbc84..4853529229fe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt @@ -21,11 +21,11 @@ import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -52,6 +52,7 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() { underTest = ColorInversionRepositoryImpl( testDispatcher, + scope.backgroundScope, settings, ) } @@ -59,55 +60,47 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() { @Test fun isEnabled_initiallyGetsSettingsValue() = scope.runTest { - settings.putIntForUser(SETTING_NAME, 1, testUser1.identifier) + val actualValue by collectLastValue(underTest.isEnabled(testUser1)) - underTest = - ColorInversionRepositoryImpl( - testDispatcher, - settings, - ) - - underTest.isEnabled(testUser1).launchIn(backgroundScope) + settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) runCurrent() - val actualValue: Boolean = underTest.isEnabled(testUser1).first() assertThat(actualValue).isTrue() } @Test fun isEnabled_settingUpdated_valueUpdated() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val flowValues: List<Boolean> by + collectValues(underTest.isEnabled(testUser1)) settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isFalse() - settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isTrue() - settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isFalse() + + assertThat(flowValues.size).isEqualTo(3) + assertThat(flowValues).containsExactly(false, true, false).inOrder() } @Test fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1)) + val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2)) + settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier) - underTest.isEnabled(testUser2).launchIn(backgroundScope) settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier) - runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isFalse() - assertThat(underTest.isEnabled(testUser2).first()).isFalse() + assertThat(lastValueUser1).isFalse() + assertThat(lastValueUser2).isFalse() settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isTrue() - assertThat(underTest.isEnabled(testUser2).first()).isFalse() + assertThat(lastValueUser1).isTrue() + assertThat(lastValueUser2).isFalse() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt new file mode 100644 index 000000000000..820bfbfdf0a2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.data.repository + +import android.content.SharedPreferences +import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.settings.UserFileManager +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.FakeSharedPreferences +import com.google.common.truth.Truth.assertThat +import java.io.File +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalPrefsRepositoryImplTest : SysuiTestCase() { + private lateinit var underTest: CommunalPrefsRepositoryImpl + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private lateinit var userRepository: FakeUserRepository + private lateinit var userFileManager: UserFileManager + + @Before + fun setUp() { + userRepository = kosmos.fakeUserRepository + userRepository.setUserInfos(USER_INFOS) + + userFileManager = + FakeUserFileManager( + mapOf( + USER_INFOS[0].id to FakeSharedPreferences(), + USER_INFOS[1].id to FakeSharedPreferences() + ) + ) + underTest = + CommunalPrefsRepositoryImpl( + testScope.backgroundScope, + kosmos.testDispatcher, + userRepository, + userFileManager, + ) + } + + @Test + fun isCtaDismissedValue_byDefault_isFalse() = + testScope.runTest { + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + assertThat(isCtaDismissed).isFalse() + } + + @Test + fun isCtaDismissedValue_onSet_isTrue() = + testScope.runTest { + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + + underTest.setCtaDismissedForCurrentUser() + assertThat(isCtaDismissed).isTrue() + } + + @Test + fun isCtaDismissedValue_whenSwitchUser() = + testScope.runTest { + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + underTest.setCtaDismissedForCurrentUser() + + // dismissed true for primary user + assertThat(isCtaDismissed).isTrue() + + // switch to secondary user + userRepository.setSelectedUserInfo(USER_INFOS[1]) + + // dismissed is false for secondary user + assertThat(isCtaDismissed).isFalse() + + // switch back to primary user + userRepository.setSelectedUserInfo(USER_INFOS[0]) + + // dismissed is true for primary user + assertThat(isCtaDismissed).isTrue() + } + + private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) : + UserFileManager { + override fun getFile(fileName: String, userId: Int): File { + throw UnsupportedOperationException() + } + + override fun getSharedPreferences( + fileName: String, + mode: Int, + userId: Int + ): SharedPreferences { + if (fileName != FILE_NAME) { + throw IllegalArgumentException("Preference files must be $FILE_NAME") + } + return sharedPrefs.getValue(userId) + } + } + + companion object { + val USER_INFOS = + listOf( + UserInfo(/* id= */ 0, "zero", /* flags= */ 0), + UserInfo(/* id= */ 1, "secondary", /* flags= */ 0), + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt index 30a5497d0a14..30a5497d0a14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index cd83c07a3b38..178eb6a42e10 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository @@ -65,6 +66,7 @@ class CommunalInteractorTest : SysuiTestCase() { private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter private lateinit var underTest: CommunalInteractor @@ -84,6 +86,7 @@ class CommunalInteractorTest : SysuiTestCase() { smartspaceRepository = withDeps.smartspaceRepository keyguardRepository = withDeps.keyguardRepository editWidgetsActivityStarter = withDeps.editWidgetsActivityStarter + communalPrefsRepository = withDeps.communalPrefsRepository underTest = withDeps.communalInteractor } @@ -331,10 +334,9 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun cta_visibilityTrue_shows() = + fun ctaTile_showsByDefault() = testScope.runTest { tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(true) val ctaTileContent by collectLastValue(underTest.ctaTileContent) @@ -346,10 +348,10 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun ctaTile_visibilityFalse_doesNotShow() = + fun ctaTile_afterDismiss_doesNotShow() = testScope.runTest { tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(false) + communalPrefsRepository.setCtaDismissedForCurrentUser() val ctaTileContent by collectLastValue(underTest.ctaTileContent) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 804c0528c46c..f9cfc3732a01 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository @@ -66,6 +67,7 @@ class CommunalViewModelTest : SysuiTestCase() { private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository private lateinit var mediaRepository: FakeCommunalMediaRepository + private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository private lateinit var underTest: CommunalViewModel @@ -82,6 +84,7 @@ class CommunalViewModelTest : SysuiTestCase() { widgetRepository = withDeps.widgetRepository smartspaceRepository = withDeps.smartspaceRepository mediaRepository = withDeps.mediaRepository + communalPrefsRepository = withDeps.communalPrefsRepository underTest = CommunalViewModel( @@ -149,9 +152,6 @@ class CommunalViewModelTest : SysuiTestCase() { // Media playing. mediaRepository.mediaActive() - // CTA Tile not dismissed. - communalRepository.setCtaTileInViewModeVisibility(true) - val communalContent by collectLastValue(underTest.communalContent) // Order is smart space, then UMO, widget content and cta tile. @@ -171,7 +171,6 @@ class CommunalViewModelTest : SysuiTestCase() { fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() = testScope.runTest { tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(true) val communalContent by collectLastValue(underTest.communalContent) val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing) @@ -195,7 +194,6 @@ class CommunalViewModelTest : SysuiTestCase() { fun popup_onDismiss_hidesImmediately() = testScope.runTest { tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(true) val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt index 8be4eeb7be7a..f23716ccca54 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.scene.domain.interactor +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.statusbar.IStatusBarService @@ -28,7 +30,11 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.init.NotificationsController +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any @@ -37,6 +43,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -50,6 +57,7 @@ import org.mockito.Mockito.verify class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { private val testScope = TestScope() + private val testDispatcher = StandardTestDispatcher() private val iStatusBarService = mock<IStatusBarService>() private val executor = FakeExecutor(FakeSystemClock()) private val windowRootViewVisibilityRepository = @@ -59,6 +67,9 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { private val notificationPresenter = mock<NotificationPresenter>() private val notificationsController = mock<NotificationsController>() private val powerInteractor = PowerInteractorFactory.create().powerInteractor + private val activeNotificationsRepository = ActiveNotificationListRepository() + private val activeNotificationsInteractor = + ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher) private val underTest = WindowRootViewVisibilityInteractor( @@ -67,6 +78,7 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { keyguardRepository, headsUpManager, powerInteractor, + activeNotificationsInteractor, ) .apply { setUp(notificationPresenter, notificationsController) } @@ -257,7 +269,8 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test - fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() = + @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOff_notifCountOne() = testScope.runTest { underTest.start() @@ -273,6 +286,23 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOn_notifCountOne() = + testScope.runTest { + underTest.start() + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + activeNotificationsRepository.setActiveNotifs(4) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(1) + } + + @Test fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() = testScope.runTest { underTest.start() @@ -288,7 +318,8 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test - fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() = + @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_noHeadsUp_flagOff_notifCountMatchesNotifController() = testScope.runTest { underTest.start() whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) @@ -304,7 +335,25 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test - fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() = + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_noHeadsUp_flagOn_notifCountMatchesNotifController() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + activeNotificationsRepository.setActiveNotifs(9) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(9) + } + + @Test + @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOff_notifCountMatchesNotifController() = testScope.runTest { underTest.start() whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) @@ -320,6 +369,23 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOn_notifCountMatchesNotifController() = + testScope.runTest { + underTest.start() + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false) + activeNotificationsRepository.setActiveNotifs(8) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(8) + } + + @Test fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() = testScope.runTest { underTest.start() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt index 0a10b2c85ebe..0c7ce970cf3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt @@ -16,11 +16,10 @@ package com.android.systemui.statusbar.notification.collection.render +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -34,18 +33,19 @@ import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @SmallTest +@RunWith(AndroidJUnit4::class) class GroupExpansionManagerTest : SysuiTestCase() { - private lateinit var gem: GroupExpansionManagerImpl + private lateinit var underTest: GroupExpansionManagerImpl private val dumpManager: DumpManager = mock() private val groupMembershipManager: GroupMembershipManager = mock() - private val featureFlags = FakeFeatureFlagsClassic() private val pipeline: NotifPipeline = mock() private lateinit var beforeRenderListListener: OnBeforeRenderListListener @@ -85,79 +85,57 @@ class GroupExpansionManagerTest : SysuiTestCase() { whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1) whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2) - gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags) + underTest = GroupExpansionManagerImpl(dumpManager, groupMembershipManager) } @Test - fun testNotifyOnlyOnChange_enabled() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun notifyOnlyOnChange() { var listenerCalledCount = 0 - gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } + underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } - gem.setGroupExpanded(summary1, false) + underTest.setGroupExpanded(summary1, false) assertThat(listenerCalledCount).isEqualTo(0) - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary1, true) assertThat(listenerCalledCount).isEqualTo(1) - gem.setGroupExpanded(summary2, true) - assertThat(listenerCalledCount).isEqualTo(2) - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary2, true) assertThat(listenerCalledCount).isEqualTo(2) - gem.setGroupExpanded(summary2, false) - assertThat(listenerCalledCount).isEqualTo(3) - } - - @Test - fun testNotifyOnlyOnChange_disabled() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - - var listenerCalledCount = 0 - gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } - - gem.setGroupExpanded(summary1, false) - assertThat(listenerCalledCount).isEqualTo(1) - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary1, true) assertThat(listenerCalledCount).isEqualTo(2) - gem.setGroupExpanded(summary2, true) + underTest.setGroupExpanded(summary2, false) assertThat(listenerCalledCount).isEqualTo(3) - gem.setGroupExpanded(summary1, true) - assertThat(listenerCalledCount).isEqualTo(4) - gem.setGroupExpanded(summary2, false) - assertThat(listenerCalledCount).isEqualTo(5) } @Test - fun testExpandUnattachedEntry() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun expandUnattachedEntry() { // First, expand the entry when it is attached. - gem.setGroupExpanded(summary1, true) - assertThat(gem.isGroupExpanded(summary1)).isTrue() + underTest.setGroupExpanded(summary1, true) + assertThat(underTest.isGroupExpanded(summary1)).isTrue() // Un-attach it, and un-expand it. NotificationEntryBuilder.setNewParent(summary1, null) - gem.setGroupExpanded(summary1, false) + underTest.setGroupExpanded(summary1, false) // Expanding again should throw. - assertThrows(IllegalArgumentException::class.java) { gem.setGroupExpanded(summary1, true) } + assertThrows(IllegalArgumentException::class.java) { + underTest.setGroupExpanded(summary1, true) + } } @Test - fun testSyncWithPipeline() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - gem.attach(pipeline) + fun syncWithPipeline() { + underTest.attach(pipeline) beforeRenderListListener = withArgCaptor { verify(pipeline).addOnBeforeRenderListListener(capture()) } val listener: OnGroupExpansionChangeListener = mock() - gem.registerGroupExpansionChangeListener(listener) + underTest.registerGroupExpansionChangeListener(listener) beforeRenderListListener.onBeforeRenderList(entries) verify(listener, never()).onGroupExpansionChange(any(), any()) // Expand one of the groups. - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary1, true) verify(listener).onGroupExpansionChange(summary1.row, true) // Empty the pipeline list and verify that the group is no longer expanded. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt index c1ffa641c6a4..2cbcc5a8d925 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt @@ -16,67 +16,35 @@ package com.android.systemui.statusbar.notification.collection.render +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.google.common.truth.Truth.assertThat -import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class GroupMembershipManagerTest : SysuiTestCase() { - private lateinit var gmm: GroupMembershipManagerImpl - - private val featureFlags = FakeFeatureFlagsClassic() - - @Before - fun setUp() { - gmm = GroupMembershipManagerImpl(featureFlags) - } + private var underTest = GroupMembershipManagerImpl() @Test - fun testIsChildInGroup_topLevel() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) + fun isChildInGroup_topLevel() { val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() - assertThat(gmm.isChildInGroup(topLevelEntry)).isFalse() - } - - @Test - fun testIsChildInGroup_noParent_old() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - val noParentEntry = NotificationEntryBuilder().setParent(null).build() - assertThat(gmm.isChildInGroup(noParentEntry)).isTrue() + assertThat(underTest.isChildInGroup(topLevelEntry)).isFalse() } @Test - fun testIsChildInGroup_noParent_new() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + fun isChildInGroup_noParent() { val noParentEntry = NotificationEntryBuilder().setParent(null).build() - assertThat(gmm.isChildInGroup(noParentEntry)).isFalse() - } - @Test - fun testIsChildInGroup_summary_old() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - - val groupKey = "group" - val summary = - NotificationEntryBuilder() - .setGroup(mContext, groupKey) - .setGroupSummary(mContext, true) - .build() - GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - - assertThat(gmm.isChildInGroup(summary)).isTrue() + assertThat(underTest.isChildInGroup(noParentEntry)).isFalse() } @Test - fun testIsChildInGroup_summary_new() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun isChildInGroup_summary() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -85,27 +53,17 @@ class GroupMembershipManagerTest : SysuiTestCase() { .build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(gmm.isChildInGroup(summary)).isFalse() + assertThat(underTest.isChildInGroup(summary)).isFalse() } @Test - fun testIsChildInGroup_child() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - val childEntry = NotificationEntryBuilder().build() - assertThat(gmm.isChildInGroup(childEntry)).isTrue() - } - - @Test - fun testIsGroupSummary_topLevelEntry() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + fun isGroupSummary_topLevelEntry() { val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() - assertThat(gmm.isGroupSummary(entry)).isFalse() + assertThat(underTest.isGroupSummary(entry)).isFalse() } @Test - fun testIsGroupSummary_summary() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun isGroupSummary_summary() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -114,13 +72,11 @@ class GroupMembershipManagerTest : SysuiTestCase() { .build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(gmm.isGroupSummary(summary)).isTrue() + assertThat(underTest.isGroupSummary(summary)).isTrue() } @Test - fun testIsGroupSummary_child() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun isGroupSummary_child() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -130,20 +86,17 @@ class GroupMembershipManagerTest : SysuiTestCase() { val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build() - assertThat(gmm.isGroupSummary(entry)).isFalse() + assertThat(underTest.isGroupSummary(entry)).isFalse() } @Test - fun testGetGroupSummary_topLevelEntry() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + fun getGroupSummary_topLevelEntry() { val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() - assertThat(gmm.getGroupSummary(entry)).isNull() + assertThat(underTest.getGroupSummary(entry)).isNull() } @Test - fun testGetGroupSummary_summary() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun getGroupSummary_summary() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -152,13 +105,11 @@ class GroupMembershipManagerTest : SysuiTestCase() { .build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary) + assertThat(underTest.getGroupSummary(summary)).isEqualTo(summary) } @Test - fun testGetGroupSummary_child() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun getGroupSummary_child() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -168,6 +119,6 @@ class GroupMembershipManagerTest : SysuiTestCase() { val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build() - assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary) + assertThat(underTest.getGroupSummary(entry)).isEqualTo(summary) } } diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml new file mode 100644 index 000000000000..84b89ca68e65 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_focused="true"> + <shape android:shape="rectangle"> + <corners android:radius="16dp" /> + <!--By default this outline will not show hence 0 width. + width is set programmatically when needed and is gated by the flag: + com.android.systemui.Flags.pinInputFieldStyledFocusState--> + <stroke android:width="0dp" + android:color="@color/bouncer_password_focus_color" /> + </shape> + </item> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml index 66c54f2a668e..0b35559148af 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml @@ -23,7 +23,7 @@ android:layout_marginTop="@dimen/keyguard_lock_padding" android:importantForAccessibility="no" android:ellipsize="marquee" - android:focusable="true" + android:focusable="false" android:gravity="center" android:singleLine="true" /> </merge> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 0628c3e957b1..ddad1e3f8940 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -25,6 +25,12 @@ <!-- Maximum width of the sliding KeyguardSecurityContainer --> <dimen name="keyguard_security_width">420dp</dimen> + <!-- Width for the keyguard pin input field --> + <dimen name="keyguard_pin_field_width">292dp</dimen> + + <!-- Width for the keyguard pin input field --> + <dimen name="keyguard_pin_field_height">48dp</dimen> + <!-- Height of the sliding KeyguardSecurityContainer (includes 2x keyguard_security_view_top_margin) --> <dimen name="keyguard_security_height">420dp</dimen> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index 565ed1085fb9..f51e1098f333 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -62,10 +62,10 @@ <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string> <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. --> - <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging optimized to protect battery</string> + <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging on hold to protect battery</string> <!-- When the lock screen is showing and the phone plugged in with incompatible charger. --> - <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Issue with charging accessory</string> + <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Check charging accessory</string> <!-- SIM messages --><skip /> <!-- When the user inserts a sim card from an unsupported network, it becomes network locked --> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 2cca9510417a..4789a229c4d0 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -76,6 +76,7 @@ </style> <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item> <item name="android:gravity">center</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index 85b6e8dc12b3..5db9eee6a908 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -50,7 +50,10 @@ app:layout_constraintStart_toStartOf="@id/album_art" app:layout_constraintEnd_toEndOf="@id/album_art" app:layout_constraintTop_toTopOf="@id/album_art" - app:layout_constraintBottom_toBottomOf="@id/album_art" /> + app:layout_constraintBottom_toBottomOf="@id/album_art" + android:clipToOutline="true" + android:background="@drawable/qs_media_outline_layout_bg" + /> <com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView android:id="@+id/turbulence_noise_view" @@ -59,7 +62,10 @@ app:layout_constraintStart_toStartOf="@id/album_art" app:layout_constraintEnd_toEndOf="@id/album_art" app:layout_constraintTop_toTopOf="@id/album_art" - app:layout_constraintBottom_toBottomOf="@id/album_art" /> + app:layout_constraintBottom_toBottomOf="@id/album_art" + android:clipToOutline="true" + android:background="@drawable/qs_media_outline_layout_bg" + /> <!-- Guideline for output switcher --> <androidx.constraintlayout.widget.Guideline diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index bcc3c83b4560..61a323d44dfc 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -93,6 +93,8 @@ <color name="qs_user_switcher_selected_avatar_icon_color">#202124</color> <!-- Color of background circle of user avatars in quick settings user switcher --> <color name="qs_user_switcher_avatar_background">#3C4043</color> + <!-- Color of border for keyguard password input when focused --> + <color name="bouncer_password_focus_color">@*android:color/system_secondary_dark</color> <!-- Accessibility floating menu --> <color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% --> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 8be1cc7282e3..3839dd98cdd3 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -56,6 +56,8 @@ <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color> <!-- Color of background circle of user avatars in keyguard user switcher --> <color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color> + <!-- Color of border for keyguard password input when focused --> + <color name="bouncer_password_focus_color">@*android:color/system_secondary_light</color> <!-- Icon color for user avatars in user switcher quick settings --> <color name="qs_user_switcher_avatar_icon_color">#3C4043</color> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 6e035e8c8c36..ec4c7d5bf67e 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -249,6 +249,9 @@ --> <item type="id" name="tag_smartspace_view" /> + <!-- ID of the Scene Container root Composable view --> + <item type='id' name="scene_container_root_composable" /> + <!-- Tag set on the Compose implementation of the QS footer actions. --> <item type="id" name="tag_compose_qs_footer_actions" /> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 66f965aea76f..efd8f7f97ca3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -37,6 +37,7 @@ import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyboard.data.repository.KeyboardRepository; import com.android.systemui.log.BouncerLogger; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; @@ -210,6 +211,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final FeatureFlags mFeatureFlags; private final SelectedUserInteractor mSelectedUserInteractor; private final UiEventLogger mUiEventLogger; + private final KeyboardRepository mKeyboardRepository; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -223,7 +225,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> DevicePostureController devicePostureController, KeyguardViewController keyguardViewController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + KeyboardRepository keyboardRepository) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -240,6 +243,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mFeatureFlags = featureFlags; mSelectedUserInteractor = selectedUserInteractor; mUiEventLogger = uiEventLogger; + mKeyboardRepository = keyboardRepository; } /** Create a new {@link KeyguardInputViewController}. */ @@ -268,19 +272,22 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, emergencyButtonController, mFalsingCollector, mDevicePostureController, mFeatureFlags, mSelectedUserInteractor, - mUiEventLogger); + mUiEventLogger, mKeyboardRepository + ); } else if (keyguardInputView instanceof KeyguardSimPinView) { return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, - emergencyButtonController, mFeatureFlags, mSelectedUserInteractor); + emergencyButtonController, mFeatureFlags, mSelectedUserInteractor, + mKeyboardRepository); } else if (keyguardInputView instanceof KeyguardSimPukView) { return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, - emergencyButtonController, mFeatureFlags, mSelectedUserInteractor); + emergencyButtonController, mFeatureFlags, mSelectedUserInteractor, + mKeyboardRepository); } throw new RuntimeException("Unable to find controller for " + keyguardInputView); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 36fe75f69a45..fcff0dbc0878 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -25,6 +25,7 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FO import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; +import static com.android.systemui.Flags.pinInputFieldStyledFocusState; import android.animation.Animator; import android.animation.AnimatorSet; @@ -168,7 +169,9 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView // Set selected property on so the view can send accessibility events. mPasswordEntry.setSelected(true); - mPasswordEntry.setDefaultFocusHighlightEnabled(false); + if (!pinInputFieldStyledFocusState()) { + mPasswordEntry.setDefaultFocusHighlightEnabled(false); + } mOkButton = findViewById(R.id.key_enter); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index 376933dc5519..60dd5686c315 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -16,17 +16,25 @@ package com.android.keyguard; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import static com.android.systemui.Flags.pinInputFieldStyledFocusState; + +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.StateListDrawable; +import android.util.TypedValue; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.OnKeyListener; import android.view.View.OnTouchListener; +import android.view.ViewGroup; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyboard.data.repository.KeyboardRepository; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -35,6 +43,7 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB private final LiftToActivateListener mLiftToActivateListener; private final FalsingCollector mFalsingCollector; + private final KeyboardRepository mKeyboardRepository; protected PasswordTextView mPasswordEntry; private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> { @@ -65,12 +74,14 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector, FeatureFlags featureFlags, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, + KeyboardRepository keyboardRepository) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, falsingCollector, emergencyButtonController, featureFlags, selectedUserInteractor); mLiftToActivateListener = liftToActivateListener; mFalsingCollector = falsingCollector; + mKeyboardRepository = keyboardRepository; mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); } @@ -120,6 +131,35 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB }); okButton.setOnHoverListener(mLiftToActivateListener); } + if (pinInputFieldStyledFocusState()) { + collectFlow(mPasswordEntry, mKeyboardRepository.isAnyKeyboardConnected(), + this::setKeyboardBasedFocusOutline); + + /** + * new UI Specs for PIN Input field have new dimensions go/pin-focus-states. + * However we want these changes behind a flag, and resource files cannot be flagged + * hence the dimension change in code. When the flags are removed these dimensions + * should be set in resources permanently and the code below removed. + */ + ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams(); + layoutParams.width = (int) getResources().getDimension( + R.dimen.keyguard_pin_field_width); + layoutParams.height = (int) getResources().getDimension( + R.dimen.keyguard_pin_field_height); + } + } + + private void setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected) { + StateListDrawable background = (StateListDrawable) mPasswordEntry.getBackground(); + GradientDrawable stateDrawable = (GradientDrawable) background.getStateDrawable(0); + int color = getResources().getColor(R.color.bouncer_password_focus_color); + if (!isAnyKeyboardConnected) { + stateDrawable.setStroke(0, color); + } else { + int strokeWidthInDP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, + getResources().getDisplayMetrics()); + stateDrawable.setStroke(strokeWidthInDP, color); + } } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 2aab1f189263..b958f55bdf79 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.keyboard.data.repository.KeyboardRepository; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -58,12 +59,13 @@ public class KeyguardPinViewController LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector, - DevicePostureController postureController, - FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, - UiEventLogger uiEventLogger) { + DevicePostureController postureController, FeatureFlags featureFlags, + SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger, + KeyboardRepository keyboardRepository) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor); + emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, + keyboardRepository); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mPostureController = postureController; mLockPatternUtils = lockPatternUtils; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 5729119a582a..1cdcbd06815f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -44,6 +44,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyboard.data.repository.KeyboardRepository; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -93,10 +94,11 @@ public class KeyguardSimPinViewController LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor); + emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, + keyboardRepository); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 05fb5fa75e9e..f019d61a3ccc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -39,6 +39,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyboard.data.repository.KeyboardRepository; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -90,10 +91,11 @@ public class KeyguardSimPukViewController LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor); + emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, + keyboardRepository); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt index b15aaaf9a00e..e88aaf015f87 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt +++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt @@ -91,7 +91,7 @@ abstract class SystemUIAppComponentFactoryBase : AppComponentFactory() { return app } - @UsesReflection(KeepTarget(extendsClassConstant = SysUIComponent::class, methodName = "inject")) + @UsesReflection(KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject")) override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider { val contentProvider = super.instantiateProviderCompat(cl, className) if (contentProvider is ContextInitializer) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt index 6483ae44d5ec..c7e5b645db5f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt @@ -19,16 +19,20 @@ package com.android.systemui.accessibility.data.repository import android.os.UserHandle import android.provider.Settings.Secure import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext /** Provides data related to color correction. */ @@ -45,22 +49,24 @@ class ColorCorrectionRepositoryImpl @Inject constructor( @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, private val secureSettings: SecureSettings, ) : ColorCorrectionRepository { - companion object { - const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED - const val DISABLED = 0 - const val ENABLED = 1 - } + private val userMap = mutableMapOf<Int, Flow<Boolean>>() override fun isEnabled(userHandle: UserHandle): Flow<Boolean> = - secureSettings - .observerFlow(userHandle.identifier, SETTING_NAME) - .onStart { emit(Unit) } - .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED } - .distinctUntilChanged() - .flowOn(bgCoroutineContext) + userMap.getOrPut(userHandle.identifier) { + secureSettings + .observerFlow(userHandle.identifier, SETTING_NAME) + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED + } + .distinctUntilChanged() + .flowOn(bgCoroutineContext) + .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1) + } override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean = withContext(bgCoroutineContext) { @@ -70,4 +76,10 @@ constructor( userHandle.identifier ) } + + companion object { + private const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED + private const val DISABLED = 0 + private const val ENABLED = 1 + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt index bbf10c509e50..419eada91f87 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt @@ -19,16 +19,20 @@ package com.android.systemui.accessibility.data.repository import android.os.UserHandle import android.provider.Settings.Secure import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext /** Provides data related to color inversion. */ @@ -45,16 +49,24 @@ class ColorInversionRepositoryImpl @Inject constructor( @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, private val secureSettings: SecureSettings, ) : ColorInversionRepository { + private val userMap = mutableMapOf<Int, Flow<Boolean>>() + override fun isEnabled(userHandle: UserHandle): Flow<Boolean> = - secureSettings - .observerFlow(userHandle.identifier, SETTING_NAME) - .onStart { emit(Unit) } - .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED } - .distinctUntilChanged() - .flowOn(bgCoroutineContext) + userMap.getOrPut(userHandle.identifier) { + secureSettings + .observerFlow(userHandle.identifier, SETTING_NAME) + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED + } + .distinctUntilChanged() + .flowOn(bgCoroutineContext) + .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1) + } override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean = withContext(bgCoroutineContext) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index 10768ea6122a..dc07c1b25678 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -18,6 +18,7 @@ package com.android.systemui.communal.dagger import com.android.systemui.communal.data.db.CommunalDatabaseModule import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule +import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule import com.android.systemui.communal.data.repository.CommunalRepositoryModule import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule @@ -34,6 +35,7 @@ import dagger.Module CommunalTutorialRepositoryModule::class, CommunalWidgetRepositoryModule::class, CommunalDatabaseModule::class, + CommunalPrefsRepositoryModule::class, ] ) interface CommunalModule { diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt new file mode 100644 index 000000000000..c2ea2e93ce58 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.data.repository + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.UserInfo +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences +import com.android.systemui.user.data.repository.UserRepository +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +/** + * Stores simple preferences for the current user in communal hub. For use cases like "has the CTA + * tile been dismissed?" + */ +interface CommunalPrefsRepository { + + /** Whether the CTA tile has been dismissed. */ + val isCtaDismissed: Flow<Boolean> + + /** Save the CTA tile dismissed state for the current user. */ + suspend fun setCtaDismissedForCurrentUser() +} + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class CommunalPrefsRepositoryImpl +@Inject +constructor( + @Background private val backgroundScope: CoroutineScope, + @Background private val bgDispatcher: CoroutineDispatcher, + private val userRepository: UserRepository, + private val userFileManager: UserFileManager, +) : CommunalPrefsRepository { + + override val isCtaDismissed: Flow<Boolean> = + userRepository.selectedUserInfo + .flatMapLatest(::observeCtaDismissState) + .stateIn( + scope = backgroundScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + + override suspend fun setCtaDismissedForCurrentUser() = + withContext(bgDispatcher) { + getSharedPrefsForUser(userRepository.getSelectedUserInfo()) + .edit() + .putBoolean(CTA_DISMISSED_STATE, true) + .apply() + } + + private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> = + userFileManager + .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id) + // Emit at the start of collection to ensure we get an initial value + .onStart { emit(Unit) } + .map { getCtaDismissedState() } + .flowOn(bgDispatcher) + + private suspend fun getCtaDismissedState(): Boolean = + withContext(bgDispatcher) { + getSharedPrefsForUser(userRepository.getSelectedUserInfo()) + .getBoolean(CTA_DISMISSED_STATE, false) + } + + private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences { + return userFileManager.getSharedPreferences( + FILE_NAME, + Context.MODE_PRIVATE, + user.id, + ) + } + + companion object { + const val TAG = "CommunalRepository" + const val FILE_NAME = "communal_hub_prefs" + const val CTA_DISMISSED_STATE = "cta_dismissed" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt new file mode 100644 index 000000000000..a4ff6d3dfbef --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.communal.data.repository + +import dagger.Binds +import dagger.Module + +@Module +interface CommunalPrefsRepositoryModule { + @Binds fun communalPrefsRepository(impl: CommunalPrefsRepositoryImpl): CommunalPrefsRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt index 553b3ebc0813..1f4be4060223 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt @@ -56,9 +56,6 @@ interface CommunalRepository { /** Exposes the transition state of the communal [SceneTransitionLayout]. */ val transitionState: StateFlow<ObservableCommunalTransitionState> - /** Whether the CTA tile is visible in the hub under view mode. */ - val isCtaTileInViewModeVisible: Flow<Boolean> - /** Updates the requested scene. */ fun setDesiredScene(desiredScene: CommunalSceneKey) @@ -68,9 +65,6 @@ interface CommunalRepository { * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) - - /** Updates whether to display the CTA tile in the hub under view mode. */ - fun setCtaTileInViewModeVisibility(isVisible: Boolean) } @OptIn(ExperimentalCoroutinesApi::class) @@ -102,16 +96,6 @@ constructor( initialValue = defaultTransitionState, ) - // TODO(b/313462210) - persist the value in local storage, so the tile won't show up again - // once dismissed. - private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true) - override val isCtaTileInViewModeVisible: Flow<Boolean> = - _isCtaTileInViewModeVisible.asStateFlow() - - override fun setCtaTileInViewModeVisibility(isVisible: Boolean) { - _isCtaTileInViewModeVisible.value = isVisible - } - override fun setDesiredScene(desiredScene: CommunalSceneKey) { _desiredScene.value = desiredScene } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 9fa4cd6c7985..aa9a42634a5e 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -20,6 +20,7 @@ import android.app.smartspace.SmartspaceTarget import android.appwidget.AppWidgetHost import android.content.ComponentName import com.android.systemui.communal.data.repository.CommunalMediaRepository +import com.android.systemui.communal.data.repository.CommunalPrefsRepository import com.android.systemui.communal.data.repository.CommunalRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel @@ -49,6 +50,7 @@ class CommunalInteractor constructor( private val communalRepository: CommunalRepository, private val widgetRepository: CommunalWidgetRepository, + private val communalPrefsRepository: CommunalPrefsRepository, mediaRepository: CommunalMediaRepository, smartspaceRepository: SmartspaceRepository, keyguardInteractor: KeyguardInteractor, @@ -122,7 +124,7 @@ constructor( } /** Dismiss the CTA tile from the hub in view mode. */ - fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false) + suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser() /** * Add a widget at the specified position. @@ -174,8 +176,8 @@ constructor( /** CTA tile to be displayed in the glanceable hub (view mode). */ val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> = - communalRepository.isCtaTileInViewModeVisible.map { visible -> - if (visible) listOf(CommunalContentModel.CtaTileInViewMode()) else emptyList() + communalPrefsRepository.isCtaDismissed.map { isDismissed -> + if (isDismissed) emptyList() else listOf(CommunalContentModel.CtaTileInViewMode()) } /** A list of tutorial content to be displayed in the communal hub in tutorial mode. */ diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 09c18ed0c8de..d619362b0311 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -86,9 +86,11 @@ constructor( override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor() override fun onDismissCtaTile() { - communalInteractor.dismissCtaTile() - setPopupOnDismissCtaVisibility(true) - schedulePopupHiding() + scope.launch { + communalInteractor.dismissCtaTile() + setPopupOnDismissCtaVisibility(true) + schedulePopupHiding() + } } override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 846736c04d98..ea8ba1029782 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -102,12 +102,6 @@ object Flags { default = true ) - /** Only notify group expansion listeners when a change happens. */ - // TODO(b/292213543): Tracking Bug - @JvmField - val NOTIFICATION_GROUP_EXPANSION_CHANGE = - releasedFlag("notification_group_expansion_change") - // TODO(b/301955929) @JvmField val NOTIF_LS_BACKGROUND_THREAD = diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt index 496c64e1120e..c6fb4f9d6956 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt @@ -19,6 +19,8 @@ package com.android.systemui.keyboard import com.android.systemui.keyboard.data.repository.KeyboardRepository import com.android.systemui.keyboard.data.repository.KeyboardRepositoryImpl +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl import dagger.Binds import dagger.Module @@ -27,4 +29,9 @@ abstract class KeyboardModule { @Binds abstract fun bindKeyboardRepository(repository: KeyboardRepositoryImpl): KeyboardRepository + + @Binds + abstract fun bindStickyKeysRepository( + repository: StickyKeysRepositoryImpl + ): StickyKeysRepository } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt new file mode 100644 index 000000000000..37034f63aca7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys + +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.KeyboardLog +import javax.inject.Inject + +private const val TAG = "stickyKeys" + +class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogBuffer) { + fun logNewStickyKeysReceived(linkedHashMap: Map<ModifierKey, Locked>) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { str1 = linkedHashMap.toString() }, + { "new sticky keys state received: $str1" } + ) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt new file mode 100644 index 000000000000..34d288815570 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys.data.repository + +import android.hardware.input.InputManager +import android.hardware.input.InputManager.StickyModifierStateListener +import android.hardware.input.StickyModifierState +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.stickykeys.StickyKeysLogger +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +interface StickyKeysRepository { + val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> + val settingEnabled: Flow<Boolean> +} + +class StickyKeysRepositoryImpl +@Inject +constructor( + private val inputManager: InputManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val stickyKeysLogger: StickyKeysLogger, +) : StickyKeysRepository { + + override val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> = + conflatedCallbackFlow { + val listener = StickyModifierStateListener { stickyModifierState -> + trySendWithFailureLogging(stickyModifierState, TAG) + } + // after registering, InputManager calls listener with the current value + inputManager.registerStickyModifierStateListener(Runnable::run, listener) + awaitClose { inputManager.unregisterStickyModifierStateListener(listener) } + } + .map { toStickyKeysMap(it) } + .onEach { stickyKeysLogger.logNewStickyKeysReceived(it) } + .flowOn(backgroundDispatcher) + + // TODO(b/319837892): Implement reading actual setting + override val settingEnabled: StateFlow<Boolean> = MutableStateFlow(true) + + private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> { + val keys = linkedMapOf<ModifierKey, Locked>() + state.apply { + if (isAltGrModifierOn) keys[ALT_GR] = Locked(false) + if (isAltGrModifierLocked) keys[ALT_GR] = Locked(true) + if (isAltModifierOn) keys[ALT] = Locked(false) + if (isAltModifierLocked) keys[ALT] = Locked(true) + if (isCtrlModifierOn) keys[CTRL] = Locked(false) + if (isCtrlModifierLocked) keys[CTRL] = Locked(true) + if (isMetaModifierOn) keys[META] = Locked(false) + if (isMetaModifierLocked) keys[META] = Locked(true) + if (isShiftModifierOn) keys[SHIFT] = Locked(false) + if (isShiftModifierLocked) keys[SHIFT] = Locked(true) + } + return keys + } + + companion object { + const val TAG = "StickyKeysRepositoryImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt new file mode 100644 index 000000000000..d5f082a2566f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys.shared.model + +@JvmInline +value class Locked(val locked: Boolean) + +enum class ModifierKey(val text: String) { + ALT("ALT LEFT"), + ALT_GR("ALT RIGHT"), + CTRL("CTRL"), + META("META"), + SHIFT("SHIFT"), +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt new file mode 100644 index 000000000000..26eb706da200 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys.ui.viewmodel + +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyboard.data.repository.KeyboardRepository +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +class StickyKeysIndicatorViewModel +@Inject +constructor( + stickyKeysRepository: StickyKeysRepository, + keyboardRepository: KeyboardRepository, + @Application applicationScope: CoroutineScope, +) { + + @OptIn(ExperimentalCoroutinesApi::class) + val indicatorContent: Flow<Map<ModifierKey, Locked>> = + keyboardRepository.isAnyKeyboardConnected + .flatMapLatest { keyboardPresent -> + if (keyboardPresent) stickyKeysRepository.settingEnabled else flowOf(false) + } + .flatMapLatest { enabled -> + if (enabled) stickyKeysRepository.stickyKeys else flowOf(emptyMap()) + } + .stateIn(applicationScope, SharingStarted.Lazily, emptyMap()) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index fedd63be1454..8fa33ee7d0ca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -54,12 +54,33 @@ constructor( ) { override fun start() { - listenForAodToLockscreenOrOccluded() + listenForAodToLockscreen() + listenForAodToPrimaryBouncer() listenForAodToGone() + listenForAodToOccluded() listenForTransitionToCamera(scope, keyguardInteractor) } - private fun listenForAodToLockscreenOrOccluded() { + /** + * There are cases where the transition to AOD begins but never completes, such as tapping power + * during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to + * run AOD->OCCLUDED. + */ + private fun listenForAodToOccluded() { + scope.launch { + keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect { + (isOccluded, startedKeyguardState) -> + if (isOccluded && startedKeyguardState == KeyguardState.AOD) { + startTransitionTo( + toState = KeyguardState.OCCLUDED, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + } + } + } + } + + private fun listenForAodToLockscreen() { scope.launch { keyguardInteractor .dozeTransitionTo(DozeStateModel.FINISH) @@ -72,20 +93,15 @@ constructor( ::toTriple ) .collect { (_, lastStartedStep, occluded) -> - if (lastStartedStep.to == KeyguardState.AOD) { - val toState = - if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN + if (lastStartedStep.to == KeyguardState.AOD && !occluded) { val modeOnCanceled = - if ( - toState == KeyguardState.LOCKSCREEN && - lastStartedStep.from == KeyguardState.LOCKSCREEN - ) { + if (lastStartedStep.from == KeyguardState.LOCKSCREEN) { TransitionModeOnCanceled.REVERSE } else { TransitionModeOnCanceled.LAST_VALUE } startTransitionTo( - toState = toState, + toState = KeyguardState.LOCKSCREEN, modeOnCanceled = modeOnCanceled, ) } @@ -93,11 +109,26 @@ constructor( } } + /** + * If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the + * PRIMARY_BOUNCER. + */ + private fun listenForAodToPrimaryBouncer() { + scope.launch { + keyguardInteractor.primaryBouncerShowing + .sample(startedKeyguardTransitionStep, ::Pair) + .collect { (isBouncerShowing, lastStartedTransitionStep) -> + if (isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.AOD) { + startTransitionTo(KeyguardState.PRIMARY_BOUNCER) + } + } + } + } + private fun listenForAodToGone() { scope.launch { keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect { - pair -> - val (biometricUnlockState, keyguardState) = pair + (biometricUnlockState, keyguardState) -> if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) { startTransitionTo(KeyguardState.GONE) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt index 2a68f26d3ae7..0c0eb8a673a4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt @@ -24,6 +24,7 @@ import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.LEFT import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.RIGHT +import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder @@ -96,6 +97,11 @@ constructor( constrainHeight(R.id.end_button, height) connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin) connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin) + + // The constraint set visibility for start and end button are default visible, set to + // ignore so the view's own initial visibility (invisible) is used + setVisibilityMode(R.id.start_button, VISIBILITY_MODE_IGNORE) + setVisibilityMode(R.id.end_button, VISIBILITY_MODE_IGNORE) } } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt new file mode 100644 index 000000000000..5910701d9f2a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger + +import javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for keyboard-related functionality. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class KeyboardLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 24cb8fff9b67..3e0094081638 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -603,6 +603,14 @@ public class LogModule { return factory.create("BluetoothTileDialogLog", 50); } + /** Provides a {@link LogBuffer} for the keyboard functionalities. */ + @Provides + @SysUISingleton + @KeyboardLog + public static LogBuffer provideKeyboardLogBuffer(LogBufferFactory factory) { + return factory.create("KeyboardLog", 50); + } + /** Provides a {@link LogBuffer} for {@link PackageChangeRepository} */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt new file mode 100644 index 000000000000..ca790e830f7f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs + +import android.view.KeyEvent +import android.view.View +import androidx.core.util.Consumer + +/** + * Listens for left and right arrow keys pressed while focus is on the view. + * + * Key press is treated as correct when its full lifecycle happened on the view: first + * [KeyEvent.ACTION_DOWN] was performed, view didn't lose focus in the meantime and then + * [KeyEvent.ACTION_UP] was performed with the same [KeyEvent.getKeyCode] + */ +class LeftRightArrowPressedListener private constructor() : + View.OnKeyListener, View.OnFocusChangeListener { + + private var lastKeyCode: Int? = 0 + private var listener: Consumer<Int>? = null + + fun setArrowKeyPressedListener(arrowPressedListener: Consumer<Int>) { + listener = arrowPressedListener + } + + override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean { + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { + // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need + // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we + // have a chance to intercept ACTION_UP. + if (keyEvent.action == KeyEvent.ACTION_UP && keyCode == lastKeyCode) { + listener?.accept(keyCode) + lastKeyCode = null + } else if (keyEvent.repeatCount == 0) { + // we only read key events that are NOT coming from long pressing because that also + // causes reading ACTION_DOWN event (with repeated count > 0) when moving focus with + // arrow from another sibling view + lastKeyCode = keyCode + } + return true + } + return false + } + + override fun onFocusChange(view: View, hasFocus: Boolean) { + // resetting lastKeyCode so we get fresh cleared state on focus + if (hasFocus) { + lastKeyCode = null + } + } + + companion object { + @JvmStatic + fun createAndRegisterListenerForView(view: View): LeftRightArrowPressedListener { + val listener = LeftRightArrowPressedListener() + view.setOnKeyListener(listener) + view.onFocusChangeListener = listener + return listener + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java index 4770d5272508..1c9f5fdde6f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java @@ -1,5 +1,8 @@ package com.android.systemui.qs; +import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT; +import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT; + import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -9,10 +12,12 @@ import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; +import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; import com.android.settingslib.Utils; @@ -43,6 +48,7 @@ public class PageIndicator extends ViewGroup { private int mPosition = -1; private boolean mAnimating; + private PageScrollActionListener mPageScrollActionListener; private final Animatable2.AnimationCallback mAnimationCallback = new Animatable2.AnimationCallback() { @@ -77,6 +83,14 @@ public class PageIndicator extends ViewGroup { mPageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width); mPageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height); mPageDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width); + LeftRightArrowPressedListener arrowListener = + LeftRightArrowPressedListener.createAndRegisterListenerForView(this); + arrowListener.setArrowKeyPressedListener(keyCode -> { + if (mPageScrollActionListener != null) { + int swipeDirection = keyCode == KeyEvent.KEYCODE_DPAD_LEFT ? LEFT : RIGHT; + mPageScrollActionListener.onScrollActionTriggered(swipeDirection); + } + }); } public void setNumPages(int numPages) { @@ -280,4 +294,19 @@ public class PageIndicator extends ViewGroup { getChildAt(i).layout(left, 0, mPageIndicatorWidth + left, mPageIndicatorHeight); } } + + void setPageScrollActionListener(PageScrollActionListener listener) { + mPageScrollActionListener = listener; + } + + interface PageScrollActionListener { + + @IntDef({LEFT, RIGHT}) + @interface Direction { } + + int LEFT = 0; + int RIGHT = 1; + + void onScrollActionTriggered(@Direction int swipeDirection); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 052c0daf0b56..43f3a2242da4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -1,6 +1,8 @@ package com.android.systemui.qs; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE; +import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT; +import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -12,7 +14,6 @@ import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.util.AttributeSet; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -30,6 +31,7 @@ import androidx.viewpager.widget.ViewPager; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.systemui.plugins.qs.QSTile; +import com.android.systemui.qs.PageIndicator.PageScrollActionListener.Direction; import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanelControllerBase.TileRecord; import com.android.systemui.qs.logging.QSLogger; @@ -310,26 +312,18 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageIndicator = indicator; mPageIndicator.setNumPages(mPages.size()); mPageIndicator.setLocation(mPageIndicatorPosition); - mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> { - if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { - // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need - // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we - // have a chance to intercept ACTION_UP. - if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) { - scrollByX(getDeltaXForKeyboardScrolling(keyCode), - SINGLE_PAGE_SCROLL_DURATION_MILLIS); - } - return true; + mPageIndicator.setPageScrollActionListener(swipeDirection -> { + if (mScroller.isFinished()) { + scrollByX(getDeltaXForPageScrolling(swipeDirection), + SINGLE_PAGE_SCROLL_DURATION_MILLIS); } - return false; }); } - private int getDeltaXForKeyboardScrolling(int keyCode) { - if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) { + private int getDeltaXForPageScrolling(@Direction int swipeDirection) { + if (swipeDirection == LEFT && getCurrentItem() != 0) { return -getWidth(); - } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT - && getCurrentItem() != mPages.size() - 1) { + } else if (swipeDirection == RIGHT && getCurrentItem() != mPages.size() - 1) { return getWidth(); } return 0; diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt index a6cccf1cb41b..e2959fe834d4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt @@ -24,7 +24,9 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.init.NotificationsController +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.policy.HeadsUpManager import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -44,6 +46,7 @@ constructor( private val keyguardRepository: KeyguardRepository, private val headsUpManager: HeadsUpManager, private val powerInteractor: PowerInteractor, + private val activeNotificationsInteractor: ActiveNotificationsInteractor, ) : CoreStartable { private var notificationPresenter: NotificationPresenter? = null @@ -117,6 +120,14 @@ constructor( return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) { 1 } else { + getActiveNotificationsCount() + } + } + + private fun getActiveNotificationsCount(): Int { + return if (NotificationsLiveDataStoreRefactor.isEnabled) { + activeNotificationsInteractor.allNotificationsCountValue + } else { notificationsController?.getActiveNotificationsCount() ?: 0 } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt index ab08f660789c..a755805d1872 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt @@ -37,6 +37,7 @@ object SceneContainerFlag { /** The flag description -- not an aconfig flag name */ const val DESCRIPTION = "SceneContainerFlag" + @JvmStatic inline val isEnabled get() = SCENE_CONTAINER_ENABLED && // mainStaticFlag diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 4a839b8df9ba..93cfc5dbcbe3 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -85,12 +85,13 @@ object SceneWindowRootViewBinder { view.addView( ComposeFacade.createSceneContainerView( - scope = this, - context = view.context, - viewModel = viewModel, - windowInsets = windowInsets, - sceneByKey = sortedSceneByKey, - ) + scope = this, + context = view.context, + viewModel = viewModel, + windowInsets = windowInsets, + sceneByKey = sortedSceneByKey, + ) + .also { it.id = R.id.scene_container_root_composable } ) val legacyView = view.requireViewById<View>(R.id.legacy_window_root) diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt new file mode 100644 index 000000000000..b09bfe21e014 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.settings + +import android.annotation.UserIdInt +import android.content.Context +import android.content.SharedPreferences +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +/** Extension functions for [UserFileManager]. */ +object UserFileManagerExt { + + /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */ + fun UserFileManager.observeSharedPreferences( + fileName: String, + @Context.PreferencesMode mode: Int, + @UserIdInt userId: Int + ): Flow<Unit> = conflatedCallbackFlow { + val sharedPrefs = getSharedPreferences(fileName, mode, userId) + + val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) } + + sharedPrefs.registerOnSharedPreferenceChangeListener(listener) + awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java index 2d5afd56da72..3cdb2cd9b5c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java @@ -21,8 +21,6 @@ import androidx.annotation.NonNull; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -53,14 +51,11 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl */ private final Set<NotificationEntry> mExpandedGroups = new HashSet<>(); - private final FeatureFlags mFeatureFlags; - @Inject public GroupExpansionManagerImpl(DumpManager dumpManager, - GroupMembershipManager groupMembershipManager, FeatureFlags featureFlags) { + GroupMembershipManager groupMembershipManager) { mDumpManager = dumpManager; mGroupMembershipManager = groupMembershipManager; - mFeatureFlags = featureFlags; } /** @@ -86,10 +81,8 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl }; public void attach(NotifPipeline pipeline) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - mDumpManager.registerDumpable(this); - pipeline.addOnBeforeRenderListListener(mNotifTracker); - } + mDumpManager.registerDumpable(this); + pipeline.addOnBeforeRenderListListener(mNotifTracker); } @Override @@ -105,8 +98,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl @Override public void setGroupExpanded(NotificationEntry entry, boolean expanded) { NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry); - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) - && entry.getParent() == null) { + if (entry.getParent() == null) { if (expanded) { throw new IllegalArgumentException("Cannot expand group that is not attached"); } else { @@ -124,7 +116,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl } // Only notify listeners if something changed. - if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) || changed) { + if (changed) { sendOnGroupExpandedChange(entry, expanded); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java index cb7935369564..da1247953c4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java @@ -22,8 +22,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.flags.FeatureFlagsClassic; -import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -38,25 +36,17 @@ import javax.inject.Inject; */ @SysUISingleton public class GroupMembershipManagerImpl implements GroupMembershipManager { - FeatureFlagsClassic mFeatureFlags; - @Inject - public GroupMembershipManagerImpl(FeatureFlagsClassic featureFlags) { - mFeatureFlags = featureFlags; - } + public GroupMembershipManagerImpl() {} @Override public boolean isGroupSummary(@NonNull NotificationEntry entry) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - if (entry.getParent() == null) { - // The entry is not attached, so it doesn't count. - return false; - } - // If entry is a summary, its parent is a GroupEntry with summary = entry. - return entry.getParent().getSummary() == entry; - } else { - return getGroupSummary(entry) == entry; + if (entry.getParent() == null) { + // The entry is not attached, so it doesn't count. + return false; } + // If entry is a summary, its parent is a GroupEntry with summary = entry. + return entry.getParent().getSummary() == entry; } @Nullable @@ -70,12 +60,8 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager { @Override public boolean isChildInGroup(@NonNull NotificationEntry entry) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - // An entry is a child if it's not a summary or top level entry, but it is attached. - return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null; - } else { - return !isTopLevelEntry(entry); - } + // An entry is a child if it's not a summary or top level entry, but it is attached. + return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 5180bab60fdf..b22e9fd2fb17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -68,7 +68,8 @@ constructor( /** * The same as [allNotificationsCount], but without flows, for easy access in synchronous code. */ - val allNotificationsCountValue: Int = repository.activeNotifications.value.individuals.size + val allNotificationsCountValue: Int + get() = repository.activeNotifications.value.individuals.size /** Are any notifications being actively presented in the notification stack? */ val areAnyNotificationsPresent: Flow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index ac499601b962..1677418c5c30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.init import android.service.notification.StatusBarNotification import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags import com.android.systemui.people.widget.PeopleSpaceWidgetManager import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption import com.android.systemui.statusbar.NotificationListener @@ -72,7 +71,6 @@ constructor( private val animatedImageNotificationManager: AnimatedImageNotificationManager, private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager, private val bubblesOptional: Optional<Bubbles>, - private val featureFlags: FeatureFlags ) : NotificationsController { override fun initialize( @@ -136,5 +134,8 @@ constructor( } } - override fun getActiveNotificationsCount(): Int = notifLiveDataStore.activeNotifCount.value + override fun getActiveNotificationsCount(): Int { + NotificationsLiveDataStoreRefactor.assertInLegacyMode() + return notifLiveDataStore.activeNotifCount.value + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index d9c51089d5f8..c527bb537f19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -26,9 +26,9 @@ import android.util.MathUtils; import androidx.annotation.VisibleForTesting; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; +import com.android.systemui.res.R; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index e791a6490e15..04db653282ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -94,6 +94,7 @@ import com.android.systemui.flags.RefactorFlag; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.TouchLogger; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; @@ -187,6 +188,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private int mOverflingDistance; private float mMaxOverScroll; private boolean mIsBeingDragged; + private boolean mSendingTouchesToSceneFramework; private int mLastMotionY; private int mDownX; private int mActivePointerId = INVALID_POINTER; @@ -1509,7 +1511,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ public void setExpandedHeight(float height) { final boolean skipHeightUpdate = shouldSkipHeightUpdate(); - updateStackPosition(); + + // when scene framework is enabled, updateStackPosition is already called by + // updateTopPadding every time the stack moves, so skip it here to avoid flickering. + if (!SceneContainerFlag.isEnabled()) { + updateStackPosition(); + } if (!skipHeightUpdate) { mExpandedHeight = height; @@ -2450,6 +2457,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications, shelfIntrinsicHeight); mIntrinsicContentHeight = height; + mController.setIntrinsicContentHeight(mIntrinsicContentHeight); // The topPadding can be bigger than the regular padding when qs is expanded, in that // state the maxPanelHeight and the contentHeight should be bigger @@ -3558,8 +3566,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public boolean onTouchEvent(MotionEvent ev) { - if (mTouchHandler != null && mTouchHandler.onTouchEvent(ev)) { - return true; + if (mTouchHandler != null) { + boolean touchHandled = mTouchHandler.onTouchEvent(ev); + if (SceneContainerFlag.isEnabled() || touchHandled) { + return touchHandled; + } } return super.onTouchEvent(ev); @@ -3567,6 +3578,27 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public boolean dispatchTouchEvent(MotionEvent ev) { + if (SceneContainerFlag.isEnabled() && mIsBeingDragged) { + if (!mSendingTouchesToSceneFramework) { + // if this is the first touch being sent to the scene framework, + // convert it into a synthetic DOWN event. + mSendingTouchesToSceneFramework = true; + MotionEvent downEvent = MotionEvent.obtain(ev); + downEvent.setAction(MotionEvent.ACTION_DOWN); + mController.sendTouchToSceneFramework(downEvent); + downEvent.recycle(); + } else { + mController.sendTouchToSceneFramework(ev); + } + + if ( + ev.getActionMasked() == MotionEvent.ACTION_UP + || ev.getActionMasked() == MotionEvent.ACTION_CANCEL + ) { + setIsBeingDragged(false); + } + return false; + } return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev)); } @@ -3633,6 +3665,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return true; } + // If the scene framework is enabled, ignore all non-move gestures if we are currently + // dragging - they should be dispatched to the scene framework. Move gestures should be let + // through to determine if we are still dragging or not. + if ( + SceneContainerFlag.isEnabled() + && mIsBeingDragged + && action != MotionEvent.ACTION_MOVE + ) { + setIsBeingDragged(false); + return false; + } + switch (action) { case MotionEvent.ACTION_DOWN: { if (getChildCount() == 0 || !isInContentBounds(ev)) { @@ -3676,6 +3720,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } if (mIsBeingDragged) { + // Defer actual scrolling to the scene framework if enabled + if (SceneContainerFlag.isEnabled()) { + setIsBeingDragged(false); + return false; + } // Scroll to follow the motion event mLastMotionY = y; float scrollAmount; @@ -3770,9 +3819,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } protected boolean isInsideQsHeader(MotionEvent ev) { - if (mQsHeader == null) { - Log.wtf(TAG, "qsHeader is null while NSSL is handling a touch"); - return false; + if (SceneContainerFlag.isEnabled()) { + return ev.getY() < mController.getPlaceholderTop(); } mQsHeader.getBoundsOnScreen(mQsHeaderBound); @@ -4026,9 +4074,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestDisallowInterceptTouchEvent(true); cancelLongPress(); resetExposedMenuView(true /* animate */, true /* force */); + } else { + mSendingTouchesToSceneFramework = false; } } + @VisibleForTesting + boolean getIsBeingDragged() { + return mIsBeingDragged; + } + public void requestDisallowLongPress() { cancelLongPress(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index c0e0b73c750d..6a66bb74f16d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -80,6 +80,9 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEv import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.CommandQueue; @@ -120,6 +123,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationSnooze; +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; @@ -145,6 +149,7 @@ import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Named; +import javax.inject.Provider; /** * Controller for {@link NotificationStackScrollLayout}. @@ -181,6 +186,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final NotificationRemoteInputManager mRemoteInputManager; private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; private final ShadeController mShadeController; + private final Provider<WindowRootView> mWindowRootView; + private final NotificationStackAppearanceInteractor mStackAppearanceInteractor; private final KeyguardMediaController mKeyguardMediaController; private final SysuiStatusBarStateController mStatusBarStateController; private final KeyguardBypassController mKeyguardBypassController; @@ -689,6 +696,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { SeenNotificationsInteractor seenNotificationsInteractor, NotificationListViewBinder viewBinder, ShadeController shadeController, + SceneContainerFlags sceneContainerFlags, + Provider<WindowRootView> windowRootView, + NotificationStackAppearanceInteractor stackAppearanceInteractor, InteractionJankMonitor jankMonitor, StackStateLogger stackLogger, NotificationStackScrollLogger logger, @@ -739,6 +749,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator; mSeenNotificationsInteractor = seenNotificationsInteractor; mShadeController = shadeController; + mWindowRootView = windowRootView; + mStackAppearanceInteractor = stackAppearanceInteractor; mFeatureFlags = featureFlags; mNotificationTargetsHelper = notificationTargetsHelper; mSecureSettings = secureSettings; @@ -1076,6 +1088,28 @@ public class NotificationStackScrollLayoutController implements Dumpable { return mView.getIntrinsicContentHeight(); } + /** + * Dispatch a touch to the scene container framework. + * TODO(b/316965302): Replace findViewById to avoid DFS + */ + public void sendTouchToSceneFramework(MotionEvent ev) { + View sceneContainer = mWindowRootView.get() + .findViewById(R.id.scene_container_root_composable); + if (sceneContainer != null) { + sceneContainer.dispatchTouchEvent(ev); + } + } + + /** Get the y-coordinate of the top bound of the stack. */ + public float getPlaceholderTop() { + return mStackAppearanceInteractor.getStackBounds().getValue().getTop(); + } + + /** Set the intrinsic height of the stack content without additional padding. */ + public void setIntrinsicContentHeight(float intrinsicContentHeight) { + mStackAppearanceInteractor.setIntrinsicContentHeight(intrinsicContentHeight); + } + public void setIntrinsicPadding(int intrinsicPadding) { mView.setIntrinsicPadding(intrinsicPadding); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt index e78a694735e0..aac3c28a3426 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt @@ -30,4 +30,18 @@ class NotificationStackAppearanceRepository @Inject constructor() { /** The corner radius of the notification stack, in dp. */ val cornerRadiusDp = MutableStateFlow(32f) + + /** + * The height in px of the contents of notification stack. Depending on the number of + * notifications, this can exceed the space available on screen to show notifications, at which + * point the notification stack should become scrollable. + */ + val intrinsicContentHeight = MutableStateFlow(0f) + + /** + * The y-coordinate in px of top of the contents of the notification stack. This value can be + * negative, if the stack is scrolled such that its top extends beyond the top edge of the + * screen. + */ + val contentTop = MutableStateFlow(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 61a4dfcbd201..1dfde09f3a85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -34,12 +34,32 @@ constructor( /** The bounds of the notification stack in the current scene. */ val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow() + /** The corner radius of the notification stack, in dp. */ + val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow() + + /** + * The height in px of the contents of notification stack. Depending on the number of + * notifications, this can exceed the space available on screen to show notifications, at which + * point the notification stack should become scrollable. + */ + val intrinsicContentHeight = repository.intrinsicContentHeight.asStateFlow() + + /** The y-coordinate in px of top of the contents of the notification stack. */ + val contentTop = repository.contentTop.asStateFlow() + /** Sets the position of the notification stack in the current scene. */ fun setStackBounds(bounds: NotificationContainerBounds) { check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } repository.stackBounds.value = bounds } - /** The corner radius of the notification stack, in dp. */ - val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow() + /** Sets the height of the contents of the notification stack. */ + fun setIntrinsicContentHeight(height: Float) { + repository.intrinsicContentHeight.value = height + } + + /** Sets the y-coord in px of the top of the contents of the notification stack. */ + fun setContentTop(startY: Float) { + repository.contentTop.value = startY + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt index a9b542dcce2d..ed15f557fb39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt @@ -25,6 +25,7 @@ import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel +import kotlin.math.pow import kotlin.math.roundToInt import kotlinx.coroutines.launch @@ -43,24 +44,28 @@ object NotificationStackAppearanceViewBinder { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { viewModel.stackBounds.collect { bounds -> - controller.updateTopPadding( - bounds.top, - controller.isAddOrRemoveAnimationPending - ) controller.setRoundedClippingBounds( - it.left, - it.top, - it.right, - it.bottom, + bounds.left.roundToInt(), + bounds.top.roundToInt(), + bounds.right.roundToInt(), + bounds.bottom.roundToInt(), viewModel.cornerRadiusDp.value.dpToPx(context), viewModel.cornerRadiusDp.value.dpToPx(context), ) } } + + launch { + viewModel.contentTop.collect { + controller.updateTopPadding(it, controller.isAddOrRemoveAnimationPending) + } + } + launch { viewModel.expandFraction.collect { expandFraction -> ambientState.expansionFraction = expandFraction controller.expandedHeight = expandFraction * controller.view.height + controller.setMaxAlphaForExpansion(expandFraction.pow(0.75f)) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt index 834d3ffe63c9..74db5831f7f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt @@ -41,4 +41,7 @@ constructor( /** The corner radius of the notification stack, in dp. */ val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp + + /** The y-coordinate in px of top of the contents of the notification stack. */ + val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 9f22118e3332..385f0619288d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -21,9 +21,11 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled import javax.inject.Inject +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow /** @@ -35,6 +37,7 @@ class NotificationsPlaceholderViewModel @Inject constructor( private val interactor: NotificationStackAppearanceInteractor, + shadeInteractor: ShadeInteractor, flags: SceneContainerFlags, featureFlags: FeatureFlagsClassic, ) { @@ -66,4 +69,22 @@ constructor( /** The corner radius of the placeholder, in dp. */ val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp + + /** + * The height in px of the contents of notification stack. Depending on the number of + * notifications, this can exceed the space available on screen to show notifications, at which + * point the notification stack should become scrollable. + */ + val intrinsicContentHeight = interactor.intrinsicContentHeight + + /** + * The amount [0-1] that the shade has been opened. At 0, the shade is closed; at 1, the shade + * is open. + */ + val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion + + /** Sets the y-coord in px of the top of the contents of the notification stack. */ + fun onContentTopChanged(padding: Float) { + interactor.setContentTop(padding) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt index 7c626a141a4a..e0c6bbad5635 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt @@ -44,6 +44,8 @@ import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeViewController import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.util.concurrency.FakeExecutor @@ -56,6 +58,7 @@ import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import org.junit.Before @@ -89,6 +92,9 @@ class BackActionInteractorTest : SysuiTestCase() { @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher @Mock private lateinit var iStatusBarService: IStatusBarService @Mock private lateinit var headsUpManager: HeadsUpManager + private val activeNotificationsRepository = ActiveNotificationListRepository() + private val activeNotificationsInteractor = + ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher()) private val keyguardRepository = FakeKeyguardRepository() private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy { @@ -98,6 +104,7 @@ class BackActionInteractorTest : SysuiTestCase() { keyguardRepository, headsUpManager, powerInteractor, + activeNotificationsInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt new file mode 100644 index 000000000000..d397fc202637 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys.ui.viewmodel + +import android.hardware.input.InputManager +import android.hardware.input.StickyModifierState +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository +import com.android.systemui.keyboard.stickykeys.StickyKeysLogger +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions + +@SmallTest +@RunWith(JUnit4::class) +class StickyKeysIndicatorViewModelTest : SysuiTestCase() { + + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) + private lateinit var viewModel: StickyKeysIndicatorViewModel + private val inputManager = mock<InputManager>() + private val keyboardRepository = FakeKeyboardRepository() + private val captor = + ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java) + + @Before + fun setup() { + val stickyKeysRepository = StickyKeysRepositoryImpl( + inputManager, + dispatcher, + mock<StickyKeysLogger>() + ) + viewModel = + StickyKeysIndicatorViewModel( + stickyKeysRepository = stickyKeysRepository, + keyboardRepository = keyboardRepository, + applicationScope = testScope.backgroundScope, + ) + } + + @Test + fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnected() { + testScope.runTest { + collectLastValue(viewModel.indicatorContent) + runCurrent() + verifyZeroInteractions(inputManager) + + keyboardRepository.setIsAnyKeyboardConnected(true) + runCurrent() + + verify(inputManager) + .registerStickyModifierStateListener( + any(), + any(InputManager.StickyModifierStateListener::class.java) + ) + } + } + + @Test + fun stopsListeningToStickyKeysWhenKeyboardDisconnects() { + testScope.runTest { + collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + runCurrent() + + keyboardRepository.setIsAnyKeyboardConnected(false) + runCurrent() + + verify(inputManager).unregisterStickyModifierStateListener(any()) + } + } + + @Test + fun emitsStickyKeysListWhenStickyKeyIsPressed() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf(ALT to false)) + + assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(false))) + } + } + + @Test + fun emitsEmptyListWhenNoStickyKeysAreActive() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(emptyMap()) + + assertThat(stickyKeys).isEqualTo(emptyMap<ModifierKey, Locked>()) + } + } + + @Test + fun passesAllStickyKeysToDialog() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf( + ALT to false, + META to false, + SHIFT to false)) + + assertThat(stickyKeys).isEqualTo(mapOf( + ALT to Locked(false), + META to Locked(false), + SHIFT to Locked(false), + )) + } + } + + @Test + fun showsOnlyLockedStateIfKeyIsStickyAndLocked() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf( + ALT to false, + ALT to true)) + + assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(true))) + } + } + + @Test + fun doesNotChangeOrderOfKeysIfTheyBecomeLocked() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf( + META to false, + SHIFT to false, // shift is sticky but not locked + CTRL to false)) + val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false)) + + setStickyKeys(mapOf( + SHIFT to false, + SHIFT to true, // shift is now locked + META to false, + CTRL to false)) + assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true))) + .isEqualTo(previousShiftIndex) + } + } + + private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) { + runCurrent() + verify(inputManager).registerStickyModifierStateListener(any(), captor.capture()) + captor.value.onStickyModifierStateChanged(TestStickyModifierState(keys)) + runCurrent() + } + + private class TestStickyModifierState(private val keys: Map<ModifierKey, Boolean>) : + StickyModifierState() { + + private fun isOn(key: ModifierKey) = keys.any { it.key == key && !it.value } + private fun isLocked(key: ModifierKey) = keys.any { it.key == key && it.value } + + override fun isAltGrModifierLocked() = isLocked(ALT_GR) + override fun isAltGrModifierOn() = isOn(ALT_GR) + override fun isAltModifierLocked() = isLocked(ALT) + override fun isAltModifierOn() = isOn(ALT) + override fun isCtrlModifierLocked() = isLocked(CTRL) + override fun isCtrlModifierOn() = isOn(CTRL) + override fun isMetaModifierLocked() = isLocked(META) + override fun isMetaModifierOn() = isOn(META) + override fun isShiftModifierLocked() = isLocked(SHIFT) + override fun isShiftModifierOn() = isOn(SHIFT) + } +} 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 0ea4e9f8d416..8b6611f339c0 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 @@ -1342,14 +1342,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD) runCurrent() - // WHEN the keyguard is occluded and aod ends + // WHEN the keyguard is occluded keyguardRepository.setKeyguardOccluded(true) - keyguardRepository.setDozeTransitionModel( - DozeTransitionModel( - from = DozeStateModel.DOZE_AOD, - to = DozeStateModel.FINISH, - ) - ) runCurrent() val info = @@ -1366,6 +1360,30 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun aodToPrimaryBouncer() = + testScope.runTest { + // GIVEN a prior transition has run to AOD + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD) + runCurrent() + + // WHEN the primary bouncer is set to show + bouncerRepository.setPrimaryShow(true) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.AOD) + assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun lockscreenToOccluded_fromCameraGesture() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt new file mode 100644 index 000000000000..60eb3aec190f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs + +import android.testing.AndroidTestingRunner +import android.view.KeyEvent +import android.view.KeyEvent.KEYCODE_DPAD_LEFT +import android.view.View +import androidx.core.util.Consumer +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class LeftRightArrowPressedListenerTest : SysuiTestCase() { + + private lateinit var underTest: LeftRightArrowPressedListener + private val callback = + object : Consumer<Int> { + var lastValue: Int? = null + + override fun accept(keyCode: Int?) { + lastValue = keyCode + } + } + + private val view = View(context) + + @Before + fun setUp() { + underTest = LeftRightArrowPressedListener.createAndRegisterListenerForView(view) + underTest.setArrowKeyPressedListener(callback) + } + + @Test + fun shouldTriggerCallback_whenArrowUpReceived_afterArrowDownReceived() { + underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT) + + underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT) + + assertThat(callback.lastValue).isEqualTo(KEYCODE_DPAD_LEFT) + } + + @Test + fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownNotReceived() { + underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT) + + assertThat(callback.lastValue).isNull() + } + + @Test + fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownWasRepeated() { + underTest.sendKeyWithRepeat(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT, repeat = 2) + underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT) + + assertThat(callback.lastValue).isNull() + } + + @Test + fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownReceivedBeforeLosingFocus() { + underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT) + underTest.onFocusChange(view, hasFocus = false) + underTest.onFocusChange(view, hasFocus = true) + + underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT) + + assertThat(callback.lastValue).isNull() + } + + private fun LeftRightArrowPressedListener.sendKey(action: Int, keyCode: Int) { + onKey(view, keyCode, KeyEvent(action, keyCode)) + } + + private fun LeftRightArrowPressedListener.sendKeyWithRepeat( + action: Int, + keyCode: Int, + repeat: Int + ) { + val keyEvent = + KeyEvent( + /* downTime= */ 0L, + /* eventTime= */ 0L, + /* action= */ action, + /* code= */ KEYCODE_DPAD_LEFT, + /* repeat= */ repeat + ) + onKey(view, keyCode, keyEvent) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt index db9e548e74c8..8ef3f57103a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt @@ -2,11 +2,13 @@ package com.android.systemui.qs import android.content.Context import android.testing.AndroidTestingRunner -import android.view.KeyEvent import android.view.View import android.widget.Scroller import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.PageIndicator.PageScrollActionListener +import com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT +import com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -22,7 +24,7 @@ import org.mockito.MockitoAnnotations class PagedTileLayoutTest : SysuiTestCase() { @Mock private lateinit var pageIndicator: PageIndicator - @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener> + @Captor private lateinit var captor: ArgumentCaptor<PageScrollActionListener> private lateinit var pageTileLayout: TestPagedTileLayout private lateinit var scroller: Scroller @@ -32,7 +34,7 @@ class PagedTileLayoutTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) pageTileLayout = TestPagedTileLayout(mContext) pageTileLayout.setPageIndicator(pageIndicator) - verify(pageIndicator).setOnKeyListener(captor.capture()) + verify(pageIndicator).setPageScrollActionListener(captor.capture()) setViewWidth(pageTileLayout, width = PAGE_WIDTH) scroller = pageTileLayout.mScroller } @@ -43,28 +45,27 @@ class PagedTileLayoutTest : SysuiTestCase() { } @Test - fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() { + fun scrollsRight_afterRightScrollActionTriggered() { pageTileLayout.currentPageIndex = 0 - sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT) + sendScrollActionEvent(RIGHT) assertThat(scroller.isFinished).isFalse() // aka we're scrolling assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH) } @Test - fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() { + fun scrollsLeft_afterLeftScrollActionTriggered() { pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page - sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT) + sendScrollActionEvent(LEFT) assertThat(scroller.isFinished).isFalse() // aka we're scrolling assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH) } - private fun sendUpEvent(keyCode: Int) { - val event = KeyEvent(KeyEvent.ACTION_UP, keyCode) - captor.value.onKey(pageIndicator, keyCode, event) + private fun sendScrollActionEvent(@PageScrollActionListener.Direction direction: Int) { + captor.value.onScrollActionTriggered(direction) } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 215f8b1c462f..c4911a41b4a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -33,6 +33,8 @@ import com.android.systemui.scene.data.repository.WindowRootViewVisibilityReposi import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.row.NotificationGutsManager import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -45,6 +47,7 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import dagger.Lazy +import kotlinx.coroutines.test.StandardTestDispatcher import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -59,6 +62,8 @@ import org.mockito.MockitoAnnotations @SmallTest class ShadeControllerImplTest : SysuiTestCase() { private val executor = FakeExecutor(FakeSystemClock()) + private val testDispatcher = StandardTestDispatcher() + private val activeNotificationsRepository = ActiveNotificationListRepository() @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var keyguardStateController: KeyguardStateController @@ -84,6 +89,7 @@ class ShadeControllerImplTest : SysuiTestCase() { FakeKeyguardRepository(), headsUpManager, PowerInteractorFactory.create().powerInteractor, + ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt index 65697b736cbd..36f643ab9cca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -24,7 +24,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -53,8 +52,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @@ -78,28 +77,22 @@ class ConversationCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: ConversationCoordinator - private val featureFlags = FakeFeatureFlagsClassic() - @Before fun setUp() { MockitoAnnotations.initMocks(this) - coordinator = ConversationCoordinator( - peopleNotificationIdentifier, - conversationIconManager, - HighPriorityProvider( + coordinator = + ConversationCoordinator( peopleNotificationIdentifier, - GroupMembershipManagerImpl(featureFlags) - ), - headerController - ) + conversationIconManager, + HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()), + headerController + ) whenever(channel.isImportantConversation).thenReturn(true) coordinator.attach(pipeline) // capture arguments: - promoter = withArgCaptor { - verify(pipeline).addPromoter(capture()) - } + promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) } beforeRenderListListener = withArgCaptor { verify(pipeline).addOnBeforeRenderListListener(capture()) } @@ -111,10 +104,10 @@ class ConversationCoordinatorTest : SysuiTestCase() { entry = NotificationEntryBuilder().setChannel(channel).build() val section = NotifSection(peopleAlertingSectioner, 0) - entryA = NotificationEntryBuilder().setChannel(channel) - .setSection(section).setTag("A").build() - entryB = NotificationEntryBuilder().setChannel(channel) - .setSection(section).setTag("B").build() + entryA = + NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build() + entryB = + NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build() } @Test @@ -129,11 +122,12 @@ class ConversationCoordinatorTest : SysuiTestCase() { val altChildA = NotificationEntryBuilder().setTag("A").build() val altChildB = NotificationEntryBuilder().setTag("B").build() val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build() - val groupEntry = GroupEntryBuilder() - .setParent(GroupEntry.ROOT_ENTRY) - .setSummary(summary) - .setChildren(listOf(entry, altChildA, altChildB)) - .build() + val groupEntry = + GroupEntryBuilder() + .setParent(GroupEntry.ROOT_ENTRY) + .setSummary(summary) + .setChildren(listOf(entry, altChildA, altChildB)) + .build() assertTrue(promoter.shouldPromoteToTopLevel(entry)) assertFalse(promoter.shouldPromoteToTopLevel(altChildA)) assertFalse(promoter.shouldPromoteToTopLevel(altChildB)) @@ -146,41 +140,42 @@ class ConversationCoordinatorTest : SysuiTestCase() { @Test fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() { // GIVEN - val alertingEntry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_DEFAULT).build() + val alertingEntry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry)) - .thenReturn(TYPE_PERSON) + .thenReturn(TYPE_PERSON) // put alerting people notifications in this section assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue() - } + } @Test fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() { // GIVEN - val silentEntry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_LOW).build() + val silentEntry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry)) - .thenReturn(TYPE_PERSON) + .thenReturn(TYPE_PERSON) // THEN put silent people notifications in this section assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue() // People Alerting sectioning happens before the silent one. - // It claims high important conversations and rest of conversations will be considered as silent. + // It claims high important conversations and rest of conversations will be considered as + // silent. assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse() } @Test fun testNotInPeopleSection() { // GIVEN - val entry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_LOW).build() - val importantEntry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_HIGH).build() + val entry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build() + val importantEntry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry)) - .thenReturn(TYPE_NON_PERSON) + .thenReturn(TYPE_NON_PERSON) whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry)) - .thenReturn(TYPE_NON_PERSON) + .thenReturn(TYPE_NON_PERSON) // THEN - only put people notification either silent or alerting assertThat(peopleSilentSectioner.isInSection(entry)).isFalse() @@ -190,19 +185,23 @@ class ConversationCoordinatorTest : SysuiTestCase() { @Test fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() { // GIVEN - val altChildA = NotificationEntryBuilder().setTag("A") - .setImportance(IMPORTANCE_DEFAULT).build() - val altChildB = NotificationEntryBuilder().setTag("B") - .setImportance(IMPORTANCE_LOW).build() - val summary = NotificationEntryBuilder().setId(2) - .setImportance(IMPORTANCE_LOW).setChannel(channel).build() - val groupEntry = GroupEntryBuilder() + val altChildA = + NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build() + val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build() + val summary = + NotificationEntryBuilder() + .setId(2) + .setImportance(IMPORTANCE_LOW) + .setChannel(channel) + .build() + val groupEntry = + GroupEntryBuilder() .setParent(GroupEntry.ROOT_ENTRY) .setSummary(summary) .setChildren(listOf(altChildA, altChildB)) .build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary)) - .thenReturn(TYPE_PERSON) + .thenReturn(TYPE_PERSON) // THEN assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 168e782d0481..ff02ef3d4e62 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -28,6 +28,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; + import android.app.Notification; import android.os.Handler; import android.os.Looper; @@ -56,6 +58,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.logging.nano.Notifications; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -110,6 +114,11 @@ public class NotificationLoggerTest extends SysuiTestCase { private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); private final PowerInteractor mPowerInteractor = PowerInteractorFactory.create().getPowerInteractor(); + private final ActiveNotificationListRepository mActiveNotificationListRepository = + new ActiveNotificationListRepository(); + private final ActiveNotificationsInteractor mActiveNotificationsInteractor = + new ActiveNotificationsInteractor(mActiveNotificationListRepository, + StandardTestDispatcher(null, null)); private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); @@ -123,7 +132,8 @@ public class NotificationLoggerTest extends SysuiTestCase { new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor), mKeyguardRepository, mHeadsUpManager, - mPowerInteractor); + mPowerInteractor, + mActiveNotificationsInteractor); mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); mEntry = new NotificationEntryBuilder() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 8a730cfd7ddd..71613edb8737 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -42,6 +42,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; + import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; @@ -85,6 +87,8 @@ import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -156,6 +160,12 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private UserManager mUserManager; + private final ActiveNotificationListRepository mActiveNotificationListRepository = + new ActiveNotificationListRepository(); + private final ActiveNotificationsInteractor mActiveNotificationsInteractor = + new ActiveNotificationsInteractor(mActiveNotificationListRepository, + StandardTestDispatcher(null, null)); + private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; @Before @@ -171,7 +181,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { new WindowRootViewVisibilityRepository(mBarService, mExecutor), new FakeKeyguardRepository(), mHeadsUpManager, - PowerInteractorFactory.create().getPowerInteractor()); + PowerInteractorFactory.create().getPowerInteractor(), + mActiveNotificationsInteractor); mGutsManager = new NotificationGutsManager( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 88662b60c7d1..89f826b2049d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -66,6 +66,8 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -91,6 +93,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScrimController; @@ -112,6 +115,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import javax.inject.Provider; + /** * Tests for {@link NotificationStackScrollLayoutController}. */ @@ -153,6 +158,9 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; @Mock private ShadeController mShadeController; + @Mock private SceneContainerFlags mSceneContainerFlags; + @Mock private Provider<WindowRootView> mWindowRootView; + @Mock private NotificationStackAppearanceInteractor mNotificationStackAppearanceInteractor; @Mock private InteractionJankMonitor mJankMonitor; private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(), logcatLogBuffer()); @@ -724,6 +732,9 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mSeenNotificationsInteractor, mViewBinder, mShadeController, + mSceneContainerFlags, + mWindowRootView, + mNotificationStackAppearanceInteractor, mJankMonitor, mStackLogger, mLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index a1721208b2f2..4afcc8c9da43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -20,6 +20,7 @@ import static android.view.View.GONE; import static android.view.WindowInsets.Type.ime; import static com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION; +import static com.android.systemui.Flags.FLAG_SCENE_CONTAINER; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL; @@ -37,6 +38,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; @@ -51,6 +53,7 @@ import static org.mockito.Mockito.when; import android.graphics.Insets; import android.graphics.Rect; +import android.os.SystemClock; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; @@ -74,6 +77,7 @@ import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.EmptyShadeView; @@ -93,11 +97,13 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -947,6 +953,78 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { verify(runnable).run(); } + @Test + public void testDispatchTouchEvent_sceneContainerDisabled() { + Assume.assumeFalse(SceneContainerFlag.isEnabled()); + + MotionEvent event = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_MOVE, + 0, + 0, + 0 + ); + + mStackScroller.dispatchTouchEvent(event); + + verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any()); + } + + @Test + public void testDispatchTouchEvent_sceneContainerEnabled() { + Assume.assumeTrue(SceneContainerFlag.isEnabled()); + mStackScroller.setIsBeingDragged(true); + + MotionEvent moveEvent = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_MOVE, + 0, + 0, + 0 + ); + MotionEvent syntheticDownEvent = moveEvent.copy(); + syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN); + mStackScroller.dispatchTouchEvent(moveEvent); + + verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat( + new MotionEventMatcher(syntheticDownEvent))); + + mStackScroller.dispatchTouchEvent(moveEvent); + + verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent); + } + + @Test + @EnableFlags(FLAG_SCENE_CONTAINER) + public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() { + Assume.assumeTrue(SceneContainerFlag.isEnabled()); + mStackScroller.setIsBeingDragged(true); + + MotionEvent upEvent = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_UP, + 0, + 0, + 0 + ); + MotionEvent syntheticDownEvent = upEvent.copy(); + syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN); + + mStackScroller.dispatchTouchEvent(upEvent); + + verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat( + new MotionEventMatcher(syntheticDownEvent))); + + mStackScroller.dispatchTouchEvent(upEvent); + + verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat( + new MotionEventMatcher(upEvent))); + assertFalse(mStackScroller.getIsBeingDragged()); + } + private void setBarStateForTest(int state) { // Can't inject this through the listener or we end up on the actual implementation // rather than the mock because the spy just coppied the anonymous inner /shruggie. @@ -993,4 +1071,21 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { /* metaState= */0 ); } + + private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> { + private final MotionEvent mLeftEvent; + + MotionEventMatcher(MotionEvent leftEvent) { + mLeftEvent = leftEvent; + } + + @Override + public boolean matches(MotionEvent right) { + return mLeftEvent.getActionMasked() == right.getActionMasked() + && mLeftEvent.getDownTime() == right.getDownTime() + && mLeftEvent.getEventTime() == right.getEventTime() + && mLeftEvent.getX() == right.getX() + && mLeftEvent.getY() == right.getY(); + } + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt new file mode 100644 index 000000000000..79107cc818ba --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.communal.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.communalPrefsRepository: CommunalPrefsRepository by + Kosmos.Fixture { fakeCommunalPrefsRepository } +val Kosmos.fakeCommunalPrefsRepository by Kosmos.Fixture { FakeCommunalPrefsRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt new file mode 100644 index 000000000000..d3ed58bf5be0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.communal.data.repository + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Fake implementation of [CommunalPrefsRepository] */ +class FakeCommunalPrefsRepository : CommunalPrefsRepository { + private val _isCtaDismissed = MutableStateFlow(false) + override val isCtaDismissed: Flow<Boolean> = _isCtaDismissed.asStateFlow() + + override suspend fun setCtaDismissedForCurrentUser() { + _isCtaDismissed.value = true + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt index e82cae45c8f0..c85c27e277b4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn @@ -53,12 +52,4 @@ class FakeCommunalRepository( fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) { _isCommunalHubShowing.value = isCommunalHubShowing } - - private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true) - override val isCtaTileInViewModeVisible: Flow<Boolean> = - _isCtaTileInViewModeVisible.asStateFlow() - - override fun setCtaTileInViewModeVisibility(isVisible: Boolean) { - _isCtaTileInViewModeVisible.value = isVisible - } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt index 95ff889177b8..8430cf681e2a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt @@ -19,6 +19,7 @@ package com.android.systemui.communal.domain.interactor import android.appwidget.AppWidgetHost import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository @@ -42,6 +43,7 @@ object CommunalInteractorFactory { mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(), smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(), tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(), + communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(), appWidgetHost: AppWidgetHost = mock(), editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(), ): WithDependencies { @@ -55,6 +57,7 @@ object CommunalInteractorFactory { testScope, communalRepository, widgetRepository, + communalPrefsRepository, mediaRepository, smartspaceRepository, tutorialRepository, @@ -66,6 +69,7 @@ object CommunalInteractorFactory { CommunalInteractor( communalRepository, widgetRepository, + communalPrefsRepository, mediaRepository, smartspaceRepository, withDeps.keyguardInteractor, @@ -79,6 +83,7 @@ object CommunalInteractorFactory { val testScope: TestScope, val communalRepository: FakeCommunalRepository, val widgetRepository: FakeCommunalWidgetRepository, + val communalPrefsRepository: FakeCommunalPrefsRepository, val mediaRepository: FakeCommunalMediaRepository, val smartspaceRepository: FakeSmartspaceRepository, val tutorialRepository: FakeCommunalTutorialRepository, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 649b373258ef..7cbbaabe9dfa 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.domain.interactor import com.android.systemui.communal.data.repository.communalMediaRepository +import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -30,6 +31,7 @@ val Kosmos.communalInteractor by Fixture { communalRepository = communalRepository, widgetRepository = communalWidgetRepository, mediaRepository = communalMediaRepository, + communalPrefsRepository = communalPrefsRepository, smartspaceRepository = smartspaceRepository, appWidgetHost = mock(), keyguardInteractor = keyguardInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt index 59f0ec3cd3a5..37b1b08f07c5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.appwidget.AppWidgetHost import com.android.systemui.communal.data.repository.communalMediaRepository +import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor @@ -32,6 +33,7 @@ val Kosmos.communalInteractor by communalRepository = communalRepository, widgetRepository = communalWidgetRepository, mediaRepository = communalMediaRepository, + communalPrefsRepository = communalPrefsRepository, smartspaceRepository = smartspaceRepository, keyguardInteractor = keyguardInteractor, appWidgetHost = mock(AppWidgetHost::class.java), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt index da956ec67696..da956ec67696 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java index dda7fadde2d7..4efcada96a14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java @@ -52,32 +52,38 @@ public class GroupEntryBuilder { return ge; } + /** Sets the group key. */ public GroupEntryBuilder setKey(String key) { mKey = key; return this; } + /** Sets the creation time. */ public GroupEntryBuilder setCreationTime(long creationTime) { mCreationTime = creationTime; return this; } + /** Sets the parent entry of the group. */ public GroupEntryBuilder setParent(@Nullable GroupEntry entry) { mParent = entry; return this; } + /** Sets the section the group belongs to. */ public GroupEntryBuilder setSection(@Nullable NotifSection section) { mNotifSection = section; return this; } + /** Sets the group summary. */ public GroupEntryBuilder setSummary( NotificationEntry summary) { mSummary = summary; return this; } + /** Sets the group children. */ public GroupEntryBuilder setChildren(List<NotificationEntry> children) { mChildren.clear(); mChildren.addAll(children); @@ -90,6 +96,7 @@ public class GroupEntryBuilder { return this; } + /** Get the group's internal children list. */ public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) { return groupEntry.getRawChildren(); } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt index 0dbade76979c..d7e948eefc95 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt @@ -20,11 +20,13 @@ import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.scene.shared.flag.sceneContainerFlags +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor val Kosmos.notificationsPlaceholderViewModel by Fixture { NotificationsPlaceholderViewModel( interactor = notificationStackAppearanceInteractor, + shadeInteractor = shadeInteractor, flags = sceneContainerFlags, featureFlags = featureFlagsClassic, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt index d9beabb11ad9..d80ee758269f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt @@ -23,6 +23,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.policy.headsUpManager val Kosmos.windowRootViewVisibilityInteractor by Fixture { @@ -32,5 +33,6 @@ val Kosmos.windowRootViewVisibilityInteractor by Fixture { keyguardRepository = keyguardRepository, headsUpManager = headsUpManager, powerInteractor = powerInteractor, + activeNotificationsInteractor = activeNotificationsInteractor, ) } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index a856f424be5c..f3b74ea00a58 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1735,6 +1735,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState processResponseLockedForPcc(response, response.getClientState(), requestFlags); mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis(); + mFillResponseEventLogger.logAndEndEvent(); } @@ -1847,6 +1848,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } synchronized (mLock) { + // TODO(b/319913595): refactor logging for fill response for primary and secondary + // providers + // Start a new FillResponse logger for the success case. + mFillResponseEventLogger.startLogForNewResponse(); + mFillResponseEventLogger.maybeSetRequestId(fillResponse.getRequestId()); + mFillResponseEventLogger.maybeSetAppPackageUid(uid); + mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SUCCESS); + mFillResponseEventLogger.startResponseProcessingTime(); + // Time passed since session was created + final long fillRequestReceivedRelativeTimestamp = + SystemClock.elapsedRealtime() - mLatencyBaseTime; + mPresentationStatsEventLogger.maybeSetFillResponseReceivedTimestampMs( + (int) (fillRequestReceivedRelativeTimestamp)); + mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis( + (int) (fillRequestReceivedRelativeTimestamp)); + if (mDestroyed) { + Slog.w(TAG, "Call to Session#onSecondaryFillResponse() rejected - session: " + + id + " destroyed"); + mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); + mFillResponseEventLogger.logAndEndEvent(); + return; + } + + List<Dataset> datasetList = fillResponse.getDatasets(); + int datasetCount = (datasetList == null) ? 0 : datasetList.size(); + mFillResponseEventLogger.maybeSetTotalDatasetsProvided(datasetCount); + mFillResponseEventLogger.maybeSetAvailableCount(datasetCount); if (mSecondaryResponses == null) { mSecondaryResponses = new SparseArray<>(2); } @@ -1859,6 +1887,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (currentView != null) { currentView.maybeCallOnFillReady(flags); } + mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis(); + mFillResponseEventLogger.logAndEndEvent(); } } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index df8d9e1a406c..8fa0089c6db4 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -65,6 +65,9 @@ import static android.app.AppOpsManager.opRestrictsRead; import static android.app.AppOpsManager.opToName; import static android.app.AppOpsManager.opToPublicName; import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; @@ -973,7 +976,29 @@ public class AppOpsService extends IAppOpsService.Stub { String pkgName = intent.getData().getEncodedSchemeSpecificPart(); int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID); - if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) { + if (action.equals(ACTION_PACKAGE_ADDED) + && !intent.getBooleanExtra(EXTRA_REPLACING, false)) { + PackageInfo pi = getPackageManagerInternal().getPackageInfo(pkgName, + PackageManager.GET_PERMISSIONS, Process.myUid(), + UserHandle.getUserId(uid)); + boolean isSamplingTarget = isSamplingTarget(pi); + synchronized (AppOpsService.this) { + if (isSamplingTarget) { + mRarelyUsedPackages.add(pkgName); + } + UidState uidState = getUidStateLocked(uid, true); + if (!uidState.pkgOps.containsKey(pkgName)) { + uidState.pkgOps.put(pkgName, + new Ops(pkgName, uidState)); + } + + createSandboxUidStateIfNotExistsForAppLocked(uid); + } + } else if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) { + synchronized (AppOpsService.this) { + packageRemovedLocked(uid, pkgName); + } + } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) { AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName); if (pkg == null) { return; @@ -1052,7 +1077,9 @@ public class AppOpsService extends IAppOpsService.Stub { mHistoricalRegistry.systemReady(mContext.getContentResolver()); IntentFilter packageUpdateFilter = new IntentFilter(); + packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED); packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED); packageUpdateFilter.addDataScheme("package"); mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL, @@ -1079,7 +1106,7 @@ public class AppOpsService extends IAppOpsService.Stub { String action; if (!ArrayUtils.contains(pkgsInUid, pkg)) { - action = Intent.ACTION_PACKAGE_REMOVED; + action = ACTION_PACKAGE_REMOVED; } else { action = Intent.ACTION_PACKAGE_REPLACED; } @@ -1160,44 +1187,6 @@ public class AppOpsService extends IAppOpsService.Stub { // onUserRemoved handled by #removeUser }); - - getPackageManagerInternal().getPackageList( - new PackageManagerInternal.PackageListObserver() { - @Override - public void onPackageAdded(String packageName, int appId) { - PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName, - PackageManager.GET_PERMISSIONS, Process.myUid(), - mContext.getUserId()); - boolean isSamplingTarget = isSamplingTarget(pi); - int[] userIds = getUserManagerInternal().getUserIds(); - synchronized (AppOpsService.this) { - if (isSamplingTarget) { - mRarelyUsedPackages.add(packageName); - } - for (int i = 0; i < userIds.length; i++) { - int uid = UserHandle.getUid(userIds[i], appId); - UidState uidState = getUidStateLocked(uid, true); - if (!uidState.pkgOps.containsKey(packageName)) { - uidState.pkgOps.put(packageName, - new Ops(packageName, uidState)); - } - - createSandboxUidStateIfNotExistsForAppLocked(uid); - } - } - } - - @Override - public void onPackageRemoved(String packageName, int appId) { - int[] userIds = getUserManagerInternal().getUserIds(); - synchronized (AppOpsService.this) { - for (int i = 0; i < userIds.length; i++) { - int uid = UserHandle.getUid(userIds[i], appId); - packageRemovedLocked(uid, packageName); - } - } - } - }); } /** diff --git a/services/core/java/com/android/server/broadcastradio/TEST_MAPPING b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING new file mode 100644 index 000000000000..ee4eeb634c84 --- /dev/null +++ b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "frameworks/base/core/tests/BroadcastRadioTests" + } + ] +} diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index 6236e2b933bc..46668de042d4 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -1370,7 +1370,7 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { public boolean isVirtualDevice(int deviceId) { VirtualDeviceManagerInternal vdm = LocalServices.getService( VirtualDeviceManagerInternal.class); - return vdm == null || vdm.isInputDeviceOwnedByVirtualDevice(deviceId); + return vdm != null && vdm.isInputDeviceOwnedByVirtualDevice(deviceId); } private static int[] getScriptCodes(@Nullable Locale locale) { diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 4d19eade5a05..d7188c7f10c6 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -42,7 +42,6 @@ import android.util.ArraySet; import android.util.Log; import android.util.Slog; -import com.android.internal.annotations.Keep; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.RingBuffer; import com.android.server.am.ProcessList; @@ -414,7 +413,7 @@ public class NetworkPolicyLogger { private static final Date sDate = new Date(); public LogBuffer(int capacity) { - super(Data.class, capacity); + super(Data::new, Data[]::new, capacity); } public void uidStateChanged(int uid, int procState, long procStateSeq, @@ -690,12 +689,8 @@ public class NetworkPolicyLogger { /** * Container class for all networkpolicy events data. - * - * Note: This class needs to be public for RingBuffer class to be able to create - * new instances of this. */ - @Keep - public static final class Data { + private static final class Data { public int type; public long timeStamp; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c9f45e523e3b..2ae040a69583 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5931,8 +5931,7 @@ public class NotificationManagerService extends SystemService { newVisualEffects, policy.priorityConversationSenders); if (shouldApplyAsImplicitRule) { - mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy, - origin); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy); } else { ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion, policy); diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index c7f570353a5b..93ffd974bb80 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -32,6 +32,7 @@ import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; +import static com.android.internal.util.Preconditions.checkArgument; import android.annotation.DrawableRes; import android.annotation.NonNull; @@ -427,6 +428,7 @@ public class ZenModeHelper { public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule, @ConfigChangeOrigin int origin, String reason, int callingUid) { + requirePublicOrigin("addAutomaticZenRule", origin); if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) { PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner()); if (component == null) { @@ -525,6 +527,7 @@ public class ZenModeHelper { public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule, @ConfigChangeOrigin int origin, String reason, int callingUid) { + requirePublicOrigin("updateAutomaticZenRule", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -602,7 +605,11 @@ public class ZenModeHelper { rule = newImplicitZenRule(callingPkg); newConfig.automaticRules.put(rule.id, rule); } - rule.zenMode = zenMode; + // If the user has changed the rule's *zenMode*, then don't let app overwrite it. + // We allow the update if the user has only changed other aspects of the rule. + if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) { + rule.zenMode = zenMode; + } rule.snoozing = false; rule.condition = new Condition(rule.conditionId, mContext.getString(R.string.zen_mode_implicit_activated), @@ -625,7 +632,7 @@ public class ZenModeHelper { * {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}. */ void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid, - NotificationManager.Policy policy, @ConfigChangeOrigin int origin) { + NotificationManager.Policy policy) { if (!android.app.Flags.modesApi()) { Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!"); return; @@ -641,10 +648,17 @@ public class ZenModeHelper { rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; newConfig.automaticRules.put(rule.id, rule); } - // TODO: b/308673679 - Keep user customization of this rule! - rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy); - setConfigLocked(newConfig, /* triggeringComponent= */ null, origin, - "applyGlobalPolicyAsImplicitZenRule", callingUid); + // If the user has changed the rule's *ZenPolicy*, then don't let app overwrite it. + // We allow the update if the user has only changed other aspects of the rule. + if (rule.zenPolicyUserModifiedFields == 0) { + updatePolicy( + rule, + ZenAdapters.notificationPolicyToZenPolicy(policy), + /* updateBitmask= */ false); + + setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP, + "applyGlobalPolicyAsImplicitZenRule", callingUid); + } } } @@ -726,6 +740,7 @@ public class ZenModeHelper { boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason, int callingUid) { + requirePublicOrigin("removeAutomaticZenRule", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -758,6 +773,7 @@ public class ZenModeHelper { boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin, String reason, int callingUid) { + requirePublicOrigin("removeAutomaticZenRules", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -806,6 +822,7 @@ public class ZenModeHelper { void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin, int callingUid) { + requirePublicOrigin("setAutomaticZenRuleState", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return; @@ -819,6 +836,7 @@ public class ZenModeHelper { void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, @ConfigChangeOrigin int origin, int callingUid) { + requirePublicOrigin("setAutomaticZenRuleState", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return; @@ -988,7 +1006,7 @@ public class ZenModeHelper { return null; } - void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, + private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, @ConfigChangeOrigin int origin, boolean isNew) { if (Flags.modesApi()) { // These values can always be edited by the app, so we apply changes immediately. @@ -1053,11 +1071,9 @@ public class ZenModeHelper { rule.zenMode = newZenMode; // Updates the bitmask and values for all policy fields, based on the origin. - rule.zenPolicy = updatePolicy(rule.zenPolicy, automaticZenRule.getZenPolicy(), - updateBitmask); + updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask); // Updates the bitmask and values for all device effect fields, based on the origin. - rule.zenDeviceEffects = updateZenDeviceEffects( - rule.zenDeviceEffects, automaticZenRule.getDeviceEffects(), + updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(), origin == UPDATE_ORIGIN_APP, updateBitmask); } else { if (rule.enabled != automaticZenRule.isEnabled()) { @@ -1069,13 +1085,6 @@ public class ZenModeHelper { rule.enabled = automaticZenRule.isEnabled(); rule.modified = automaticZenRule.isModified(); rule.zenPolicy = automaticZenRule.getZenPolicy(); - if (Flags.modesApi()) { - rule.zenDeviceEffects = updateZenDeviceEffects( - rule.zenDeviceEffects, - automaticZenRule.getDeviceEffects(), - origin == UPDATE_ORIGIN_APP, - origin == UPDATE_ORIGIN_USER); - } rule.zenMode = NotificationManager.zenModeFromInterruptionFilter( automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF); rule.configurationActivity = automaticZenRule.getConfigurationActivity(); @@ -1099,28 +1108,28 @@ public class ZenModeHelper { } /** - * Modifies {@link ZenPolicy} that is being stored as part of a new or updated ZenRule. - * Returns a policy based on {@code oldPolicy}, but with fields updated to match - * {@code newPolicy} where they differ, and updating the internal user-modified bitmask to - * track these changes, if applicable based on {@code origin}. + * Modifies the {@link ZenPolicy} associated to a new or updated ZenRule. + * + * <p>The new policy is {@code newPolicy}, while the user-modified bitmask is updated to reflect + * the changes being applied (if applicable, i.e. if the update is from the user). */ - @Nullable - private ZenPolicy updatePolicy(@Nullable ZenPolicy oldPolicy, @Nullable ZenPolicy newPolicy, - boolean updateBitmask) { - // If the update is to make the policy null, we don't need to update the bitmask, - // because it won't be stored anywhere anyway. + private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy, + boolean updateBitmask) { if (newPolicy == null) { - return null; + // TODO: b/319242206 - Treat as newPolicy == default policy and continue below. + zenRule.zenPolicy = null; + return; } // If oldPolicy is null, we compare against the default policy when determining which // fields in the bitmask should be marked as updated. - if (oldPolicy == null) { - oldPolicy = mDefaultConfig.toZenPolicy(); - } + ZenPolicy oldPolicy = + zenRule.zenPolicy != null ? zenRule.zenPolicy : mDefaultConfig.toZenPolicy(); + + zenRule.zenPolicy = newPolicy; - int userModifiedFields = oldPolicy.getUserModifiedFields(); if (updateBitmask) { + int userModifiedFields = zenRule.zenPolicyUserModifiedFields; if (oldPolicy.getPriorityMessageSenders() != newPolicy.getPriorityMessageSenders()) { userModifiedFields |= ZenPolicy.FIELD_MESSAGES; } @@ -1178,66 +1187,47 @@ public class ZenModeHelper { != newPolicy.getVisualEffectNotificationList()) { userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_NOTIFICATION_LIST; } + zenRule.zenPolicyUserModifiedFields = userModifiedFields; } - - // After all bitmask changes have been made, sets the bitmask. - return new ZenPolicy.Builder(newPolicy).setUserModifiedFields(userModifiedFields).build(); } /** - * Modifies {@link ZenDeviceEffects} that are being stored as part of a new or updated ZenRule. - * Returns a {@link ZenDeviceEffects} based on {@code oldEffects}, but with fields updated to - * match {@code newEffects} where they differ, and updating the internal user-modified bitmask - * to track these changes, if applicable based on {@code origin}. - * <ul> - * <li> Apps cannot turn on hidden effects (those tagged as {@code @hide}) since they are - * intended for platform-specific rules (e.g. wearables). If it's a new rule, we blank them - * out; if it's an update, we preserve the previous values. - * </ul> + * Modifies the {@link ZenDeviceEffects} associated to a new or updated ZenRule. + * + * <p>The new value is {@code newEffects}, while the user-modified bitmask is updated to reflect + * the changes being applied (if applicable, i.e. if the update is from the user). + * + * <p>Apps cannot turn on hidden effects (those tagged as {@code @hide}), so those fields are + * treated especially: for a new rule, they are blanked out; for an updated rule, previous + * values are preserved. */ - @Nullable - private static ZenDeviceEffects updateZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects, - @Nullable ZenDeviceEffects newEffects, - boolean isFromApp, - boolean updateBitmask) { + private static void updateZenDeviceEffects(ZenRule zenRule, + @Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) { if (newEffects == null) { - return null; + zenRule.zenDeviceEffects = null; + return; } - // Since newEffects is not null, we want to adopt all the new provided device effects. - ZenDeviceEffects.Builder builder = new ZenDeviceEffects.Builder(newEffects); + ZenDeviceEffects oldEffects = zenRule.zenDeviceEffects != null + ? zenRule.zenDeviceEffects + : new ZenDeviceEffects.Builder().build(); if (isFromApp) { - if (oldEffects != null) { - // We can do this because we know we don't need to update the bitmask FROM_APP. - return builder - .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness()) - .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake()) - .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake()) - .setShouldDisableTouch(oldEffects.shouldDisableTouch()) - .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage()) - .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze()) - .build(); - } else { - return builder - .setShouldDisableAutoBrightness(false) - .setShouldDisableTapToWake(false) - .setShouldDisableTiltToWake(false) - .setShouldDisableTouch(false) - .setShouldMinimizeRadioUsage(false) - .setShouldMaximizeDoze(false) - .build(); - } + // Don't allow apps to toggle hidden effects. + newEffects = new ZenDeviceEffects.Builder(newEffects) + .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness()) + .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake()) + .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake()) + .setShouldDisableTouch(oldEffects.shouldDisableTouch()) + .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage()) + .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze()) + .build(); } - // If oldEffects is null, we compare against the default device effects object when - // determining which fields in the bitmask should be marked as updated. - if (oldEffects == null) { - oldEffects = new ZenDeviceEffects.Builder().build(); - } + zenRule.zenDeviceEffects = newEffects; - int userModifiedFields = oldEffects.getUserModifiedFields(); if (updateBitmask) { + int userModifiedFields = zenRule.zenDeviceEffectsUserModifiedFields; if (oldEffects.shouldDisplayGrayscale() != newEffects.shouldDisplayGrayscale()) { userModifiedFields |= ZenDeviceEffects.FIELD_GRAYSCALE; } @@ -1270,11 +1260,8 @@ public class ZenModeHelper { if (oldEffects.shouldMaximizeDoze() != newEffects.shouldMaximizeDoze()) { userModifiedFields |= ZenDeviceEffects.FIELD_MAXIMIZE_DOZE; } + zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields; } - - // Since newEffects is not null, we want to adopt all the new provided device effects. - // Set the usermodifiedFields value separately, to reflect the updated bitmask. - return builder.setUserModifiedFields(userModifiedFields).build(); } private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) { @@ -1293,7 +1280,6 @@ public class ZenModeHelper { .setOwner(rule.component) .setConfigurationActivity(rule.configurationActivity) .setTriggerDescription(rule.triggerDescription) - .setUserModifiedFields(rule.userModifiedFields) .build(); } else { azr = new AutomaticZenRule(rule.name, rule.component, @@ -2369,6 +2355,19 @@ public class ZenModeHelper { return null; } } + + /** Checks that the {@code origin} supplied to a ZenModeHelper "API" method makes sense. */ + private static void requirePublicOrigin(String method, @ConfigChangeOrigin int origin) { + if (!Flags.modesApi()) { + return; + } + checkArgument(origin == UPDATE_ORIGIN_APP || origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + || origin == UPDATE_ORIGIN_USER, + "Expected one of UPDATE_ORIGIN_APP, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, or " + + "UPDATE_ORIGIN_USER for %s, but received '%s'.", + method, origin); + } + private final class Metrics extends Callback { private static final String COUNTER_MODE_PREFIX = "dnd_mode_"; private static final String COUNTER_TYPE_PREFIX = "dnd_type_"; diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index e993d9e5b724..d644235b8714 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -33,6 +33,7 @@ import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaRequest; +import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -47,6 +48,7 @@ import android.graphics.drawable.Icon; import android.os.Binder; import android.os.PersistableBundle; import android.os.StrictMode; +import android.os.SystemClock; import android.text.format.Formatter; import android.util.ArrayMap; import android.util.ArraySet; @@ -192,6 +194,9 @@ class ShortcutPackage extends ShortcutPackageItem { private long mLastKnownForegroundElapsedTime; @GuardedBy("mLock") + private long mLastReportedTime; + + @GuardedBy("mLock") private boolean mIsAppSearchSchemaUpToDate; private ShortcutPackage(ShortcutUser shortcutUser, @@ -1673,6 +1678,26 @@ class ShortcutPackage extends ShortcutPackageItem { return condition[0]; } + void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal, + @NonNull final String shortcutId) { + synchronized (mLock) { + final long currentTS = SystemClock.elapsedRealtime(); + final ShortcutService s = mShortcutUser.mService; + if (currentTS - mLastReportedTime > s.mSaveDelayMillis) { + mLastReportedTime = currentTS; + } else { + return; + } + final long token = s.injectClearCallingIdentity(); + try { + usageStatsManagerInternal.reportShortcutUsage(getPackageName(), shortcutId, + getPackageUserId()); + } finally { + s.injectRestoreCallingIdentity(token); + } + } + } + public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) { pw.println(); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 96c205c00ea9..c23d2abf0853 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -371,7 +371,7 @@ public class ShortcutService extends IShortcutService.Stub { private CompressFormat mIconPersistFormat; private int mIconPersistQuality; - private int mSaveDelayMillis; + int mSaveDelayMillis; private final IPackageManager mIPackageManager; private final PackageManagerInternal mPackageManagerInternal; @@ -2291,7 +2291,7 @@ public class ShortcutService extends IShortcutService.Stub { packageShortcutsChanged(ps, changedShortcuts, removedShortcuts); - reportShortcutUsedInternal(packageName, shortcut.getId(), userId); + ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId()); verifyStates(); } @@ -2695,25 +2695,17 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d", shortcutId, packageName, userId)); } + final ShortcutPackage ps; synchronized (mLock) { throwIfUserLockedL(userId); - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); + ps = getPackageShortcutsForPublisherLocked(packageName, userId); if (ps.findShortcutById(shortcutId) == null) { Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s", packageName, shortcutId)); return; } } - reportShortcutUsedInternal(packageName, shortcutId, userId); - } - - private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) { - final long token = injectClearCallingIdentity(); - try { - mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId); - } finally { - injectRestoreCallingIdentity(token); - } + ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId); } @Override @@ -5202,13 +5194,11 @@ public class ShortcutService extends IShortcutService.Stub { } // Injection point. - @VisibleForTesting long injectClearCallingIdentity() { return Binder.clearCallingIdentity(); } // Injection point. - @VisibleForTesting void injectRestoreCallingIdentity(long token) { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index c1b74898e5ae..7b0a69bc6786 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -46,6 +46,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityManagerNative; +import android.app.ActivityOptions; import android.app.BroadcastOptions; import android.app.IActivityManager; import android.app.IStopUserCallback; @@ -587,7 +588,10 @@ public class UserManagerService extends IUserManager.Stub { public void onFinished(int id, Bundle extras) { mHandler.post(() -> { try { - mContext.startIntentSender(mTarget, null, 0, 0, 0); + ActivityOptions activityOptions = + ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + mContext.startIntentSender(mTarget, null, 0, 0, 0, activityOptions.toBundle()); } catch (IntentSender.SendIntentException e) { Slog.e(LOG_TAG, "Failed to start the target in the callback", e); } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index 60dc4ff224bc..d95b431752ec 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -348,6 +348,10 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { private void pinWebviewIfRequired(ApplicationInfo appInfo) { PinnerService pinnerService = LocalServices.getService(PinnerService.class); + if (pinnerService == null) { + // This happens in unit tests which do not have services. + return; + } int webviewPinQuota = pinnerService.getWebviewPinQuota(); if (webviewPinQuota <= 0) { return; diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 676203bc746a..2e0546eee8e7 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -778,17 +778,22 @@ class ActivityClientController extends IActivityClientController.Stub { try { synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); + if (r == null) { + return false; + } // Create a transition if the activity is playing in case the below activity didn't // commit invisible. That's because if any activity below this one has changed its // visibility while playing transition, there won't able to commit visibility until // the running transition finish. - final Transition transition = r != null - && r.mTransitionController.inPlayingTransition(r) + final Transition transition = r.mTransitionController.isShellTransitionsEnabled() && !r.mTransitionController.isCollecting() ? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null; - final boolean changed = r != null && r.setOccludesParent(true); + final boolean changed = r.setOccludesParent(true); if (transition != null) { if (changed) { + // Always set as scene transition because it expects to be a jump-cut. + transition.setOverrideAnimation(TransitionInfo.AnimationOptions + .makeSceneTransitionAnimOptions(), null, null); r.mTransitionController.requestStartTransition(transition, null /*startTask */, null /* remoteTransition */, null /* displayChange */); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 69fbe6ba3c29..9b1f9c8441ad 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3093,7 +3093,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean changed = occludesParent != mOccludesParent; mOccludesParent = occludesParent; setMainWindowOpaque(occludesParent); - mWmService.mWindowPlacerLocked.requestTraversal(); if (changed && task != null && !occludesParent) { getRootTask().convertActivityToTranslucent(this); diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index e7621ffe8e3c..182e1c133790 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY; import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; @@ -144,22 +145,20 @@ class ActivityStartInterceptor { } private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) { - Bundle bOptions = deferCrossProfileAppsAnimationIfNecessary(); + ActivityOptions activityOptions = deferCrossProfileAppsAnimationIfNecessary(); + activityOptions.setPendingIntentCreatorBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOWED); final TaskFragment taskFragment = getLaunchTaskFragment(); // If the original intent is going to be embedded, try to forward the embedding TaskFragment // and its task id to embed back the original intent. if (taskFragment != null) { - ActivityOptions activityOptions = bOptions != null - ? ActivityOptions.fromBundle(bOptions) - : ActivityOptions.makeBasic(); activityOptions.setLaunchTaskFragmentToken(taskFragment.getFragmentToken()); - bOptions = activityOptions.toBundle(); } final IIntentSender target = mService.getIntentSenderLocked( INTENT_SENDER_ACTIVITY, mCallingPackage, mCallingFeatureId, callingUid, mUserId, null /*token*/, null /*resultCode*/, 0 /*requestCode*/, new Intent[] { mIntent }, new String[] { mResolvedType }, - flags, bOptions); + flags, activityOptions.toBundle()); return new IntentSender(target); } @@ -272,12 +271,12 @@ class ActivityStartInterceptor { * * @return the activity option used to start the original intent. */ - private Bundle deferCrossProfileAppsAnimationIfNecessary() { + private ActivityOptions deferCrossProfileAppsAnimationIfNecessary() { if (hasCrossProfileAnimation()) { mActivityOptions = null; - return ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(); + return ActivityOptions.makeOpenCrossProfileAppsAnimation(); } - return null; + return ActivityOptions.makeBasic(); } private boolean interceptQuietProfileIfNeeded() { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f620a9743eb4..2accf9a2a43a 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2841,6 +2841,19 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } + /** Returns {@code true} if the display should use high performance hint for this transition. */ + boolean shouldUsePerfHint(@NonNull DisplayContent dc) { + if (mOverrideOptions != null + && mOverrideOptions.getType() == ActivityOptions.ANIM_SCENE_TRANSITION + && mType == TRANSIT_TO_BACK && mParticipants.size() == 1) { + // This should be from convertFromTranslucent that makes the occluded activity invisible + // without animation. So do not use perf hint (especially early-wakeup) that may disturb + // SurfaceFlinger scheduling around the last frame. + return false; + } + return mTargetDisplays.contains(dc); + } + /** * Returns {@code true} if the transition and the corresponding transaction should be applied * on display thread. Currently, this only checks for display rotation change because the order diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 708d63e27ec2..59e3350d5c13 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -1237,8 +1237,15 @@ class TransitionController { // enableHighPerfTransition(true) is also called in Transition#recordDisplay. for (int i = mAtm.mRootWindowContainer.getChildCount() - 1; i >= 0; i--) { final DisplayContent dc = mAtm.mRootWindowContainer.getChildAt(i); - if (isTransitionOnDisplay(dc)) { + if (mCollectingTransition != null && mCollectingTransition.shouldUsePerfHint(dc)) { dc.enableHighPerfTransition(true); + continue; + } + for (int j = mPlayingTransitions.size() - 1; j >= 0; j--) { + if (mPlayingTransitions.get(j).shouldUsePerfHint(dc)) { + dc.enableHighPerfTransition(true); + break; + } } } // Usually transitions put quite a load onto the system already (with all the things diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 022268df4a63..62d2d7ee848a 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -134,7 +134,9 @@ class AppIdPermissionPolicy : SchemePolicy() { ) { val changedPermissionNames = MutableIndexedSet<String>() packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName]!! + // The package may still be removed even if it was once notified as installed. + val packageState = newState.externalState.packageStates[packageName] + ?: return@forEachIndexed adoptPermissions(packageState, changedPermissionNames) addPermissionGroups(packageState) addPermissions(packageState, changedPermissionNames) @@ -147,12 +149,14 @@ class AppIdPermissionPolicy : SchemePolicy() { } packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName]!! + val packageState = newState.externalState.packageStates[packageName] + ?: return@forEachIndexed val installedPackageState = if (isSystemUpdated) packageState else null evaluateAllPermissionStatesForPackage(packageState, installedPackageState) } packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName]!! + val packageState = newState.externalState.packageStates[packageName] + ?: return@forEachIndexed newState.externalState.userIds.forEachIndexed { _, userId -> inheritImplicitPermissionStates(packageState.appId, userId) } @@ -1607,6 +1611,13 @@ class AppIdPermissionPolicy : SchemePolicy() { flagMask: Int, flagValues: Int ): Boolean { + if (userId !in newState.userStates) { + // Despite that we check UserManagerInternal.exists() in PermissionService, we may still + // sometimes get race conditions between that check and the actual mutateState() call. + // This should rarely happen but at least we should not crash. + Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId") + return false + } val oldFlags = newState.userStates[userId]!! .appIdPermissionFlags[appId] diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java index ff91d34470d4..92016dfc631b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java @@ -20,11 +20,10 @@ package com.android.server.display.mode; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.Mode.INVALID_MODE_ID; - import static com.android.server.display.mode.DisplayModeDirector.SYNCHRONIZED_REFRESH_RATE_TOLERANCE; import static com.android.server.display.mode.Vote.PRIORITY_LIMIT_MODE; -import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE; import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE; +import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE; import static com.android.server.display.mode.VotesStorage.GLOBAL_ID; import static com.google.common.truth.Truth.assertThat; @@ -43,6 +42,7 @@ import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfigInterface; +import android.test.mock.MockContentResolver; import android.view.Display; import android.view.DisplayInfo; @@ -51,21 +51,26 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.sensors.SensorManagerInternal; +import junitparams.JUnitParamsRunner; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import junitparams.JUnitParamsRunner; - - @SmallTest @RunWith(JUnitParamsRunner.class) public class DisplayObserverTest { + @Rule + public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + private static final int EXTERNAL_DISPLAY = 1; private static final int MAX_WIDTH = 1920; private static final int MAX_HEIGHT = 1080; @@ -120,6 +125,8 @@ public class DisplayObserverTest { mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResources = mock(Resources.class); when(mContext.getResources()).thenReturn(mResources); + MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext); + when(mContext.getContentResolver()).thenReturn(resolver); when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate)) .thenReturn(0); when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth)) diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 00450267ee79..0831086b28ca 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -29,8 +29,6 @@ android_test { "src/**/*.java", "src/**/*.kt", - "test-apps/JobTestApp/src/**/*.java", - "test-apps/SuspendTestApp/src/**/*.java", ], static_libs: [ @@ -124,7 +122,6 @@ android_test { }, data: [ - ":JobTestApp", ":SimpleServiceTestApp1", ":SimpleServiceTestApp2", ":SimpleServiceTestApp3", diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index b1d50399416a..27c522d68119 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -29,7 +29,6 @@ <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> <option name="test-file-name" value="FrameworksServicesTests.apk" /> - <option name="test-file-name" value="JobTestApp.apk" /> <option name="test-file-name" value="SuspendTestApp.apk" /> <option name="test-file-name" value="SimpleServiceTestApp1.apk" /> <option name="test-file-name" value="SimpleServiceTestApp2.apk" /> diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java deleted file mode 100644 index e871fc567107..000000000000 --- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.job; - -import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STARTED; -import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STOPPED; -import static com.android.servicestests.apps.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.app.IActivityManager; -import android.app.job.JobParameters; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.IDeviceIdleController; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.UserHandle; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.FlakyTest; -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.servicestests.apps.jobtestapp.TestJobActivity; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests that background restrictions on jobs work as expected. - * This test requires test-apps/JobTestApp to be installed on the device. - * To run this test from root of checkout: - * <pre> - * mmm -j32 frameworks/base/services/tests/servicestests/ - * adb install -r $OUT/data/app/JobTestApp/JobTestApp.apk - * adb install -r $OUT/data/app/FrameworksServicesTests/FrameworksServicesTests.apk - * adb shell am instrument -e class 'com.android.server.job.BackgroundRestrictionsTest' -w \ - * com.android.frameworks.servicestests - * </pre> - */ -@RunWith(AndroidJUnit4.class) -@LargeTest -public class BackgroundRestrictionsTest { - private static final String TAG = BackgroundRestrictionsTest.class.getSimpleName(); - private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp"; - private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity"; - private static final long POLL_INTERVAL = 500; - private static final long DEFAULT_WAIT_TIMEOUT = 10_000; - - private Context mContext; - private AppOpsManager mAppOpsManager; - private IDeviceIdleController mDeviceIdleController; - private IActivityManager mIActivityManager; - private volatile int mTestJobId = -1; - private int mTestPackageUid; - /* accesses must be synchronized on itself */ - private final TestJobStatus mTestJobStatus = new TestJobStatus(); - private final BroadcastReceiver mJobStateChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY); - Log.d(TAG, "Received action " + intent.getAction()); - synchronized (mTestJobStatus) { - switch (intent.getAction()) { - case ACTION_JOB_STARTED: - mTestJobStatus.running = true; - mTestJobStatus.jobId = params.getJobId(); - mTestJobStatus.stopReason = JobParameters.STOP_REASON_UNDEFINED; - break; - case ACTION_JOB_STOPPED: - mTestJobStatus.running = false; - mTestJobStatus.jobId = params.getJobId(); - mTestJobStatus.stopReason = params.getStopReason(); - break; - } - } - } - }; - - @Before - public void setUp() throws Exception { - mContext = InstrumentationRegistry.getTargetContext(); - mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); - mDeviceIdleController = IDeviceIdleController.Stub.asInterface( - ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); - mIActivityManager = ActivityManager.getService(); - mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0); - mTestJobStatus.reset(); - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ACTION_JOB_STARTED); - intentFilter.addAction(ACTION_JOB_STOPPED); - mContext.registerReceiver(mJobStateChangeReceiver, intentFilter, - Context.RECEIVER_EXPORTED_UNAUDITED); - setAppOpsModeAllowed(true); - setPowerExemption(false); - } - - private void scheduleTestJob() { - mTestJobId = (int) (SystemClock.uptimeMillis() / 1000); - final Intent scheduleJobIntent = new Intent(TestJobActivity.ACTION_START_JOB); - scheduleJobIntent.putExtra(TestJobActivity.EXTRA_JOB_ID_KEY, mTestJobId); - scheduleJobIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY)); - mContext.startActivity(scheduleJobIntent); - } - - private void scheduleAndAssertJobStarted() throws Exception { - scheduleTestJob(); - Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY); - assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - } - - @FlakyTest - @Test - public void testPowerExemption() throws Exception { - scheduleAndAssertJobStarted(); - setAppOpsModeAllowed(false); - mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT); - assertTrue("Job did not stop after putting app under bg-restriction", - awaitJobStop(DEFAULT_WAIT_TIMEOUT, - JobParameters.STOP_REASON_BACKGROUND_RESTRICTION)); - - setPowerExemption(true); - scheduleTestJob(); - Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY); - assertTrue("Job did not start when the app was in the power exemption list", - awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - - setPowerExemption(false); - assertTrue("Job did not stop after removing from the power exemption list", - awaitJobStop(DEFAULT_WAIT_TIMEOUT, - JobParameters.STOP_REASON_BACKGROUND_RESTRICTION)); - - scheduleTestJob(); - Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY); - assertFalse("Job started under bg-restrictions", awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - setPowerExemption(true); - assertTrue("Job did not start when the app was in the power exemption list", - awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - } - - @After - public void tearDown() throws Exception { - final Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS); - cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY)); - cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(cancelJobsIntent); - mContext.unregisterReceiver(mJobStateChangeReceiver); - Thread.sleep(500); // To avoid race with register in the next setUp - setAppOpsModeAllowed(true); - setPowerExemption(false); - } - - private void setPowerExemption(boolean exempt) throws RemoteException { - if (exempt) { - mDeviceIdleController.addPowerSaveWhitelistApp(TEST_APP_PACKAGE); - } else { - mDeviceIdleController.removePowerSaveWhitelistApp(TEST_APP_PACKAGE); - } - } - - private void setAppOpsModeAllowed(boolean allow) { - mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mTestPackageUid, - TEST_APP_PACKAGE, allow ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); - } - - private boolean awaitJobStart(long timeout) throws InterruptedException { - return waitUntilTrue(timeout, () -> { - synchronized (mTestJobStatus) { - return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running; - } - }); - } - - private boolean awaitJobStop(long timeout, @JobParameters.StopReason int expectedStopReason) - throws InterruptedException { - return waitUntilTrue(timeout, () -> { - synchronized (mTestJobStatus) { - return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running - && (expectedStopReason == JobParameters.STOP_REASON_UNDEFINED - || mTestJobStatus.stopReason == expectedStopReason); - } - }); - } - - private boolean waitUntilTrue(long timeout, Condition condition) throws InterruptedException { - final long deadLine = SystemClock.uptimeMillis() + timeout; - do { - Thread.sleep(POLL_INTERVAL); - } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine); - return condition.isTrue(); - } - - private static final class TestJobStatus { - int jobId; - int stopReason; - boolean running; - - private void reset() { - running = false; - stopReason = jobId = 0; - } - } - - private interface Condition { - boolean isTrue(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 0f5fb91a140f..d50affba1ea1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -406,8 +406,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { public void testPushDynamicShortcut() { // Change the max number of shortcuts. - mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5"); - + mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5," + + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); setCaller(CALLING_PACKAGE_1, USER_0); final ShortcutInfo s1 = makeShortcut("s1"); @@ -545,6 +545,57 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_0)); } + public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled() + throws InterruptedException { + mService.updateConfigurationLocked( + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500"); + + // Verify calls to UsageStatsManagerInternal#reportShortcutUsage are throttled. + setCaller(CALLING_PACKAGE_1, USER_0); + { + final ShortcutInfo si = makeShortcut("s0"); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_1), eq("s0"), eq(USER_0)); + Mockito.reset(mMockUsageStatsManagerInternal); + for (int i = 2; i <= 10; i++) { + final ShortcutInfo si = makeShortcut("s" + i); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( + any(), any(), anyInt()); + + // Verify pkg2 isn't blocked by pkg1, but consecutive calls from pkg2 are throttled as well. + setCaller(CALLING_PACKAGE_2, USER_0); + { + final ShortcutInfo si = makeShortcut("s1"); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_2), eq("s1"), eq(USER_0)); + Mockito.reset(mMockUsageStatsManagerInternal); + for (int i = 2; i <= 10; i++) { + final ShortcutInfo si = makeShortcut("s" + i); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( + any(), any(), anyInt()); + + Mockito.reset(mMockUsageStatsManagerInternal); + // Let time passes which resets the throttle + Thread.sleep(505); + // Verify UsageStatsManagerInternal#reportShortcutUsed can be called again + setCaller(CALLING_PACKAGE_1, USER_0); + mManager.pushDynamicShortcut(makeShortcut("s10")); + setCaller(CALLING_PACKAGE_2, USER_0); + mManager.pushDynamicShortcut(makeShortcut("s10")); + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_1), any(), eq(USER_0)); + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_2), any(), eq(USER_0)); + } + public void testUnlimitedCalls() { setCaller(CALLING_PACKAGE_1, USER_0); diff --git a/services/tests/servicestests/test-apps/JobTestApp/Android.bp b/services/tests/servicestests/test-apps/JobTestApp/Android.bp deleted file mode 100644 index 6458bcd4fc8f..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/Android.bp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2017 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "JobTestApp", - - sdk_version: "current", - - srcs: ["**/*.java"], - - dex_preopt: { - enabled: false, - }, - optimize: { - enabled: false, - }, -} diff --git a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml deleted file mode 100644 index ac35805e8af6..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.servicestests.apps.jobtestapp"> - - <application> - <service android:name=".TestJobService" - android:permission="android.permission.BIND_JOB_SERVICE" /> - <activity android:name=".TestJobActivity" - android:exported="true" /> - </application> - -</manifest>
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/JobTestApp/OWNERS b/services/tests/servicestests/test-apps/JobTestApp/OWNERS deleted file mode 100644 index 6f207fb1a00e..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /apex/jobscheduler/OWNERS diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java deleted file mode 100644 index 99eb196e3298..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.servicestests.apps.jobtestapp; - -import android.app.Activity; -import android.app.job.JobInfo; -import android.app.job.JobScheduler; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; - -public class TestJobActivity extends Activity { - private static final String TAG = TestJobActivity.class.getSimpleName(); - private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp"; - - public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID"; - public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB"; - public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS"; - public static final int JOB_INITIAL_BACKOFF = 10_000; - public static final int JOB_MINIMUM_LATENCY = 5_000; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - ComponentName jobServiceComponent = new ComponentName(this, TestJobService.class); - JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); - final Intent intent = getIntent(); - switch (intent.getAction()) { - case ACTION_CANCEL_JOBS: - jobScheduler.cancelAll(); - Log.d(TAG, "Cancelled all jobs for " + getPackageName()); - break; - case ACTION_START_JOB: - final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode()); - JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent) - .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR) - .setMinimumLatency(JOB_MINIMUM_LATENCY) - .setOverrideDeadline(JOB_MINIMUM_LATENCY); - final int result = jobScheduler.schedule(jobBuilder.build()); - if (result != JobScheduler.RESULT_SUCCESS) { - Log.e(TAG, "Could not schedule job " + jobId); - } else { - Log.d(TAG, "Successfully scheduled job with id " + jobId); - } - break; - default: - Log.e(TAG, "Unknown action " + intent.getAction()); - } - finish(); - } -}
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java deleted file mode 100644 index b8585f26c185..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.servicestests.apps.jobtestapp; - -import android.annotation.TargetApi; -import android.app.job.JobParameters; -import android.app.job.JobService; -import android.content.Intent; -import android.util.Log; - -@TargetApi(24) -public class TestJobService extends JobService { - private static final String TAG = TestJobService.class.getSimpleName(); - private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp"; - public static final String ACTION_JOB_STARTED = PACKAGE_NAME + ".action.JOB_STARTED"; - public static final String ACTION_JOB_STOPPED = PACKAGE_NAME + ".action.JOB_STOPPED"; - public static final String JOB_PARAMS_EXTRA_KEY = PACKAGE_NAME + ".extra.JOB_PARAMETERS"; - - @Override - public boolean onStartJob(JobParameters params) { - Log.i(TAG, "Test job executing: " + params.getJobId()); - Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED); - reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - sendBroadcast(reportJobStartIntent); - return true; - } - - @Override - public boolean onStopJob(JobParameters params) { - Log.i(TAG, "Test job stopped executing: " + params.getJobId()); - Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED); - reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - sendBroadcast(reportJobStopIntent); - // Deadline constraint is dropped on reschedule, so it's more reliable to use a new job. - return false; - } -} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index c1f35ccb69e0..723ac15fb50f 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -13792,8 +13792,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); mBinderService.setNotificationPolicy("package", policy, false); - verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy), - eq(ZenModeConfig.UPDATE_ORIGIN_APP)); + verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy)); } @Test @@ -13859,7 +13858,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt()); } else { verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(), - eq(policy), anyInt()); + eq(policy)); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java index 3d8ec2ec9277..f604f1e77cf4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java @@ -52,7 +52,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase { .setShouldMaximizeDoze(true) .setShouldUseNightMode(false) .setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true) - .setUserModifiedFields(8) .build(); assertThat(deviceEffects.shouldDimWallpaper()).isTrue(); @@ -65,7 +64,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase { assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse(); assertThat(deviceEffects.shouldUseNightMode()).isFalse(); assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue(); - assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(8); } @Test @@ -97,7 +95,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase { .setShouldMinimizeRadioUsage(true) .setShouldUseNightMode(true) .setShouldSuppressAmbientDisplay(true) - .setUserModifiedFields(6) .build(); Parcel parcel = Parcel.obtain(); @@ -116,7 +113,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase { assertThat(copy.shouldUseNightMode()).isTrue(); assertThat(copy.shouldSuppressAmbientDisplay()).isTrue(); assertThat(copy.shouldDisplayGrayscale()).isFalse(); - assertThat(copy.getUserModifiedFields()).isEqualTo(6); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index db92719ecfd6..e523e79f6370 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -285,7 +285,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders()); assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders()); assertEquals(expected.getPriorityChannels(), actual.getPriorityChannels()); - assertEquals(expected.getUserModifiedFields(), actual.getUserModifiedFields()); } @Test @@ -342,45 +341,32 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.zenPolicy = null; rule.zenDeviceEffects = null; - assertThat(rule.canBeUpdatedByApp()).isTrue(); rule.userModifiedFields = 1; + assertThat(rule.canBeUpdatedByApp()).isFalse(); } @Test public void testCanBeUpdatedByApp_policyModified() throws Exception { - ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0); - ZenPolicy policy = policyBuilder.build(); - ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); - rule.zenPolicy = policy; - - assertThat(rule.userModifiedFields).isEqualTo(0); + rule.zenPolicy = new ZenPolicy(); assertThat(rule.canBeUpdatedByApp()).isTrue(); - policy = policyBuilder.setUserModifiedFields(1).build(); - assertThat(policy.getUserModifiedFields()).isEqualTo(1); - rule.zenPolicy = policy; + rule.zenPolicyUserModifiedFields = 1; + assertThat(rule.canBeUpdatedByApp()).isFalse(); } @Test public void testCanBeUpdatedByApp_deviceEffectsModified() throws Exception { - ZenDeviceEffects.Builder deviceEffectsBuilder = - new ZenDeviceEffects.Builder().setUserModifiedFields(0); - ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build(); - ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); - rule.zenDeviceEffects = deviceEffects; - - assertThat(rule.userModifiedFields).isEqualTo(0); + rule.zenDeviceEffects = new ZenDeviceEffects.Builder().build(); assertThat(rule.canBeUpdatedByApp()).isTrue(); - deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build(); - assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1); - rule.zenDeviceEffects = deviceEffects; + rule.zenDeviceEffectsUserModifiedFields = 1; + assertThat(rule.canBeUpdatedByApp()).isFalse(); } @@ -406,6 +392,8 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.allowManualInvocation = ALLOW_MANUAL; rule.type = TYPE; rule.userModifiedFields = 16; + rule.zenPolicyUserModifiedFields = 5; + rule.zenDeviceEffectsUserModifiedFields = 2; rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); @@ -432,6 +420,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.iconResName, parceled.iconResName); assertEquals(rule.type, parceled.type); assertEquals(rule.userModifiedFields, parceled.userModifiedFields); + assertEquals(rule.zenPolicyUserModifiedFields, parceled.zenPolicyUserModifiedFields); + assertEquals(rule.zenDeviceEffectsUserModifiedFields, + parceled.zenDeviceEffectsUserModifiedFields); assertEquals(rule.triggerDescription, parceled.triggerDescription); assertEquals(rule.zenPolicy, parceled.zenPolicy); assertEquals(rule.deletionInstant, parceled.deletionInstant); @@ -511,6 +502,8 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.allowManualInvocation = ALLOW_MANUAL; rule.type = TYPE; rule.userModifiedFields = 4; + rule.zenPolicyUserModifiedFields = 5; + rule.zenDeviceEffectsUserModifiedFields = 2; rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); @@ -541,6 +534,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.allowManualInvocation, fromXml.allowManualInvocation); assertEquals(rule.type, fromXml.type); assertEquals(rule.userModifiedFields, fromXml.userModifiedFields); + assertEquals(rule.zenPolicyUserModifiedFields, fromXml.zenPolicyUserModifiedFields); + assertEquals(rule.zenDeviceEffectsUserModifiedFields, + fromXml.zenDeviceEffectsUserModifiedFields); assertEquals(rule.triggerDescription, fromXml.triggerDescription); assertEquals(rule.iconResName, fromXml.iconResName); assertEquals(rule.deletionInstant, fromXml.deletionInstant); @@ -697,7 +693,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { .allowPriorityChannels(false) .hideAllVisualEffects() .showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true) - .setUserModifiedFields(4) .build(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -732,7 +727,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(policy.getVisualEffectAmbient(), fromXml.getVisualEffectAmbient()); assertEquals(policy.getVisualEffectNotificationList(), fromXml.getVisualEffectNotificationList()); - assertEquals(policy.getUserModifiedFields(), fromXml.getUserModifiedFields()); } private ZenModeConfig getMutedRingerConfig() { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java index 9d7cf53e62db..2e64645ecade 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -73,13 +73,15 @@ public class ZenModeDiffTest extends UiServiceTestCase { : Set.of("version", "manualRule", "automaticRules"); // Differences for flagged fields are only generated if the flag is enabled. - // "Metadata" fields (userModifiedFields, deletionInstant) are not compared. + // "Metadata" fields (userModifiedFields & co, deletionInstant) are not compared. private static final Set<String> ZEN_RULE_EXEMPT_FIELDS = android.app.Flags.modesApi() - ? Set.of("userModifiedFields", "deletionInstant") + ? Set.of("userModifiedFields", "zenPolicyUserModifiedFields", + "zenDeviceEffectsUserModifiedFields", "deletionInstant") : Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION, RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL, RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, "userModifiedFields", + "zenPolicyUserModifiedFields", "zenDeviceEffectsUserModifiedFields", "deletionInstant"); // allowPriorityChannels is flagged by android.app.modes_api diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 78fe41f31849..edc876aab388 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -46,6 +46,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; +import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS; import static android.provider.Settings.Global.ZEN_MODE_OFF; import static android.service.notification.Condition.SOURCE_SCHEDULE; import static android.service.notification.Condition.SOURCE_USER_ACTION; @@ -67,6 +68,7 @@ import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW; import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW; import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -295,6 +297,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL); when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt())) .thenReturn(appInfoSpy); + when(mPackageManager.getApplicationInfo(eq(mContext.getPackageName()), anyInt())) + .thenReturn(appInfoSpy); mZenModeHelper.mPm = mPackageManager; mZenModeEventLogger.reset(); @@ -2236,12 +2240,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); - // savedRule.getDeviceEffects() is equal to zde, except for the userModifiedFields. - // So we clear before comparing. - ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects()) - .setUserModifiedFields(0).build(); - - assertThat(savedEffects).isEqualTo(zde); + assertThat(savedRule.getDeviceEffects()).isEqualTo(zde); } @Test @@ -2331,12 +2330,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); - // savedRule.getDeviceEffects() is equal to updateFromUser, except for the - // userModifiedFields, so we clear before comparing. - ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects()) - .setUserModifiedFields(0).build(); - - assertThat(savedEffects).isEqualTo(updateFromUser); + assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser); } @Test @@ -3411,7 +3405,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { rule.allowManualInvocation = ALLOW_MANUAL; rule.type = TYPE; - rule.userModifiedFields = AutomaticZenRule.FIELD_NAME; rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; @@ -3426,7 +3419,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(POLICY, actual.getZenPolicy()); assertEquals(CONFIG_ACTIVITY, actual.getConfigurationActivity()); assertEquals(TYPE, actual.getType()); - assertEquals(AutomaticZenRule.FIELD_NAME, actual.getUserModifiedFields()); assertEquals(ALLOW_MANUAL, actual.isManualInvocationAllowed()); assertEquals(CREATION_TIME, actual.getCreationTime()); assertEquals(OWNER.getPackageName(), actual.getPackageName()); @@ -3453,29 +3445,31 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setManualInvocationAllowed(ALLOW_MANUAL) .build(); - ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); - - mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true); - - assertEquals(NAME, rule.name); - assertEquals(OWNER, rule.component); - assertEquals(CONDITION_ID, rule.conditionId); - assertEquals(INTERRUPTION_FILTER_ZR, rule.zenMode); - assertEquals(ENABLED, rule.enabled); - assertEquals(POLICY, rule.zenPolicy); - assertEquals(CONFIG_ACTIVITY, rule.configurationActivity); - assertEquals(TYPE, rule.type); - assertEquals(ALLOW_MANUAL, rule.allowManualInvocation); - assertEquals(OWNER.getPackageName(), rule.getPkg()); - assertEquals(ICON_RES_NAME, rule.iconResName); + String ruleId = mZenModeHelper.addAutomaticZenRule(OWNER.getPackageName(), azr, + UPDATE_ORIGIN_APP, "add", CUSTOM_PKG_UID); + + ZenModeConfig.ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + + assertThat(storedRule).isNotNull(); + assertEquals(NAME, storedRule.name); + assertEquals(OWNER, storedRule.component); + assertEquals(CONDITION_ID, storedRule.conditionId); + assertEquals(INTERRUPTION_FILTER_ZR, storedRule.zenMode); + assertEquals(ENABLED, storedRule.enabled); + assertEquals(POLICY, storedRule.zenPolicy); + assertEquals(CONFIG_ACTIVITY, storedRule.configurationActivity); + assertEquals(TYPE, storedRule.type); + assertEquals(ALLOW_MANUAL, storedRule.allowManualInvocation); + assertEquals(OWNER.getPackageName(), storedRule.getPkg()); + assertEquals(ICON_RES_NAME, storedRule.iconResName); // Because the origin of the update is the app, we don't expect the bitmask to change. - assertEquals(0, rule.userModifiedFields); - assertEquals(TRIGGER_DESC, rule.triggerDescription); + assertEquals(0, storedRule.userModifiedFields); + assertEquals(TRIGGER_DESC, storedRule.triggerDescription); } @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_updatesNameUnlessUserModified() { + public void updateAutomaticZenRule_fromApp_updatesNameUnlessUserModified() { // Add a starting rule with the name OriginalName. AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID) .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) @@ -3492,7 +3486,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { Process.SYSTEM_UID); rule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(rule.getName()).isEqualTo("NewName"); - assertThat(rule.canUpdate()).isTrue(); // The user modifies some other field in the rule, which makes the rule as a whole not // app modifiable. @@ -3501,10 +3494,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID); - rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.getUserModifiedFields()) - .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER); - assertThat(rule.canUpdate()).isFalse(); // ...but the app can still modify the name, because the name itself hasn't been modified // by the user. @@ -3524,8 +3513,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { Process.SYSTEM_UID); rule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(rule.getName()).isEqualTo("UserProvidedName"); - assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME - | AutomaticZenRule.FIELD_INTERRUPTION_FILTER); // The app is no longer able to modify the name. azrUpdate = new AutomaticZenRule.Builder(rule) @@ -3539,7 +3526,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_updatesBitmaskAndValueForUserOrigin() { + public void updateAutomaticZenRule_fromUser_updatesBitmaskAndValue() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setZenPolicy(new ZenPolicy.Builder().build()) @@ -3571,84 +3558,21 @@ public class ZenModeHelperTest extends UiServiceTestCase { // UPDATE_ORIGIN_USER should change the bitmask and change the values. assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY); - assertThat(rule.getUserModifiedFields()) - .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER); - assertThat(rule.getZenPolicy().getUserModifiedFields()) - .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS); assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW); - assertThat(rule.getDeviceEffects().getUserModifiedFields()) - .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE); assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); - } - @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_doesNotUpdateValuesForInitUserOrigin() { - // Adds a starting rule with empty zen policies and device effects - AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) - .setInterruptionFilter(INTERRUPTION_FILTER_ALL) // Already the default, no change - .setZenPolicy(new ZenPolicy.Builder() - .allowReminders(false) - .build()) - .setDeviceEffects(new ZenDeviceEffects.Builder() - .setShouldDisplayGrayscale(false) - .build()) - .build(); - // Adds the rule using the user, to set user-modified bits. - String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID); - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.canUpdate()).isFalse(); - assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME); - - ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy()) - .allowReminders(true) - .build(); - ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder(rule.getDeviceEffects()) - .setShouldDisplayGrayscale(true) - .build(); - AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule) - .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) - .setZenPolicy(policy) - .setDeviceEffects(deviceEffects) - .build(); - - // Attempts to update the rule with the AZR from origin init user. - mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason", - Process.SYSTEM_UID); - AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId); - - // UPDATE_ORIGIN_INIT_USER does not change the bitmask or values if rule is user modified. - // TODO: b/318506692 - Remove once we check that INIT origins can't call add/updateAZR. - assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields()); - assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL); - assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo( - rule.getZenPolicy().getUserModifiedFields()); - assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders()).isEqualTo( - ZenPolicy.STATE_DISALLOW); - assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo( - rule.getDeviceEffects().getUserModifiedFields()); - assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isFalse(); - - // Creates a new rule with the AZR from origin init user. - String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason", Process.SYSTEM_UID); - AutomaticZenRule newRule = mZenModeHelper.getAutomaticZenRule(newRuleId); - - // UPDATE_ORIGIN_INIT_USER does change the values if the rule is new, - // but does not update the bitmask. - assertThat(newRule.getUserModifiedFields()).isEqualTo(0); - assertThat(newRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY); - assertThat(newRule.getZenPolicy().getUserModifiedFields()).isEqualTo(0); - assertThat(newRule.getZenPolicy().getPriorityCategoryReminders()) - .isEqualTo(ZenPolicy.STATE_ALLOW); - assertThat(newRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0); - assertThat(newRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.userModifiedFields) + .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER); + assertThat(storedRule.zenPolicyUserModifiedFields) + .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS); + assertThat(storedRule.zenDeviceEffectsUserModifiedFields) + .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE); } @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_updatesValuesForSystemUiOrigin() { + public void updateAutomaticZenRule_fromSystemUi_updatesValues() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setInterruptionFilter(INTERRUPTION_FILTER_ALL) @@ -3684,17 +3608,19 @@ public class ZenModeHelperTest extends UiServiceTestCase { rule = mZenModeHelper.getAutomaticZenRule(ruleId); // UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask. - assertThat(rule.getUserModifiedFields()).isEqualTo(0); - assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(0); assertThat(rule.getZenPolicy().getPriorityCategoryReminders()) .isEqualTo(ZenPolicy.STATE_ALLOW); - assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0); assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); + + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.userModifiedFields).isEqualTo(0); + assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0); + assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0); } @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_updatesValuesIfRuleNotUserModified() { + public void updateAutomaticZenRule_fromApp_updatesValuesIfRuleNotUserModified() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setInterruptionFilter(INTERRUPTION_FILTER_ALL) @@ -3709,7 +3635,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.canUpdate()).isTrue(); ZenPolicy policy = new ZenPolicy.Builder() .allowReminders(true) @@ -3717,57 +3642,59 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder() .setShouldDisplayGrayscale(true) .build(); - AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule) + AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule) .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) .setZenPolicy(policy) .setDeviceEffects(deviceEffects) .build(); - // Since the rule is not already user modified, UPDATE_ORIGIN_UNKNOWN can modify the rule. + // Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule. // The bitmask is not modified. - mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_UNKNOWN, "reason", + mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); - AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields()); - assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS); - assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo( - rule.getZenPolicy().getUserModifiedFields()); - assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders()) + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.userModifiedFields).isEqualTo(0); + + assertThat(storedRule.zenMode).isEqualTo(ZEN_MODE_ALARMS); + assertThat(storedRule.zenPolicy.getPriorityCategoryReminders()) .isEqualTo(ZenPolicy.STATE_ALLOW); - assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo( - rule.getDeviceEffects().getUserModifiedFields()); - assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); + assertThat(storedRule.zenDeviceEffects.shouldDisplayGrayscale()).isTrue(); + assertThat(storedRule.userModifiedFields).isEqualTo(0); + assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0); + assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0); // Creates another rule, this time from user. This will have user modified bits set. String ruleIdUser = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID); - AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser); - assertThat(ruleUser.canUpdate()).isFalse(); + storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser); + int ruleModifiedFields = storedRule.userModifiedFields; + int rulePolicyModifiedFields = storedRule.zenPolicyUserModifiedFields; + int ruleDeviceEffectsModifiedFields = storedRule.zenDeviceEffectsUserModifiedFields; - // Zen rule update coming from unknown origin. This cannot fully update the rule, because + // Zen rule update coming from the app again. This cannot fully update the rule, because // the rule is already considered user modified. - mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_UNKNOWN, + mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); - ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser); + AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser); - // UPDATE_ORIGIN_UNKNOWN can only change the value if the rule is not already user modified, + // The app can only change the value if the rule is not already user modified, // so the rule is not changed, and neither is the bitmask. assertThat(ruleUser.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL); - // Interruption Filter All is the default value, so it's not included as a modified field. - assertThat(ruleUser.getUserModifiedFields() | AutomaticZenRule.FIELD_NAME).isGreaterThan(0); - assertThat(ruleUser.getZenPolicy().getUserModifiedFields() - | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS).isGreaterThan(0); assertThat(ruleUser.getZenPolicy().getPriorityCategoryReminders()) .isEqualTo(ZenPolicy.STATE_DISALLOW); - assertThat(ruleUser.getDeviceEffects().getUserModifiedFields() - | ZenDeviceEffects.FIELD_GRAYSCALE).isGreaterThan(0); assertThat(ruleUser.getDeviceEffects().shouldDisplayGrayscale()).isFalse(); + + storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser); + assertThat(storedRule.userModifiedFields).isEqualTo(ruleModifiedFields); + assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(rulePolicyModifiedFields); + assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo( + ruleDeviceEffectsModifiedFields); } @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_updatesValuesIfRuleNew() { + public void addAutomaticZenRule_updatesValues() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) @@ -3778,21 +3705,22 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setShouldDisplayGrayscale(true) .build()) .build(); - // Adds the rule using origin unknown, to show that a new rule is always allowed. String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - azrBase, UPDATE_ORIGIN_UNKNOWN, "reason", Process.SYSTEM_UID); + azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); // The values are modified but the bitmask is not. - assertThat(rule.canUpdate()).isTrue(); assertThat(rule.getZenPolicy().getPriorityCategoryReminders()) .isEqualTo(ZenPolicy.STATE_ALLOW); assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); + + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.canBeUpdatedByApp()).isTrue(); } @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_nullDeviceEffectsUpdate() { + public void updateAutomaticZenRule_nullDeviceEffectsUpdate() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setDeviceEffects(new ZenDeviceEffects.Builder().build()) @@ -3807,9 +3735,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setDeviceEffects(null) .build(); - // Zen rule update coming from unknown origin, but since the rule isn't already + // Zen rule update coming from app, but since the rule isn't already // user modified, it can be updated. - mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason", + mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); rule = mZenModeHelper.getAutomaticZenRule(ruleId); @@ -3819,7 +3747,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_nullPolicyUpdate() { + public void updateAutomaticZenRule_nullPolicyUpdate() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setZenPolicy(new ZenPolicy.Builder().build()) @@ -3828,16 +3756,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.canUpdate()).isTrue(); AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase) // Set zen policy to null .setZenPolicy(null) .build(); - // Zen rule update coming from unknown origin, but since the rule isn't already + // Zen rule update coming from app, but since the rule isn't already // user modified, it can be updated. - mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason", + mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); rule = mZenModeHelper.getAutomaticZenRule(ruleId); @@ -3859,7 +3786,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.canUpdate()).isTrue(); // Create a fully populated ZenPolicy. ZenPolicy policy = new ZenPolicy.Builder() @@ -3894,8 +3820,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { // New ZenPolicy differs from the default config assertThat(rule.getZenPolicy()).isNotNull(); assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW); - assertThat(rule.canUpdate()).isFalse(); - assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo( + + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.canBeUpdatedByApp()).isFalse(); + assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo( ZenPolicy.FIELD_ALLOW_CHANNELS | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS | ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS @@ -3918,7 +3846,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.canUpdate()).isTrue(); ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder() .setShouldDisplayGrayscale(true) @@ -3935,8 +3862,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { // New ZenDeviceEffects is used; all fields considered set, since previously were null. assertThat(rule.getDeviceEffects()).isNotNull(); assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); - assertThat(rule.canUpdate()).isFalse(); - assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo( + + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.canBeUpdatedByApp()).isFalse(); + assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo( ZenDeviceEffects.FIELD_GRAYSCALE); } @@ -4339,7 +4268,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID); assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000); - assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).canUpdate()).isTrue(); // User customizes it. AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule) @@ -4371,9 +4299,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS); assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo( ZenPolicy.STATE_ALLOW); - assertThat(finalRule.getUserModifiedFields()).isEqualTo( + + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.userModifiedFields).isEqualTo( AutomaticZenRule.FIELD_INTERRUPTION_FILTER); - assertThat(finalRule.getZenPolicy().getUserModifiedFields()).isEqualTo( + assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo( ZenPolicy.FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS); // Also, we discarded the "deleted rule" since we already used it for restoration. @@ -4652,7 +4582,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZEN_MODE_IMPORTANT_INTERRUPTIONS); assertThat(mZenModeHelper.mConfig.automaticRules.values()) - .comparingElementsUsing(IGNORE_TIMESTAMPS) + .comparingElementsUsing(IGNORE_METADATA) .containsExactly( expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, true)); @@ -4672,12 +4602,75 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZEN_MODE_ALARMS); assertThat(mZenModeHelper.mConfig.automaticRules.values()) - .comparingElementsUsing(IGNORE_TIMESTAMPS) + .comparingElementsUsing(IGNORE_METADATA) .containsExactly( expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, null, true)); } @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void applyGlobalZenModeAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() { + mZenModeHelper.mConfig.automaticRules.clear(); + String pkg = mContext.getPackageName(); + + // From app, call "setInterruptionFilter" and create and implicit rule. + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID, + ZEN_MODE_IMPORTANT_INTERRUPTIONS); + String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet()); + assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode) + .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS); + + // From user, update that rule's interruption filter. + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); + AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) + .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason", + Process.SYSTEM_UID); + + // From app, call "setInterruptionFilter" again. + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID, + ZEN_MODE_NO_INTERRUPTIONS); + + // The app's update was ignored, and the user's update is still current, and the current + // mode is the one they chose. + assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode) + .isEqualTo(ZEN_MODE_ALARMS); + assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void applyGlobalZenModeAsImplicitZenRule_ruleCustomizedButNotFilter_updatesRule() { + mZenModeHelper.mConfig.automaticRules.clear(); + String pkg = mContext.getPackageName(); + + // From app, call "setInterruptionFilter" and create and implicit rule. + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID, + ZEN_MODE_IMPORTANT_INTERRUPTIONS); + String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet()); + assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode) + .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS); + + // From user, update something in that rule, but not the interruption filter. + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); + AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) + .setName("Renamed") + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason", + Process.SYSTEM_UID); + + // From app, call "setInterruptionFilter" again. + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID, + ZEN_MODE_NO_INTERRUPTIONS); + + // The app's update was accepted, and the current mode is the one that they wanted. + assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode) + .isEqualTo(ZEN_MODE_NO_INTERRUPTIONS); + assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_NO_INTERRUPTIONS); + } + + @Test public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mZenModeHelper.mConfig.automaticRules.clear(); @@ -4747,8 +4740,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED, Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT); - mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy, - UPDATE_ORIGIN_APP); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy); ZenPolicy expectedZenPolicy = new ZenPolicy.Builder() .disallowAllSounds() @@ -4758,7 +4750,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .allowPriorityChannels(true) .build(); assertThat(mZenModeHelper.mConfig.automaticRules.values()) - .comparingElementsUsing(IGNORE_TIMESTAMPS) + .comparingElementsUsing(IGNORE_METADATA) .containsExactly( expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS, expectedZenPolicy, /* conditionActive= */ null)); @@ -4773,14 +4765,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED, Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT); mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, - original, UPDATE_ORIGIN_APP); + original); // Change priorityCallSenders: contacts -> starred. Policy updated = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED, Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT); - mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated, - UPDATE_ORIGIN_APP); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated); ZenPolicy expectedZenPolicy = new ZenPolicy.Builder() .disallowAllSounds() @@ -4790,20 +4781,87 @@ public class ZenModeHelperTest extends UiServiceTestCase { .allowPriorityChannels(true) .build(); assertThat(mZenModeHelper.mConfig.automaticRules.values()) - .comparingElementsUsing(IGNORE_TIMESTAMPS) + .comparingElementsUsing(IGNORE_METADATA) .containsExactly( expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS, expectedZenPolicy, /* conditionActive= */ null)); } @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void applyGlobalPolicyAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() { + mZenModeHelper.mConfig.automaticRules.clear(); + String pkg = mContext.getPackageName(); + + // From app, call "setNotificationPolicy" and create and implicit rule. + Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy); + String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet()); + + // From user, update that rule's policy. + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); + ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds() + .allowAlarms(true).build(); + AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) + .setZenPolicy(userUpdateZenPolicy) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason", + Process.SYSTEM_UID); + + // From app, call "setNotificationPolicy" again. + Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy); + + // The app's update was ignored, and the user's update is still current. + assertThat(mZenModeHelper.mConfig.automaticRules.values()) + .comparingElementsUsing(IGNORE_METADATA) + .containsExactly( + expectedImplicitRule(pkg, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + userUpdateZenPolicy, + /* conditionActive= */ null)); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void applyGlobalPolicyAsImplicitZenRule_ruleCustomizedButNotZenPolicy_updatesRule() { + mZenModeHelper.mConfig.automaticRules.clear(); + String pkg = mContext.getPackageName(); + + // From app, call "setNotificationPolicy" and create and implicit rule. + Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy); + String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet()); + + // From user, update something in that rule, but not the ZenPolicy. + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); + AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) + .setName("Rule renamed, not touching policy") + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason", + Process.SYSTEM_UID); + + // From app, call "setNotificationPolicy" again. + Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy); + + // The app's update was applied. + ZenPolicy appsSecondZenPolicy = new ZenPolicy.Builder() + .disallowAllSounds() + .allowSystem(true) + .allowPriorityChannels(true) + .build(); + assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenPolicy) + .isEqualTo(appsSecondZenPolicy); + } + + @Test public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() { mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API); mZenModeHelper.mConfig.automaticRules.clear(); withoutWtfCrash( () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, - CUSTOM_PKG_UID, new Policy(0, 0, 0), UPDATE_ORIGIN_APP)); + CUSTOM_PKG_UID, new Policy(0, 0, 0))); assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty(); } @@ -4816,7 +4874,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { Policy.getAllSuppressedVisualEffects(), STATE_FALSE, CONVERSATION_SENDERS_IMPORTANT); mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, - writtenPolicy, UPDATE_ORIGIN_APP); + writtenPolicy); Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule( CUSTOM_PKG_NAME); @@ -4856,7 +4914,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(readPolicy.allowConversations()).isFalse(); } - private static final Correspondence<ZenRule, ZenRule> IGNORE_TIMESTAMPS = + private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA = Correspondence.transforming(zr -> { Parcel p = Parcel.obtain(); try { @@ -4864,12 +4922,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { p.setDataPosition(0); ZenRule copy = new ZenRule(p); copy.creationTime = 0; + copy.userModifiedFields = 0; + copy.zenPolicyUserModifiedFields = 0; + copy.zenDeviceEffectsUserModifiedFields = 0; return copy; } finally { p.recycle(); } }, - "Ignoring timestamps"); + "Ignoring timestamp and userModifiedFields"); private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy, @Nullable Boolean conditionActive) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java index 7941eb4d2090..4ed55df7775c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java @@ -645,38 +645,12 @@ public class ZenPolicyTest extends UiServiceTestCase { } @Test - public void testFromParcel() { - ZenPolicy.Builder builder = new ZenPolicy.Builder(); - builder.setUserModifiedFields(10); - - ZenPolicy policy = builder.build(); - assertThat(policy.getUserModifiedFields()).isEqualTo(10); - - Parcel parcel = Parcel.obtain(); - policy.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - - ZenPolicy fromParcel = ZenPolicy.CREATOR.createFromParcel(parcel); - assertThat(fromParcel.getUserModifiedFields()).isEqualTo(10); - } - - @Test - public void testPolicy_userModifiedFields() { - ZenPolicy.Builder builder = new ZenPolicy.Builder(); - builder.setUserModifiedFields(10); - assertThat(builder.build().getUserModifiedFields()).isEqualTo(10); - - builder.setUserModifiedFields(0); - assertThat(builder.build().getUserModifiedFields()).isEqualTo(0); - } - - @Test public void testPolicyBuilder_constructFromPolicy() { ZenPolicy.Builder builder = new ZenPolicy.Builder(); ZenPolicy policy = builder.allowRepeatCallers(true).allowAlarms(false) .showLights(true).showBadges(false) .allowPriorityChannels(true) - .setUserModifiedFields(20).build(); + .build(); ZenPolicy newPolicy = new ZenPolicy.Builder(policy).build(); @@ -689,7 +663,6 @@ public class ZenPolicyTest extends UiServiceTestCase { assertThat(newPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET); assertThat(newPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW); - assertThat(newPolicy.getUserModifiedFields()).isEqualTo(20); } @Test diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java index 336bfdd0fb14..a8fd6f29f862 100644 --- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java +++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java @@ -35,11 +35,13 @@ import android.util.Slog; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.Keep; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.RingBuffer; import com.android.server.usage.BroadcastResponseStatsTracker.NotificationEventType; +import java.util.function.IntFunction; +import java.util.function.Supplier; + public class BroadcastResponseStatsLogger { private static final int MAX_LOG_SIZE = @@ -49,10 +51,10 @@ public class BroadcastResponseStatsLogger { @GuardedBy("mLock") private final LogBuffer mBroadcastEventsBuffer = new LogBuffer( - BroadcastEvent.class, MAX_LOG_SIZE); + BroadcastEvent::new, BroadcastEvent[]::new, MAX_LOG_SIZE); @GuardedBy("mLock") private final LogBuffer mNotificationEventsBuffer = new LogBuffer( - NotificationEvent.class, MAX_LOG_SIZE); + NotificationEvent::new, NotificationEvent[]::new, MAX_LOG_SIZE); void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage, UserHandle targetUser, long idForResponseEvent, @@ -96,8 +98,8 @@ public class BroadcastResponseStatsLogger { private static final class LogBuffer<T extends Data> extends RingBuffer<T> { - LogBuffer(Class<T> classType, int capacity) { - super(classType, capacity); + LogBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) { + super(newItem, newBacking, capacity); } void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage, @@ -179,8 +181,7 @@ public class BroadcastResponseStatsLogger { } } - @Keep - public static final class BroadcastEvent implements Data { + private static final class BroadcastEvent implements Data { public int sourceUid; public int targetUserId; public int targetUidProcessState; @@ -200,8 +201,7 @@ public class BroadcastResponseStatsLogger { } } - @Keep - public static final class NotificationEvent implements Data { + private static final class NotificationEvent implements Data { public int type; public String packageName; public int userId; @@ -218,7 +218,7 @@ public class BroadcastResponseStatsLogger { } } - public interface Data { + private interface Data { void reset(); } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java index 1df7012c44f8..49ad46131b0d 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java @@ -62,7 +62,8 @@ public class PhoneCallStateHandler { SubscriptionManager subscriptionManager, TelephonyManager telephonyManager, Callback callback) { - mSubscriptionManager = Objects.requireNonNull(subscriptionManager); + mSubscriptionManager = Objects.requireNonNull(subscriptionManager) + .createForAllUserProfiles(); mTelephonyManager = Objects.requireNonNull(telephonyManager); mCallback = Objects.requireNonNull(callback); mSubscriptionManager.addOnSubscriptionsChangedListener( diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 57b13e960093..e81f48280e46 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1049,8 +1049,17 @@ public class TelecomManager { public static final int PRESENTATION_UNAVAILABLE = 5; + /** + * Controls audio route for video calls. + * 0 - Use the default audio routing strategy. + * 1 - Disable the speaker. Route the audio to Headset or Bluetooth + * or Earpiece, based on the default audio routing strategy. + * @hide + */ + public static final String PROPERTY_VIDEOCALL_AUDIO_OUTPUT = "persist.radio.call.audio.output"; + /* - * Values for the adb property "persist.radio.videocall.audio.output" + * Values for the adb property "persist.radio.call.audio.output" */ /** @hide */ public static final int AUDIO_OUTPUT_ENABLE_SPEAKER = 0; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 73c26a3e5fc9..1badf674c8ce 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9694,6 +9694,27 @@ public class CarrierConfigManager { "remove_satellite_plmn_in_manual_network_scan_bool"; /** + * An integer key holds the time interval for refreshing or re-querying the satellite + * entitlement status from the entitlement server to ensure it is the latest. + * + * The default value is 30 days (1 month). + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = + "satellite_entitlement_status_refresh_days_int"; + + /** + * This configuration enables device to query the entitlement server to get the satellite + * configuration. + * This will need agreement the carrier before enabling this flag. + * + * The default value is false. + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = + "satellite_entitlement_supported_bool"; + + /** * Indicating whether DUN APN should be disabled when the device is roaming. In that case, * the default APN (i.e. internet) will be used for tethering. * @@ -10799,6 +10820,8 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT, CellSignalStrengthLte.USE_RSRP); sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true); + sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30); + sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false); sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, ""); sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false); diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 3b0397b6e480..70047a6feb9c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -928,10 +928,19 @@ public final class SatelliteManager { @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; + /** + * Satellite communication restricted by entitlement server. This can be triggered based on + * the EntitlementStatus value received from the entitlement server to enable or disable + * satellite. + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; + /** @hide */ @IntDef(prefix = "SATELLITE_COMMUNICATION_RESTRICTION_REASON_", value = { SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, - SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION + SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION, + SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteCommunicationRestrictionReason {} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java index e3988cd20199..c44d9435d203 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java @@ -68,7 +68,7 @@ public class ActivityEmbeddingSecondaryActivity extends Activity { // This triggers a recalcuation of splitatributes. ActivityEmbeddingController .getInstance(ActivityEmbeddingSecondaryActivity.this) - .invalidateTopVisibleActivityStacks(); + .invalidateVisibleActivityStacks(); } }); findViewById(R.id.secondary_enter_pip_button).setOnClickListener( diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java new file mode 100644 index 000000000000..379c4ae8a059 --- /dev/null +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.okhttp.internalandroidapi; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; + +/** + * A domain name service that resolves IP addresses for host names. + * @hide + * @hide This class is not part of the Android public SDK API + */ +public interface Dns { + /** + * Returns the IP addresses of {@code hostname}, in the order they should + * be attempted. + * + * @hide + */ + List<InetAddress> lookup(String hostname) throws UnknownHostException; +} |