Merge "Add setLogo() API on Biometric Prompt." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index c43aa75..217101e7 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.internal.foldables.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}",
+ ":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}",
+ ":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}",
- ":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.net.flags-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}",
- ":power_flags_lib{.generated_srcjars}",
- ":android.content.flags-aconfig-java{.generated_srcjars}",
- ":aconfig_mediacodec_flags_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 d13c4d7..0877bce 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -97,6 +97,7 @@
"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 d59775f..c904eb4 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 1686943..126176d 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -269,6 +269,7 @@
// 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 43ba6dc..f052b85 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5317,7 +5317,6 @@
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 @@
@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 @@
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 b8c9fc66..2596f9c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -133,6 +133,7 @@
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 @@
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 @@
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 bbe03a3..0d1d8d7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -284,16 +284,6 @@
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 @@
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 @@
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 5b354fc..d57a4e5 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -23,7 +23,6 @@
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 @@
@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 @@
* @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 @@
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 @@
mIconResId = source.readInt();
mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
mType = source.readInt();
- mUserModifiedFields = source.readInt();
}
}
@@ -307,8 +302,7 @@
* 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 @@
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 @@
dest.writeInt(mIconResId);
dest.writeString(mTriggerDescription);
dest.writeInt(mType);
- dest.writeInt(mUserModifiedFields);
}
}
@@ -582,16 +549,14 @@
.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 @@
&& 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 @@
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 @@
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 @@
mAllowManualInvocation = rule.isManualInvocationAllowed();
mCreationTime = rule.getCreationTime();
mPkg = rule.getPackageName();
- mUserModifiedFields = rule.mUserModifiedFields;
}
public Builder(@NonNull String name, @NonNull Uri conditionId) {
@@ -848,19 +809,6 @@
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 @@
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 476232c..ed0cfbe 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5652,7 +5652,7 @@
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 57749d4..269c6c2 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -123,6 +123,7 @@
* @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 @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 @@
* @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 @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 @@
@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 @@
}
/**
+ * 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 @@
@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 @@
}
/**
+ * 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 @@
*
* @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 @@
}
/**
+ * 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 088949e..f4312a9 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 @@
* 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 5d31cc0..cbe4c62 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 @@
* 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 @@
* <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 @@
*
* @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 0000000..ee4eeb6
--- /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 7d84bb3..04d8a49 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3236,9 +3236,17 @@
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 @@
|| 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 @@
// 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 @@
needsGenerationTracker = true;
}
}
-
if (mCallListCommand == null) {
// No list command specified, return empty map
return keyValues;
@@ -3693,20 +3680,23 @@
}
// 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 @@
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 @@
@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 @@
}
}
- 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 8b4a99e..d5ac7a7 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 03ebae5..90049e6 100644
--- a/core/java/android/service/notification/ZenDeviceEffects.java
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -20,7 +20,6 @@
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 @@
@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 @@
/**
* @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 @@
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 @@
mDisableTouch = disableTouch;
mMinimizeRadioUsage = minimizeRadioUsage;
mMaximizeDoze = maximizeDoze;
- mUserModifiedFields = userModifiedFields;
}
@Override
@@ -153,15 +138,14 @@
&& 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 @@
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 @@
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 @@
}
};
- /**
- * 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 @@
dest.writeBoolean(mDisableTouch);
dest.writeBoolean(mMinimizeRadioUsage);
dest.writeBoolean(mMaximizeDoze);
- dest.writeInt(mUserModifiedFields);
}
/** Builder class for {@link ZenDeviceEffects} objects. */
@@ -365,7 +338,6 @@
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 @@
mDisableTouch = zenDeviceEffects.shouldDisableTouch();
mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage();
mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze();
- mUserModifiedFields = zenDeviceEffects.mUserModifiedFields;
}
/**
@@ -510,24 +481,13 @@
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 d4d602d..c479877 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -206,7 +206,7 @@
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 @@
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 @@
}
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 @@
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 @@
if (Flags.modesApi()) {
writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannels(), out);
- out.attributeInt(null, USER_MODIFIED_FIELDS, policy.getUserModifiedFields());
}
}
@@ -1083,7 +1087,6 @@
.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 @@
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 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 @@
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 @@
}
/**
- * @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 @@
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 @@
.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 @@
&& 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 @@
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 2cda7c8..fb491d0 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -45,8 +45,8 @@
*/
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 @@
* the same time.
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_MESSAGES = 1 << 0;
/**
@@ -84,7 +83,6 @@
* the same time.
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_CALLS = 1 << 1;
/**
@@ -92,13 +90,11 @@
* 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 @@
/**
* @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 @@
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 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 @@
* 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 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 @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 @@
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 @@
dest.writeInt(mConversationSenders);
if (Flags.modesApi()) {
dest.writeInt(mAllowChannels);
- dest.writeInt(mUserModifiedFields);
}
}
@@ -1079,7 +1030,7 @@
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 @@
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 @@
&& 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 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 34b46700..57174de 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -26,6 +26,7 @@
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 @@
// 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 @@
* 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 @@
// 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 @@
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 bceb872..feae173 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -421,8 +421,11 @@
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 @@
@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 bb16ad2..2b96ee6 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -69,11 +69,10 @@
}
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 0488659..342ba1b6 100644
--- a/core/java/com/android/internal/util/RingBuffer.java
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -19,7 +19,10 @@
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 @@
@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 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 5f3f641..4a82675 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 f3acab0..fe12f6e 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 @@
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="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="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="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="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="M159.03,176.91h4.04v4.04h-4.04z"
+ 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,275.73h4.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="M373.41,158.93h4.04v4.04h-4.04z"
+ 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="M112.1,129.34h4.04v4.04h-4.04z"
+ 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="M285.93,252.22h4.04v4.04h-4.04z"
+ 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="M318.96,218.84h4.04v4.04h-4.04z"
+ 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="M294.05,288.55h4.04v4.04h-4.04z"
+ 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="M330.82,263.31h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
+ <path
+ android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M224.18,216.82h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M293.34,339.25h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M165.09,236.28h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M378.92,192h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ 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 0000000..b085a27
--- /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 9d85b65..1925588 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.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 @@
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 1617eda..e32a57b 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 @@
@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 @@
@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 cf3eb12..cfbda84 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -19,6 +19,7 @@
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 @@
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 df99a16..4be75f8 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -542,6 +542,8 @@
<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 bb433db..e7f6f0d 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 @@
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 @@
*/
@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 e487328..bb0dd95 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 @@
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 eb4d494..ac2a936 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -124,24 +124,19 @@
// 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 b002bbf..ea136ed 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1079,7 +1079,7 @@
* @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 5da4b95..c2cb757 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -9,6 +9,9 @@
android_library {
name: "SettingsLib",
+ defaults: [
+ "SettingsLintDefaults",
+ ],
static_libs: [
"androidx.localbroadcastmanager_localbroadcastmanager",
@@ -60,8 +63,15 @@
"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 1c830c1..74b556e 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 @@
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 916d83a..3f7a852 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 @@
* 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 071afaf..f7f0673 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 @@
}
}
+ 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 032a838..560bc46 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 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 7409eea..f7ec80b 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 @@
}
@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 890510f..0eed2b7 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 ecac5ee..edbc0b3 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 @@
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 e065187..3dfc454 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 7443e4c..168e6e0 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 c23a49c..b464498 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -343,3 +343,10 @@
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 900616f..42fcd13 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 0b26ae9..e835d3e 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.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.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 @@
)
}
-/** 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 @@
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 bded98d..747faab 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 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 @@
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 6bb525a..0c2c519 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 @@
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 9c0f1fe..085dcf4 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.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.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 @@
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 @@
},
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 8fe9656..fc337fb 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 e893169..c4bcb53 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.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 @@
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 78b854e..c2efc05 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.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 @@
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 f775175..0959f1b 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.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.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 @@
`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 @@
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 45a60199..1281e44 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.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 @@
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 27d1eb7..c86c747 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 @@
underTest =
ColorCorrectionRepositoryImpl(
testDispatcher,
+ scope.backgroundScope,
settings,
)
}
@@ -58,83 +60,78 @@
@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 @@
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 @@
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 423e124..4853529 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 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 @@
underTest =
ColorInversionRepositoryImpl(
testDispatcher,
+ scope.backgroundScope,
settings,
)
}
@@ -59,55 +60,47 @@
@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)
- settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
- underTest.isEnabled(testUser2).launchIn(backgroundScope)
- settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
+ val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+ val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
+ settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
+ 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 0000000..820bfbf
--- /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
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
rename to 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 cd83c07..178eb6a 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.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 @@
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 @@
smartspaceRepository = withDeps.smartspaceRepository
keyguardRepository = withDeps.keyguardRepository
editWidgetsActivityStarter = withDeps.editWidgetsActivityStarter
+ communalPrefsRepository = withDeps.communalPrefsRepository
underTest = withDeps.communalInteractor
}
@@ -331,10 +334,9 @@
}
@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 @@
}
@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 804c052..f9cfc37 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.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 @@
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 @@
widgetRepository = withDeps.widgetRepository
smartspaceRepository = withDeps.smartspaceRepository
mediaRepository = withDeps.mediaRepository
+ communalPrefsRepository = withDeps.communalPrefsRepository
underTest =
CommunalViewModel(
@@ -149,9 +152,6 @@
// 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 @@
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 @@
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 8be4eeb..f23716c 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.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.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 @@
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 @@
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 @@
keyguardRepository,
headsUpManager,
powerInteractor,
+ activeNotificationsInteractor,
)
.apply { setUp(notificationPresenter, notificationsController) }
@@ -257,7 +269,8 @@
}
@Test
- fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() =
+ @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOff_notifCountOne() =
testScope.runTest {
underTest.start()
@@ -273,6 +286,23 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 0a10b2c..0c7ce97 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 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 @@
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)
+ underTest.setGroupExpanded(summary2, true)
assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary1, true)
+ underTest.setGroupExpanded(summary1, true)
assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary2, false)
+ underTest.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)
- assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary2, true)
- 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/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
new file mode 100644
index 0000000..2cbcc5a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.render
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+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.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GroupMembershipManagerTest : SysuiTestCase() {
+ private var underTest = GroupMembershipManagerImpl()
+
+ @Test
+ fun isChildInGroup_topLevel() {
+ val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.isChildInGroup(topLevelEntry)).isFalse()
+ }
+
+ @Test
+ fun isChildInGroup_noParent() {
+ val noParentEntry = NotificationEntryBuilder().setParent(null).build()
+ assertThat(underTest.isChildInGroup(noParentEntry)).isFalse()
+ }
+
+ @Test
+ fun isChildInGroup_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.isChildInGroup(summary)).isFalse()
+ }
+
+ @Test
+ fun isGroupSummary_topLevelEntry() {
+ val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.isGroupSummary(entry)).isFalse()
+ }
+
+ @Test
+ fun isGroupSummary_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.isGroupSummary(summary)).isTrue()
+ }
+
+ @Test
+ fun isGroupSummary_child() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+ assertThat(underTest.isGroupSummary(entry)).isFalse()
+ }
+
+ @Test
+ fun getGroupSummary_topLevelEntry() {
+ val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
+ assertThat(underTest.getGroupSummary(entry)).isNull()
+ }
+
+ @Test
+ fun getGroupSummary_summary() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
+
+ assertThat(underTest.getGroupSummary(summary)).isEqualTo(summary)
+ }
+
+ @Test
+ fun getGroupSummary_child() {
+ val groupKey = "group"
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, groupKey)
+ .setGroupSummary(mContext, true)
+ .build()
+ val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
+ GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
+
+ assertThat(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 0000000..84b89ca
--- /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 66c54f2..0b35559 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 0628c3e..ddad1e3 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 565ed10..f51e109 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 2cca951..4789a22 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 85b6e8d..5db9eee 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 bcc3c83..61a323d4 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 8be1cc7..3839dd9 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 6e035e8..ec4c7d5 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 66f965a..efd8f7f 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.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 @@
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 @@
DevicePostureController devicePostureController,
KeyguardViewController keyguardViewController,
FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ KeyboardRepository keyboardRepository) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -240,6 +243,7 @@
mFeatureFlags = featureFlags;
mSelectedUserInteractor = selectedUserInteractor;
mUiEventLogger = uiEventLogger;
+ mKeyboardRepository = keyboardRepository;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -268,19 +272,22 @@
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 36fe75f..fcff0db 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_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 @@
// 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 376933d..60dd568 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 @@
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 @@
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 @@
});
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 2aab1f1..b958f55 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.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 @@
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 5729119..1cdcbd0 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.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 @@
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 05fb5fa..f019d61 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.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 @@
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 b15aaaf..e88aaf01 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
@@ -91,7 +91,7 @@
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 6483ae4..c7e5b64 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 @@
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 @@
@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 @@
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 bbf10c5..419eada 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 @@
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 @@
@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 10768ea..dc07c1b 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 @@
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 @@
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 0000000..c2ea2e9
--- /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 0000000..a4ff6d3
--- /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 553b3eb..1f4be40 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 @@
/** 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 @@
* 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 @@
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 9fa4cd6..aa9a426 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.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 @@
constructor(
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
+ private val communalPrefsRepository: CommunalPrefsRepository,
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
keyguardInteractor: KeyguardInteractor,
@@ -122,7 +124,7 @@
}
/** 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 @@
/** 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 09c18ed..d619362 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 @@
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 846736c..ea8ba10 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 @@
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 496c64e..c6fb4f9 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 @@
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 @@
@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 0000000..37034f6
--- /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 0000000..34d2888
--- /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 0000000..d5f082a
--- /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 0000000..26eb706
--- /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 fedd63b..8fa33ee7 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 @@
) {
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 @@
::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 @@
}
}
+ /**
+ * 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 2a68f26..0c0eb8a 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.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 @@
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 0000000..5910701
--- /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 24cb8ff..3e00940 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 @@
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 0000000..ca790e8
--- /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 4770d52..1c9f5fd 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.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 @@
private int mPosition = -1;
private boolean mAnimating;
+ private PageScrollActionListener mPageScrollActionListener;
private final Animatable2.AnimationCallback mAnimationCallback =
new Animatable2.AnimationCallback() {
@@ -77,6 +83,14 @@
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 @@
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 052c0da..43f3a22 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.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 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 @@
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 a6cccf1..e2959fe 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.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 @@
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 @@
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 ab08f66..a755805 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 @@
/** 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 4a839b8..93cfc5d 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 @@
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 0000000..b09bfe2
--- /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 2d5afd5..3cdb2cd 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 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 @@
*/
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 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 @@
@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 @@
}
// 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 cb79353..da12479 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.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 @@
*/
@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 @@
@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 5180bab..b22e9fd 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 @@
/**
* 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 ac49960..1677418 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 @@
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 @@
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 @@
}
}
- 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 d9c5108..c527bb5 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 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 e791a64..04db653 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.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 @@
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 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 @@
/* 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 @@
@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 @@
@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 @@
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 @@
}
}
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 @@
}
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 @@
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 c0e0b73..6a66bb7 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.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.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 javax.inject.Inject;
import javax.inject.Named;
+import javax.inject.Provider;
/**
* Controller for {@link NotificationStackScrollLayout}.
@@ -181,6 +186,8 @@
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 @@
SeenNotificationsInteractor seenNotificationsInteractor,
NotificationListViewBinder viewBinder,
ShadeController shadeController,
+ SceneContainerFlags sceneContainerFlags,
+ Provider<WindowRootView> windowRootView,
+ NotificationStackAppearanceInteractor stackAppearanceInteractor,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
@@ -739,6 +749,8 @@
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
+ mWindowRootView = windowRootView;
+ mStackAppearanceInteractor = stackAppearanceInteractor;
mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
mSecureSettings = secureSettings;
@@ -1076,6 +1088,28 @@
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 e78a694..aac3c28 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 @@
/** 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 61a4dfc..1dfde09 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 @@
/** 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 a9b542d..ed15f55 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.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 @@
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 834d3ff..74db583 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 @@
/** 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 9f22118..385f061 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.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 @@
@Inject
constructor(
private val interactor: NotificationStackAppearanceInteractor,
+ shadeInteractor: ShadeInteractor,
flags: SceneContainerFlags,
featureFlags: FeatureFlagsClassic,
) {
@@ -66,4 +69,22 @@
/** 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 7c626a1..e0c6bba 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.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 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 @@
@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 @@
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 0000000..d397fc2
--- /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 0ea4e9f..8b6611f 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 @@
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 @@
}
@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 0000000..60eb3ae
--- /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 db9e548..8ef3f57 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 @@
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 @@
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 @@
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 @@
}
@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 215f8b1..c4911a4 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.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.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 @@
@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 @@
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 65697b73..36f643a 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.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.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 @@
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 @@
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 @@
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 @@
@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 @@
@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/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
deleted file mode 100644
index c1ffa64..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render
-
-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
-
-@SmallTest
-class GroupMembershipManagerTest : SysuiTestCase() {
- private lateinit var gmm: GroupMembershipManagerImpl
-
- private val featureFlags = FakeFeatureFlagsClassic()
-
- @Before
- fun setUp() {
- gmm = GroupMembershipManagerImpl(featureFlags)
- }
-
- @Test
- fun testIsChildInGroup_topLevel() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
- 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()
- }
-
- @Test
- fun testIsChildInGroup_noParent_new() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
- val noParentEntry = NotificationEntryBuilder().setParent(null).build()
- assertThat(gmm.isChildInGroup(noParentEntry)).isFalse()
- }
- @Test
- fun testIsChildInGroup_summary_old() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.isChildInGroup(summary)).isTrue()
- }
-
- @Test
- fun testIsChildInGroup_summary_new() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.isChildInGroup(summary)).isFalse()
- }
-
- @Test
- fun testIsChildInGroup_child() {
- 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)
- val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(gmm.isGroupSummary(entry)).isFalse()
- }
-
- @Test
- fun testIsGroupSummary_summary() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.isGroupSummary(summary)).isTrue()
- }
-
- @Test
- fun testIsGroupSummary_child() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
- assertThat(gmm.isGroupSummary(entry)).isFalse()
- }
-
- @Test
- fun testGetGroupSummary_topLevelEntry() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
- val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(gmm.getGroupSummary(entry)).isNull()
- }
-
- @Test
- fun testGetGroupSummary_summary() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary)
- }
-
- @Test
- fun testGetGroupSummary_child() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
-
- assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 168e782..ff02ef3 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.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.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 @@
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 @@
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 8a730cf..71613ed 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.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.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 @@
@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 @@
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 88662b6..89f826b 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.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.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.Mock;
import org.mockito.MockitoAnnotations;
+import javax.inject.Provider;
+
/**
* Tests for {@link NotificationStackScrollLayoutController}.
*/
@@ -153,6 +158,9 @@
@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 @@
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 a172120..4afcc8c 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.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.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 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.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.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 @@
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 @@
/* 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 0000000..79107cc
--- /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 0000000..d3ed58b
--- /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 e82cae4..c85c27e 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.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 @@
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 95ff889..8430cf6 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 @@
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 @@
mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
+ communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(),
appWidgetHost: AppWidgetHost = mock(),
editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(),
): WithDependencies {
@@ -55,6 +57,7 @@
testScope,
communalRepository,
widgetRepository,
+ communalPrefsRepository,
mediaRepository,
smartspaceRepository,
tutorialRepository,
@@ -66,6 +69,7 @@
CommunalInteractor(
communalRepository,
widgetRepository,
+ communalPrefsRepository,
mediaRepository,
smartspaceRepository,
withDeps.keyguardInteractor,
@@ -79,6 +83,7 @@
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 649b373..7cbbaab 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 @@
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 59f0ec3..37b1b08 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 @@
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 @@
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
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
rename to 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
similarity index 90%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index dda7fad..4efcada 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 @@
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 @@
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 0dbade7..d7e948e 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.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 d9beabb..d80ee75 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.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 @@
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 a856f42..f3b74ea 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 @@
processResponseLockedForPcc(response, response.getClientState(), requestFlags);
mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
+ mFillResponseEventLogger.logAndEndEvent();
}
@@ -1847,6 +1848,33 @@
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 @@
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 df8d9e1..8fa0089 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.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 @@
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 @@
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 @@
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 @@
// 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 0000000..ee4eeb6
--- /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 6236e2b..46668de 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 @@
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 4d19ead..d7188c7 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.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 @@
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 @@
/**
* 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 c9f45e5..2ae040a6 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 @@
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 c7f5703..93ffd97 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_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 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 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 @@
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 @@
* {@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
/**
- * 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();
- int userModifiedFields = oldPolicy.getUserModifiedFields();
+ zenRule.zenPolicy = newPolicy;
+
if (updateBitmask) {
+ int userModifiedFields = zenRule.zenPolicyUserModifiedFields;
if (oldPolicy.getPriorityMessageSenders() != newPolicy.getPriorityMessageSenders()) {
userModifiedFields |= ZenPolicy.FIELD_MESSAGES;
}
@@ -1178,66 +1187,47 @@
!= 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 @@
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 @@
.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 @@
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 e993d9e..d644235 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.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.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 @@
private long mLastKnownForegroundElapsedTime;
@GuardedBy("mLock")
+ private long mLastReportedTime;
+
+ @GuardedBy("mLock")
private boolean mIsAppSearchSchemaUpToDate;
private ShortcutPackage(ShortcutUser shortcutUser,
@@ -1673,6 +1678,26 @@
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 96c205c..c23d2ab 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 @@
private CompressFormat mIconPersistFormat;
private int mIconPersistQuality;
- private int mSaveDelayMillis;
+ int mSaveDelayMillis;
private final IPackageManager mIPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
@@ -2291,7 +2291,7 @@
packageShortcutsChanged(ps, changedShortcuts, removedShortcuts);
- reportShortcutUsedInternal(packageName, shortcut.getId(), userId);
+ ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId());
verifyStates();
}
@@ -2695,25 +2695,17 @@
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 @@
}
// 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 c1b7489..7b0a69b 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.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 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 60dc4ff..d95b431 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 @@
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 676203b..2e0546e 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 @@
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 69fbe6b..9b1f9c8 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 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 e7621ff..182e1c1 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 @@
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 @@
}
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 @@
*
* @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 f620a97..2accf9a 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 @@
}
}
+ /** 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 708d63e..59e3350 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 @@
// 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 022268d..62d2d7e 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 @@
) {
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 @@
}
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 @@
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 ff91d34..92016df 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 @@
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.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.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 @@
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 0045026..0831086 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -29,8 +29,6 @@
"src/**/*.java",
"src/**/*.kt",
- "test-apps/JobTestApp/src/**/*.java",
-
"test-apps/SuspendTestApp/src/**/*.java",
],
static_libs: [
@@ -124,7 +122,6 @@
},
data: [
- ":JobTestApp",
":SimpleServiceTestApp1",
":SimpleServiceTestApp2",
":SimpleServiceTestApp3",
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index b1d5039..27c522d 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 e871fc5..0000000
--- 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 0f5fb91..d50affb 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 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 @@
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 6458bcd..0000000
--- 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 ac35805..0000000
--- 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 6f207fb1..0000000
--- 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 99eb196..0000000
--- 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 b8585f2..0000000
--- 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 c1f35cc..723ac15 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 @@
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 @@
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 3d8ec2e..f604f1e 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 @@
.setShouldMaximizeDoze(true)
.setShouldUseNightMode(false)
.setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true)
- .setUserModifiedFields(8)
.build();
assertThat(deviceEffects.shouldDimWallpaper()).isTrue();
@@ -65,7 +64,6 @@
assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse();
assertThat(deviceEffects.shouldUseNightMode()).isFalse();
assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue();
- assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(8);
}
@Test
@@ -97,7 +95,6 @@
.setShouldMinimizeRadioUsage(true)
.setShouldUseNightMode(true)
.setShouldSuppressAmbientDisplay(true)
- .setUserModifiedFields(6)
.build();
Parcel parcel = Parcel.obtain();
@@ -116,7 +113,6 @@
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 db92719..e523e79f 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
.allowPriorityChannels(false)
.hideAllVisualEffects()
.showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true)
- .setUserModifiedFields(4)
.build();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -732,7 +727,6 @@
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 9d7cf53..2e64645 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 @@
: 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 78fe41f..edc876a 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.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_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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
.setManualInvocationAllowed(ALLOW_MANUAL)
.build();
- ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(OWNER.getPackageName(), azr,
+ UPDATE_ORIGIN_APP, "add", CUSTOM_PKG_UID);
- mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true);
+ ZenModeConfig.ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
- 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);
+ 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 @@
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 @@
.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 @@
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 @@
@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 @@
// 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();
+
+ 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_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();
- }
-
- @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 @@
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 @@
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 @@
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 @@
.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 @@
.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 @@
@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 @@
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 @@
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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
.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 @@
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 @@
.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 @@
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 @@
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 @@
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 7941eb4..4ed55df 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 @@
}
@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 @@
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 336bfdd..a8fd6f2 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.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 @@
@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 @@
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 @@
}
}
- @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 @@
}
}
- @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 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 1df7012..49ad461 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 @@
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 57b13e9..e81f482 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1049,8 +1049,17 @@
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 73c26a3..1badf67 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9694,6 +9694,27 @@
"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 @@
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 3b0397b..70047a6 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -928,10 +928,19 @@
@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 e3988cd..c44d943 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 @@
// 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 0000000..379c4ae
--- /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;
+}