diff options
317 files changed, 6259 insertions, 2877 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 2ae72ef4e81c..217101e77bb7 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -13,70 +13,142 @@ // limitations under the License. aconfig_srcjars = [ - ":android.app.usage.flags-aconfig-java{.generated_srcjars}", + // !!! KEEP THIS LIST ALPHABETICAL !!! + ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}", + ":android.app.flags-aconfig-java{.generated_srcjars}", ":android.app.smartspace.flags-aconfig-java{.generated_srcjars}", + ":android.app.usage.flags-aconfig-java{.generated_srcjars}", + ":android.appwidget.flags-aconfig-java{.generated_srcjars}", + ":android.chre.flags-aconfig-java{.generated_srcjars}", ":android.companion.flags-aconfig-java{.generated_srcjars}", + ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}", + ":android.content.flags-aconfig-java{.generated_srcjars}", ":android.content.pm.flags-aconfig-java{.generated_srcjars}", ":android.content.res.flags-aconfig-java{.generated_srcjars}", + ":android.credentials.flags-aconfig-java{.generated_srcjars}", + ":android.database.sqlite-aconfig-java{.generated_srcjars}", + ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}", ":android.hardware.flags-aconfig-java{.generated_srcjars}", ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}", + ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}", ":android.location.flags-aconfig-java{.generated_srcjars}", + ":android.media.tv.flags-aconfig-java{.generated_srcjars}", + ":android.multiuser.flags-aconfig-java{.generated_srcjars}", ":android.net.vcn.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", ":android.os.flags-aconfig-java{.generated_srcjars}", ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", + ":android.permission.flags-aconfig-java{.generated_srcjars}", + ":android.provider.flags-aconfig-java{.generated_srcjars}", ":android.security.flags-aconfig-java{.generated_srcjars}", ":android.server.app.flags-aconfig-java{.generated_srcjars}", + ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", ":android.service.chooser.flags-aconfig-java{.generated_srcjars}", + ":android.service.controls.flags-aconfig-java{.generated_srcjars}", ":android.service.dreams.flags-aconfig-java{.generated_srcjars}", ":android.service.notification.flags-aconfig-java{.generated_srcjars}", - ":android.view.flags-aconfig-java{.generated_srcjars}", + ":android.service.voice.flags-aconfig-java{.generated_srcjars}", + ":android.speech.flags-aconfig-java{.generated_srcjars}", + ":android.tracing.flags-aconfig-java{.generated_srcjars}", ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}", + ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}", + ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", + ":android.view.flags-aconfig-java{.generated_srcjars}", + ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}", + ":android.webkit.flags-aconfig-java{.generated_srcjars}", + ":android.widget.flags-aconfig-java{.generated_srcjars}", ":audio-framework-aconfig", ":camera_platform_flags_core_java_lib{.generated_srcjars}", - ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", - ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}", ":com.android.hardware.input-aconfig-java{.generated_srcjars}", ":com.android.input.flags-aconfig-java{.generated_srcjars}", - ":com.android.text.flags-aconfig-java{.generated_srcjars}", - ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}", - ":telecom_flags_core_java_lib{.generated_srcjars}", - ":telephony_flags_core_java_lib{.generated_srcjars}", - ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}", - ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}", - ":android.widget.flags-aconfig-java{.generated_srcjars}", - ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}", - ":sdk_sandbox_flags_lib{.generated_srcjars}", - ":android.permission.flags-aconfig-java{.generated_srcjars}", - ":android.database.sqlite-aconfig-java{.generated_srcjars}", - ":hwui_flags_java_lib{.generated_srcjars}", - ":framework_graphics_flags_java_lib{.generated_srcjars}", - ":display_flags_lib{.generated_srcjars}", ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}", - ":android.multiuser.flags-aconfig-java{.generated_srcjars}", - ":android.app.flags-aconfig-java{.generated_srcjars}", - ":android.credentials.flags-aconfig-java{.generated_srcjars}", - ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", - ":com.android.server.flags.services-aconfig-java{.generated_srcjars}", - ":android.service.controls.flags-aconfig-java{.generated_srcjars}", - ":android.service.voice.flags-aconfig-java{.generated_srcjars}", - ":android.media.tv.flags-aconfig-java{.generated_srcjars}", - ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", + ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}", ":com.android.net.flags-aconfig-java{.generated_srcjars}", + ":com.android.server.flags.services-aconfig-java{.generated_srcjars}", + ":com.android.text.flags-aconfig-java{.generated_srcjars}", + ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", ":device_policy_aconfig_flags_lib{.generated_srcjars}", - ":surfaceflinger_flags_java_lib{.generated_srcjars}", - ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}", - ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}", - ":android.tracing.flags-aconfig-java{.generated_srcjars}", - ":android.appwidget.flags-aconfig-java{.generated_srcjars}", - ":android.webkit.flags-aconfig-java{.generated_srcjars}", - ":android.provider.flags-aconfig-java{.generated_srcjars}", - ":android.chre.flags-aconfig-java{.generated_srcjars}", - ":android.speech.flags-aconfig-java{.generated_srcjars}", + ":display_flags_lib{.generated_srcjars}", + ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}", + ":framework_graphics_flags_java_lib{.generated_srcjars}", + ":hwui_flags_java_lib{.generated_srcjars}", ":power_flags_lib{.generated_srcjars}", - ":android.content.flags-aconfig-java{.generated_srcjars}", + ":sdk_sandbox_flags_lib{.generated_srcjars}", + ":surfaceflinger_flags_java_lib{.generated_srcjars}", + ":telecom_flags_core_java_lib{.generated_srcjars}", + ":telephony_flags_core_java_lib{.generated_srcjars}", + // !!! KEEP THIS LIST ALPHABETICAL !!! ] +stubs_defaults { + name: "framework-minus-apex-aconfig-declarations", + aconfig_declarations: [ + "android.app.flags-aconfig", + "android.app.smartspace.flags-aconfig", + "android.app.usage.flags-aconfig", + "android.appwidget.flags-aconfig", + "android.companion.flags-aconfig", + "android.companion.virtual.flags-aconfig", + "android.content.pm.flags-aconfig", + "android.content.res.flags-aconfig", + "android.credentials.flags-aconfig", + "android.database.sqlite-aconfig", + "android.hardware.biometrics.flags-aconfig", + "android.hardware.flags-aconfig", + "android.hardware.radio.flags-aconfig", + "android.hardware.usb.flags-aconfig", + "android.location.flags-aconfig", + "android.media.audio-aconfig", + "android.media.audiopolicy-aconfig", + "android.media.midi-aconfig", + "android.media.tv.flags-aconfig", + "android.multiuser.flags-aconfig", + "android.net.vcn.flags-aconfig", + "android.nfc.flags-aconfig", + "android.os.flags-aconfig", + "android.os.vibrator.flags-aconfig", + "android.permission.flags-aconfig", + "android.provider.flags-aconfig", + "android.security.flags-aconfig", + "android.server.app.flags-aconfig", + "android.service.autofill.flags-aconfig", + "android.service.chooser.flags-aconfig", + "android.service.controls.flags-aconfig", + "android.service.dreams.flags-aconfig", + "android.service.notification.flags-aconfig", + "android.service.voice.flags-aconfig", + "android.speech.flags-aconfig", + "android.tracing.flags-aconfig", + "android.view.accessibility.flags-aconfig", + "android.view.contentcapture.flags-aconfig", + "android.view.contentprotection.flags-aconfig", + "android.view.flags-aconfig", + "android.view.inputmethod.flags-aconfig", + "android.webkit.flags-aconfig", + "android.widget.flags-aconfig", + "camera_platform_flags", + "chre_flags", + "com.android.hardware.input.input-aconfig", + "com.android.input.flags-aconfig", + "com.android.media.flags.bettertogether-aconfig", + "com.android.net.flags-aconfig", + "com.android.server.flags.services-aconfig", + "com.android.text.flags-aconfig", + "com.android.window.flags.window-aconfig", + "device_policy_aconfig_flags", + "display_flags", + "fold_lock_setting_flags", + "framework-jobscheduler-job.flags-aconfig", + "framework_graphics_flags", + "hwui_flags", + "power_flags", + "sdk_sandbox_flags", + "surfaceflinger_flags", + "telecom_flags", + "telephony_flags", + ], +} + filegroup { name: "framework-minus-apex-aconfig-srcjars", srcs: aconfig_srcjars, diff --git a/Ravenwood.bp b/Ravenwood.bp index d13c4d78190c..0877bcedb609 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -97,6 +97,7 @@ android_ravenwood_libgroup { "framework-minus-apex.ravenwood", "hoststubgen-helper-runtime.ravenwood", "hoststubgen-helper-framework-runtime.ravenwood", + "core-libart-for-host", "all-updatable-modules-system-stubs", "junit", "truth", diff --git a/TEST_MAPPING b/TEST_MAPPING index d59775f4060b..c904eb46d88e 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -138,15 +138,13 @@ } ], "postsubmit-ravenwood": [ - // TODO(ravenwood) promote it to presubmit - // TODO: Enable it once the infra knows how to run it. -// { -// "name": "CtsUtilTestCasesRavenwood", -// "file_patterns": [ -// "*Ravenwood*", -// "*ravenwood*" -// ] -// } + { + "name": "CtsUtilTestCasesRavenwood", + "host": true, + "file_patterns": [ + "[Rr]avenwood" + ] + } ], "postsubmit-managedprofile-stress": [ { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 2c9af67ecdc4..44afbe6aff51 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -951,7 +951,7 @@ public final class FlexibilityController extends StateController { @VisibleForTesting static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; @VisibleForTesting - static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 72 * HOUR_IN_MILLIS; + static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS; private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS; @VisibleForTesting final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80}; diff --git a/api/Android.bp b/api/Android.bp index 1686943d08ca..126176dab3f3 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -269,6 +269,7 @@ packages_to_document = [ // classpath (or sources) somehow. stubs_defaults { name: "android-non-updatable-stubs-defaults", + defaults: ["framework-minus-apex-aconfig-declarations"], srcs: [":android-non-updatable-stub-sources"], sdk_version: "none", system_modules: "none", diff --git a/core/api/current.txt b/core/api/current.txt index 5fa0c66161c7..d4cec2d5ba08 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5317,7 +5317,6 @@ package android.app { ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean); ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean); ctor public AutomaticZenRule(android.os.Parcel); - method @FlaggedApi("android.app.modes_api") public boolean canUpdate(); method public int describeContents(); method public android.net.Uri getConditionId(); method @Nullable public android.content.ComponentName getConfigurationActivity(); @@ -13676,11 +13675,8 @@ package android.content.res { @FlaggedApi("android.content.res.font_scale_converter_public") public interface FontScaleConverter { method public float convertDpToSp(float); method public float convertSpToDp(float); - } - - @FlaggedApi("android.content.res.font_scale_converter_public") public class FontScaleConverterFactory { - method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float); - method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread public static boolean isNonLinearFontScalingActive(float); + method @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float); + method @AnyThread public static boolean isNonLinearFontScalingActive(float); } public class ObbInfo implements android.os.Parcelable { @@ -18685,6 +18681,8 @@ package android.hardware.biometrics { method @Nullable public int getAllowedAuthenticators(); method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView(); method @Nullable public CharSequence getDescription(); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.graphics.Bitmap getLogoBitmap(); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public int getLogoRes(); method @Nullable public CharSequence getNegativeButtonText(); method @Nullable public CharSequence getSubtitle(); method @NonNull public CharSequence getTitle(); @@ -18734,6 +18732,8 @@ package android.hardware.biometrics { method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence); method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@DrawableRes int); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@NonNull android.graphics.Bitmap); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence); @@ -18755,21 +18755,21 @@ package android.hardware.biometrics { method @Nullable public java.security.Signature getSignature(); } - @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentListItem { + @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentItem { } - @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentListItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentListItem { - ctor public PromptContentListItemBulletedText(@NonNull CharSequence); + @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem { + ctor public PromptContentItemBulletedText(@NonNull CharSequence); method public int describeContents(); method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentListItemBulletedText> CREATOR; + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemBulletedText> CREATOR; } - @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentListItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentListItem { - ctor public PromptContentListItemPlainText(@NonNull CharSequence); + @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem { + ctor public PromptContentItemPlainText(@NonNull CharSequence); method public int describeContents(); method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentListItemPlainText> CREATOR; + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemPlainText> CREATOR; } @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentView { @@ -18778,7 +18778,7 @@ package android.hardware.biometrics { @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView { method public int describeContents(); method @Nullable public CharSequence getDescription(); - method @NonNull public java.util.List<android.hardware.biometrics.PromptContentListItem> getListItems(); + method @NonNull public java.util.List<android.hardware.biometrics.PromptContentItem> getListItems(); method public static int getMaxEachItemCharacterNumber(); method public static int getMaxItemCount(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -18787,7 +18787,8 @@ package android.hardware.biometrics { public static final class PromptVerticalListContentView.Builder { ctor public PromptVerticalListContentView.Builder(); - method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentListItem); + method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem); + method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem, int); method @NonNull public android.hardware.biometrics.PromptVerticalListContentView build(); method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull CharSequence); } @@ -23343,6 +23344,7 @@ package android.media { field public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info"; field public static final String KEY_HDR_STATIC_INFO = "hdr-static-info"; field public static final String KEY_HEIGHT = "height"; + field @FlaggedApi("com.android.media.codec.flags.codec_importance") public static final String KEY_IMPORTANCE = "importance"; field public static final String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period"; field public static final String KEY_IS_ADTS = "is-adts"; field public static final String KEY_IS_AUTOSELECT = "is-autoselect"; @@ -26671,12 +26673,16 @@ package android.media.tv { public static final class TvContract.Channels implements android.media.tv.TvContract.BaseTvColumns { method @Nullable public static String getVideoResolution(String); + field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_INVISIBLE = 2; // 0x2 + field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_NUMERIC_SELECTABLE_ONLY = 1; // 0x1 + field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_VISIBLE = 0; // 0x0 field public static final String COLUMN_APP_LINK_COLOR = "app_link_color"; field public static final String COLUMN_APP_LINK_ICON_URI = "app_link_icon_uri"; field public static final String COLUMN_APP_LINK_INTENT_URI = "app_link_intent_uri"; field public static final String COLUMN_APP_LINK_POSTER_ART_URI = "app_link_poster_art_uri"; field public static final String COLUMN_APP_LINK_TEXT = "app_link_text"; field public static final String COLUMN_BROADCAST_GENRE = "broadcast_genre"; + field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final String COLUMN_BROADCAST_VISIBILITY_TYPE = "broadcast_visibility_type"; field public static final String COLUMN_BROWSABLE = "browsable"; field public static final String COLUMN_CHANNEL_LIST_ID = "channel_list_id"; field public static final String COLUMN_DESCRIPTION = "description"; @@ -31847,6 +31853,7 @@ package android.os { method public long computeChargeTimeRemaining(); method public int getIntProperty(int); method public long getLongProperty(int); + method @FlaggedApi("android.os.battery_part_status_api") @Nullable public String getStringProperty(int); method public boolean isCharging(); field public static final String ACTION_CHARGING = "android.os.action.CHARGING"; field public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING"; @@ -43074,6 +43081,8 @@ package android.telephony { field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool"; field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index d26662d9039d..e0c58d5d1193 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -133,6 +133,7 @@ package android { field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String GET_APP_METADATA = "android.permission.GET_APP_METADATA"; field public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS"; + field @FlaggedApi("android.app.bic_client") public static final String GET_BACKGROUND_INSTALLED_PACKAGES = "android.permission.GET_BACKGROUND_INSTALLED_PACKAGES"; field @FlaggedApi("android.app.get_binding_uid_importance") public static final String GET_BINDING_UID_IMPORTANCE = "android.permission.GET_BINDING_UID_IMPORTANCE"; field public static final String GET_HISTORICAL_APP_OPS_STATS = "android.permission.GET_HISTORICAL_APP_OPS_STATS"; field public static final String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"; @@ -622,6 +623,7 @@ package android.app { field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility"; field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications"; + field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS = "android:access_restricted_settings"; field public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn"; field public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn"; field public static final String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot"; @@ -3541,6 +3543,7 @@ package android.content { field public static final String CLOUDSEARCH_SERVICE = "cloudsearch"; field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; field public static final String CONTEXTHUB_SERVICE = "contexthub"; + field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation"; field public static final String ETHERNET_SERVICE = "ethernet"; field public static final String EUICC_CARD_SERVICE = "euicc_card"; field public static final String FONT_SERVICE = "font"; @@ -4219,11 +4222,14 @@ package android.content.pm { field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR; field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1 field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0 + field public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; // 0xffffffff field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2 field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1 field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0 + field public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1; // 0xffffffff field public static final int SHOW_IN_SHARING_SURFACES_NO = 2; // 0x2 field public static final int SHOW_IN_SHARING_SURFACES_SEPARATE = 1; // 0x1 + field public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = -1; // 0xffffffff field public static final int SHOW_IN_SHARING_SURFACES_WITH_PARENT = 0; // 0x0 } @@ -10038,12 +10044,17 @@ package android.os { field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9 field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8 field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7 + field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_PART_STATUS = 12; // 0xc + field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11; // 0xb field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3 field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2 field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4 field public static final int CHARGING_POLICY_DEFAULT = 1; // 0x1 field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS"; field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP"; + field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_ORIGINAL = 1; // 0x1 + field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_REPLACED = 2; // 0x2 + field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_UNSUPPORTED = 0; // 0x0 } public final class BatterySaverPolicyConfig implements android.os.Parcelable { @@ -14554,6 +14565,7 @@ package android.telephony { field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1 field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9 field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41; // 0x29 field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SRVCC_STATE_CHANGED = 16; // 0x10 field public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; // 0x14 field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; // 0x12 @@ -14600,6 +14612,10 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int); } + @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") public static interface TelephonyCallback.SimultaneousCellularCallingSupportListener { + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSimultaneousCellularCallingSubscriptionsChanged(@NonNull java.util.Set<java.lang.Integer>); + } + public static interface TelephonyCallback.SrvccStateListener { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSrvccStateChanged(int); } @@ -17154,6 +17170,7 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; // 0x2 field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index bbe03a3d11a2..0d1d8d733139 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -284,16 +284,6 @@ package android.app { method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int); } - public final class AutomaticZenRule implements android.os.Parcelable { - method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields(); - field @FlaggedApi("android.app.modes_api") public static final int FIELD_INTERRUPTION_FILTER = 2; // 0x2 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_NAME = 1; // 0x1 - } - - @FlaggedApi("android.app.modes_api") public static final class AutomaticZenRule.Builder { - method @FlaggedApi("android.app.modes_api") @NonNull public android.app.AutomaticZenRule.Builder setUserModifiedFields(int); - } - public class BroadcastOptions extends android.app.ComponentOptions { ctor public BroadcastOptions(); ctor public BroadcastOptions(@NonNull android.os.Bundle); @@ -1177,6 +1167,7 @@ package android.content.pm { method public int getShowInLauncher(); field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2 field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1 + field public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1; // 0xffffffff field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0 } @@ -3021,47 +3012,8 @@ package android.service.notification { method @Deprecated public boolean isBound(); } - @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable { - method public int getUserModifiedFields(); - field public static final int FIELD_DIM_WALLPAPER = 4; // 0x4 - field public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 16; // 0x10 - field public static final int FIELD_DISABLE_TAP_TO_WAKE = 32; // 0x20 - field public static final int FIELD_DISABLE_TILT_TO_WAKE = 64; // 0x40 - field public static final int FIELD_DISABLE_TOUCH = 128; // 0x80 - field public static final int FIELD_GRAYSCALE = 1; // 0x1 - field public static final int FIELD_MAXIMIZE_DOZE = 512; // 0x200 - field public static final int FIELD_MINIMIZE_RADIO_USAGE = 256; // 0x100 - field public static final int FIELD_NIGHT_MODE = 8; // 0x8 - field public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 2; // 0x2 - } - - @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder { - method @NonNull public android.service.notification.ZenDeviceEffects.Builder setUserModifiedFields(int); - } - - public final class ZenPolicy implements android.os.Parcelable { - method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields(); - field @FlaggedApi("android.app.modes_api") public static final int FIELD_ALLOW_CHANNELS = 8; // 0x8 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_CALLS = 2; // 0x2 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_CONVERSATIONS = 4; // 0x4 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_MESSAGES = 1; // 0x1 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 128; // 0x80 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 32; // 0x20 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 256; // 0x100 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 64; // 0x40 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 512; // 0x200 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_AMBIENT = 32768; // 0x8000 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_BADGE = 16384; // 0x4000 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1024; // 0x400 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_LIGHTS = 2048; // 0x800 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 65536; // 0x10000 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_PEEK = 4096; // 0x1000 - field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 8192; // 0x2000 - } - public static final class ZenPolicy.Builder { ctor public ZenPolicy.Builder(@Nullable android.service.notification.ZenPolicy); - method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder setUserModifiedFields(int); } } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 1db1caf51800..669baf9549cc 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2180,6 +2180,8 @@ public class AppOpsManager { * * @hide */ + @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) + @SystemApi public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS = "android:access_restricted_settings"; diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 5b354fc3b9ed..d57a4e583a1a 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -23,7 +23,6 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.app.NotificationManager.InterruptionFilter; import android.content.ComponentName; import android.net.Uri; @@ -113,8 +112,8 @@ public final class AutomaticZenRule implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface Type {} - /** Used to track which rule variables have been modified by the user. - * Should be checked against the bitmask {@link #getUserModifiedFields()}. + /** + * Enum for the user-modifiable fields in this object. * @hide */ @IntDef(flag = true, prefix = { "FIELD_" }, value = { @@ -128,13 +127,11 @@ public final class AutomaticZenRule implements Parcelable { * @hide */ @FlaggedApi(Flags.FLAG_MODES_API) - @TestApi public static final int FIELD_NAME = 1 << 0; /** * @hide */ @FlaggedApi(Flags.FLAG_MODES_API) - @TestApi public static final int FIELD_INTERRUPTION_FILTER = 1 << 1; private boolean enabled; @@ -153,7 +150,6 @@ public final class AutomaticZenRule implements Parcelable { private int mIconResId; private String mTriggerDescription; private boolean mAllowManualInvocation; - private @ModifiableField int mUserModifiedFields; // Bitwise representation /** * The maximum string length for any string contained in this automatic zen rule. This pertains @@ -256,7 +252,6 @@ public final class AutomaticZenRule implements Parcelable { mIconResId = source.readInt(); mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH); mType = source.readInt(); - mUserModifiedFields = source.readInt(); } } @@ -307,8 +302,7 @@ public final class AutomaticZenRule implements Parcelable { * Returns whether this rule's name has been modified by the user. * @hide */ - // TODO: b/310620812 - Replace with mUserModifiedFields & FIELD_NAME once - // FLAG_MODES_API is inlined. + // TODO: b/310620812 - Consider removing completely. Seems not be used anywhere except tests. public boolean isModified() { return mModified; } @@ -506,32 +500,6 @@ public final class AutomaticZenRule implements Parcelable { return type; } - /** - * Gets the bitmask representing which fields are user modified. Bits are set using - * {@link ModifiableField}. - * @hide - */ - @FlaggedApi(Flags.FLAG_MODES_API) - @TestApi - public @ModifiableField int getUserModifiedFields() { - return mUserModifiedFields; - } - - /** - * Returns {@code true} if the {@link AutomaticZenRule} can be updated. - * When this returns {@code false}, calls to - * {@link NotificationManager#updateAutomaticZenRule(String, AutomaticZenRule)}) with this rule - * will ignore changes to user-configurable fields. - */ - @FlaggedApi(Flags.FLAG_MODES_API) - public boolean canUpdate() { - // The rule is considered updateable if its bitmask has no user modifications, and - // the bitmasks of the policy and device effects have no modification. - return mUserModifiedFields == 0 - && (mZenPolicy == null || mZenPolicy.getUserModifiedFields() == 0) - && (mDeviceEffects == null || mDeviceEffects.getUserModifiedFields() == 0); - } - @Override public int describeContents() { return 0; @@ -560,7 +528,6 @@ public final class AutomaticZenRule implements Parcelable { dest.writeInt(mIconResId); dest.writeString(mTriggerDescription); dest.writeInt(mType); - dest.writeInt(mUserModifiedFields); } } @@ -582,16 +549,14 @@ public final class AutomaticZenRule implements Parcelable { .append(",allowManualInvocation=").append(mAllowManualInvocation) .append(",iconResId=").append(mIconResId) .append(",triggerDescription=").append(mTriggerDescription) - .append(",type=").append(mType) - .append(",userModifiedFields=") - .append(modifiedFieldsToString(mUserModifiedFields)); + .append(",type=").append(mType); } return sb.append(']').toString(); } - @FlaggedApi(Flags.FLAG_MODES_API) - private String modifiedFieldsToString(int bitmask) { + /** @hide */ + public static String fieldsToString(@ModifiableField int bitmask) { ArrayList<String> modified = new ArrayList<>(); if ((bitmask & FIELD_NAME) != 0) { modified.add("FIELD_NAME"); @@ -623,8 +588,7 @@ public final class AutomaticZenRule implements Parcelable { && other.mAllowManualInvocation == mAllowManualInvocation && other.mIconResId == mIconResId && Objects.equals(other.mTriggerDescription, mTriggerDescription) - && other.mType == mType - && other.mUserModifiedFields == mUserModifiedFields; + && other.mType == mType; } return finalEquals; } @@ -634,8 +598,7 @@ public final class AutomaticZenRule implements Parcelable { if (Flags.modesApi()) { return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime, - mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType, - mUserModifiedFields); + mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType); } return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, configurationActivity, mZenPolicy, mModified, creationTime, mPkg); @@ -704,7 +667,6 @@ public final class AutomaticZenRule implements Parcelable { private boolean mAllowManualInvocation; private long mCreationTime; private String mPkg; - private @ModifiableField int mUserModifiedFields; public Builder(@NonNull AutomaticZenRule rule) { mName = rule.getName(); @@ -721,7 +683,6 @@ public final class AutomaticZenRule implements Parcelable { mAllowManualInvocation = rule.isManualInvocationAllowed(); mCreationTime = rule.getCreationTime(); mPkg = rule.getPackageName(); - mUserModifiedFields = rule.mUserModifiedFields; } public Builder(@NonNull String name, @NonNull Uri conditionId) { @@ -848,19 +809,6 @@ public final class AutomaticZenRule implements Parcelable { return this; } - /** - * Sets the bitmask representing which fields have been user-modified. - * This method should not be used outside of tests. The value of userModifiedFields - * should be set based on what values are changed when a rule is populated or updated.. - * @hide - */ - @FlaggedApi(Flags.FLAG_MODES_API) - @TestApi - public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) { - mUserModifiedFields = userModifiedFields; - return this; - } - public @NonNull AutomaticZenRule build() { AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity, mConditionId, mPolicy, mInterruptionFilter, mEnabled); @@ -871,7 +819,6 @@ public final class AutomaticZenRule implements Parcelable { rule.mIconResId = mIconResId; rule.mAllowManualInvocation = mAllowManualInvocation; rule.setPackageName(mPkg); - rule.mUserModifiedFields = mUserModifiedFields; return rule; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 476232cb40b3..ed0cfbe3d9c3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5652,7 +5652,7 @@ public class Notification implements Parcelable pillColor = Colors.flattenAlpha( getColors(p).getTertiaryFixedDimAccentColor(), bgColor); textColor = Colors.flattenAlpha( - getColors(p).getOnTertiaryAccentTextColor(), pillColor); + getColors(p).getOnTertiaryFixedAccentTextColor(), pillColor); } contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 9cf732abb86a..d7554137fa5b 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -31,6 +31,7 @@ import android.app.appsearch.AppSearchManagerFrameworkInitializer; import android.app.blob.BlobStoreManagerFrameworkInitializer; import android.app.contentsuggestions.ContentSuggestionsManager; import android.app.contentsuggestions.IContentSuggestionsManager; +import android.app.ecm.EnhancedConfirmationFrameworkInitializer; import android.app.job.JobSchedulerFrameworkInitializer; import android.app.people.PeopleManager; import android.app.prediction.AppPredictionManager; @@ -1631,6 +1632,9 @@ public final class SystemServiceRegistry { OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers(); DeviceLockFrameworkInitializer.registerServiceWrappers(); VirtualizationFrameworkInitializer.registerServiceWrappers(); + if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) { + EnhancedConfirmationFrameworkInitializer.registerServiceWrappers(); + } } finally { // If any of the above code throws, we're in a pretty bad shape and the process // will likely crash, but we'll reset it just in case there's an exception handler... diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 4bc1237a55d1..249c0e434e78 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4242,6 +4242,7 @@ public abstract class Context { VIRTUALIZATION_SERVICE, GRAMMATICAL_INFLECTION_SERVICE, SECURITY_STATE_SERVICE, + //@hide: ECM_ENHANCED_CONFIRMATION_SERVICE, }) @Retention(RetentionPolicy.SOURCE) @@ -6527,6 +6528,18 @@ public abstract class Context { public static final String SECURITY_STATE_SERVICE = "security_state"; /** + * Use with {@link #getSystemService(String)} to retrieve an + * {@link android.app.ecm.EnhancedConfirmationManager}. + * + * @see #getSystemService(String) + * @see android.app.ecm.EnhancedConfirmationManager + * @hide + */ + @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) + @SystemApi + public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java index 57749d43eb37..269c6c27821c 100644 --- a/core/java/android/content/pm/UserProperties.java +++ b/core/java/android/content/pm/UserProperties.java @@ -123,6 +123,7 @@ public final class UserProperties implements Parcelable { * @hide */ @IntDef(prefix = "SHOW_IN_LAUNCHER_", value = { + SHOW_IN_LAUNCHER_UNKNOWN, SHOW_IN_LAUNCHER_WITH_PARENT, SHOW_IN_LAUNCHER_SEPARATE, SHOW_IN_LAUNCHER_NO, @@ -131,6 +132,13 @@ public final class UserProperties implements Parcelable { public @interface ShowInLauncher { } /** + * Indicates that the show in launcher value for this profile is unknown or unsupported. + * @hide + */ + @TestApi + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1; + /** * Suggests that the launcher should show this user's apps in the main tab. * That is, either this user is a full user, so its apps should be presented accordingly, or, if * this user is a profile, then its apps should be shown alongside its parent's apps. @@ -157,6 +165,7 @@ public final class UserProperties implements Parcelable { * @hide */ @IntDef(prefix = "SHOW_IN_SETTINGS_", value = { + SHOW_IN_SETTINGS_UNKNOWN, SHOW_IN_SETTINGS_WITH_PARENT, SHOW_IN_SETTINGS_SEPARATE, SHOW_IN_SETTINGS_NO, @@ -165,6 +174,12 @@ public final class UserProperties implements Parcelable { public @interface ShowInSettings { } /** + * Indicates that the show in settings value for this profile is unknown or unsupported. + * @hide + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_SETTINGS_UNKNOWN = -1; + /** * Suggests that the Settings app should show this user's apps in the main tab. * That is, either this user is a full user, so its apps should be presented accordingly, or, if * this user is a profile, then its apps should be shown alongside its parent's apps. @@ -309,6 +324,7 @@ public final class UserProperties implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "SHOW_IN_QUIET_MODE_", value = { + SHOW_IN_QUIET_MODE_UNKNOWN, SHOW_IN_QUIET_MODE_PAUSED, SHOW_IN_QUIET_MODE_HIDDEN, SHOW_IN_QUIET_MODE_DEFAULT, @@ -318,6 +334,12 @@ public final class UserProperties implements Parcelable { } /** + * Indicates that the show in quiet mode value for this profile is unknown. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1; + + /** * Indicates that the profile should still be visible in quiet mode but should be shown as * paused (e.g. by greying out its icons). */ @@ -347,6 +369,7 @@ public final class UserProperties implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "SHOW_IN_SHARING_SURFACES_", value = { + SHOW_IN_SHARING_SURFACES_UNKNOWN, SHOW_IN_SHARING_SURFACES_SEPARATE, SHOW_IN_SHARING_SURFACES_WITH_PARENT, SHOW_IN_SHARING_SURFACES_NO, @@ -356,6 +379,12 @@ public final class UserProperties implements Parcelable { } /** + * Indicates that the show in launcher value for this profile is unknown or unsupported. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = SHOW_IN_LAUNCHER_UNKNOWN; + + /** * Indicates that the profile data and apps should be shown in sharing surfaces intermixed with * parent user's data and apps. */ @@ -379,7 +408,8 @@ public final class UserProperties implements Parcelable { * * @hide */ - @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_STRATEGY_"}, value = { + @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_"}, value = { + CROSS_PROFILE_CONTENT_SHARING_UNKNOWN, CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION, CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT }) @@ -388,6 +418,13 @@ public final class UserProperties implements Parcelable { } /** + * Signifies that cross-profile content sharing strategy, both to and from this profile, is + * unknown/unsupported. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; + + /** * Signifies that cross-profile content sharing strategy, both to and from this profile, should * not be delegated to any other user/profile. * For ex: diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java index 088949e7eec2..f4312a905110 100644 --- a/core/java/android/content/res/FontScaleConverter.java +++ b/core/java/android/content/res/FontScaleConverter.java @@ -17,7 +17,9 @@ package android.content.res; +import android.annotation.AnyThread; import android.annotation.FlaggedApi; +import android.annotation.Nullable; /** * A converter for non-linear font scaling. Converts font sizes given in "sp" dimensions to a @@ -40,4 +42,35 @@ public interface FontScaleConverter { * Converts a dimension in "dp" back to "sp". */ float convertDpToSp(float dp); + + /** + * Returns true if non-linear font scaling curves would be in effect for the given scale, false + * if the scaling would follow a linear curve or for no scaling. + * + * <p>Example usage: {@code + * isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)} + */ + @AnyThread + static boolean isNonLinearFontScalingActive(float fontScale) { + return FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale); + } + + /** + * Finds a matching FontScaleConverter for the given fontScale factor. + * + * Generally you shouldn't need this; you can use {@link + * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do + * the scaling conversion for you. Dimens and resources loaded from XML will also be + * automatically converted. But for UI frameworks or other situations where you need to do the + * conversion without an Android Context, you can use this method. + * + * @param fontScale the scale factor, usually from {@link Configuration#fontScale}. + * + * @return a converter for the given scale, or null if non-linear scaling should not be used. + */ + @Nullable + @AnyThread + static FontScaleConverter forScale(float fontScale) { + return FontScaleConverterFactory.forScale(fontScale); + } } diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java index 5d31cc0f0243..cbe4c62d7069 100644 --- a/core/java/android/content/res/FontScaleConverterFactory.java +++ b/core/java/android/content/res/FontScaleConverterFactory.java @@ -17,7 +17,6 @@ package android.content.res; import android.annotation.AnyThread; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.MathUtils; @@ -32,8 +31,9 @@ import com.android.internal.annotations.VisibleForTesting; * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do the * scaling conversion for you. But for UI frameworks or other situations where you need to do the * conversion without an Android Context, you can use this class. + * + * @hide */ -@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) public class FontScaleConverterFactory { private static final float SCALE_KEY_MULTIPLIER = 100f; @@ -124,7 +124,6 @@ public class FontScaleConverterFactory { * <p>Example usage: * <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code> */ - @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) @AnyThread public static boolean isNonLinearFontScalingActive(float fontScale) { return fontScale >= sMinScaleBeforeCurvesApplied; @@ -137,7 +136,6 @@ public class FontScaleConverterFactory { * * @return a converter for the given scale, or null if non-linear scaling should not be used. */ - @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) @Nullable @AnyThread public static FontScaleConverter forScale(float fontScale) { diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index f876eebe64c1..1165f9a697de 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -33,4 +33,11 @@ flag { name: "new_settings_ui" description: "Enables new settings UI for VIC" bug: "315209085" +} + +flag { + namespace: "credential_manager" + name: "selector_ui_improvements_enabled" + description: "Enables Credential Selector UI improvements for VIC" + bug: "319448437" }
\ No newline at end of file diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index a0f4d8df0bd5..c0424dbeb813 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -16,6 +16,7 @@ package android.hardware.biometrics; +import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG; import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; @@ -25,6 +26,7 @@ import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT; import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT; import android.annotation.CallbackExecutor; +import android.annotation.DrawableRes; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; @@ -33,6 +35,7 @@ import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.content.Context; import android.content.DialogInterface; +import android.graphics.Bitmap; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Binder; @@ -160,6 +163,45 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Optional: Sets the drawable resource of the logo that will be shown on the prompt. + * + * <p> Note that using this method is not recommended in most scenarios because the calling + * application's icon will be used by default. Setting the logo is intended for large + * bundled applications that perform a wide range of functions and need to show distinct + * icons for each function. + * + * @param logoRes A drawable resource of the logo that will be shown on the prompt. + * @return This builder. + */ + @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) + @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @NonNull + public BiometricPrompt.Builder setLogo(@DrawableRes int logoRes) { + mPromptInfo.setLogoRes(logoRes); + return this; + } + + /** + * Optional: Sets the bitmap drawable of the logo that will be shown on the prompt. + * + * <p> Note that using this method is not recommended in most scenarios because the calling + * application's icon will be used by default. Setting the logo is intended for large + * bundled applications that perform a wide range of functions and need to show distinct + * icons for each function. + * + * @param logoBitmap A bitmap drawable of the logo that will be shown on the prompt. + * @return This builder. + */ + @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) + @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @NonNull + public BiometricPrompt.Builder setLogo(@NonNull Bitmap logoBitmap) { + mPromptInfo.setLogoBitmap(logoBitmap); + return this; + } + + + /** * Required: Sets the title that will be shown on the prompt. * @param title The title to display. * @return This builder. @@ -676,6 +718,34 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Gets the drawable resource of the logo for the prompt, as set by + * {@link Builder#setLogo(int)}. Currently for system applications use only. + * + * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set. + */ + @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) + @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @DrawableRes + public int getLogoRes() { + return mPromptInfo.getLogoRes(); + } + + /** + * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogo(Bitmap)}. Currently for + * system applications use only. + * + * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set. + */ + @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) + @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @Nullable + public Bitmap getLogoBitmap() { + return mPromptInfo.getLogoBitmap(); + } + + + + /** * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}. * @return The title of the prompt, which is guaranteed to be non-null. */ diff --git a/core/java/android/hardware/biometrics/PromptContentListItem.java b/core/java/android/hardware/biometrics/PromptContentItem.java index fa3783d6d874..c47b37aca2ea 100644 --- a/core/java/android/hardware/biometrics/PromptContentListItem.java +++ b/core/java/android/hardware/biometrics/PromptContentItem.java @@ -21,9 +21,9 @@ import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT; import android.annotation.FlaggedApi; /** - * A list item shown on {@link PromptVerticalListContentView}. + * An item shown on {@link PromptContentView}. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) -public interface PromptContentListItem { +public interface PromptContentItem { } diff --git a/core/java/android/hardware/biometrics/PromptContentListItemBulletedText.java b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java index c31f8a63bcb8..c5e5a8076747 100644 --- a/core/java/android/hardware/biometrics/PromptContentListItemBulletedText.java +++ b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java @@ -27,7 +27,7 @@ import android.os.Parcelable; * A list item with bulleted text shown on {@link PromptVerticalListContentView}. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) -public final class PromptContentListItemBulletedText implements PromptContentListItemParcelable { +public final class PromptContentItemBulletedText implements PromptContentItemParcelable { private final CharSequence mText; /** @@ -35,7 +35,7 @@ public final class PromptContentListItemBulletedText implements PromptContentLis * * @param text The text of this list item. */ - public PromptContentListItemBulletedText(@NonNull CharSequence text) { + public PromptContentItemBulletedText(@NonNull CharSequence text) { mText = text; } @@ -67,15 +67,15 @@ public final class PromptContentListItemBulletedText implements PromptContentLis * @see Parcelable.Creator */ @NonNull - public static final Creator<PromptContentListItemBulletedText> CREATOR = new Creator<>() { + public static final Creator<PromptContentItemBulletedText> CREATOR = new Creator<>() { @Override - public PromptContentListItemBulletedText createFromParcel(Parcel in) { - return new PromptContentListItemBulletedText(in.readCharSequence()); + public PromptContentItemBulletedText createFromParcel(Parcel in) { + return new PromptContentItemBulletedText(in.readCharSequence()); } @Override - public PromptContentListItemBulletedText[] newArray(int size) { - return new PromptContentListItemBulletedText[size]; + public PromptContentItemBulletedText[] newArray(int size) { + return new PromptContentItemBulletedText[size]; } }; } diff --git a/core/java/android/hardware/biometrics/PromptContentListItemParcelable.java b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java index 15271f003c50..668912cfdf24 100644 --- a/core/java/android/hardware/biometrics/PromptContentListItemParcelable.java +++ b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java @@ -22,9 +22,9 @@ import android.annotation.FlaggedApi; import android.os.Parcelable; /** - * A parcelable {@link PromptContentListItem}. + * A parcelable {@link PromptContentItem}. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) -sealed interface PromptContentListItemParcelable extends PromptContentListItem, Parcelable - permits PromptContentListItemPlainText, PromptContentListItemBulletedText { +sealed interface PromptContentItemParcelable extends PromptContentItem, Parcelable + permits PromptContentItemPlainText, PromptContentItemBulletedText { } diff --git a/core/java/android/hardware/biometrics/PromptContentListItemPlainText.java b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java index d72a7586d6ad..6434c5975c12 100644 --- a/core/java/android/hardware/biometrics/PromptContentListItemPlainText.java +++ b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java @@ -27,7 +27,7 @@ import android.os.Parcelable; * A list item with plain text shown on {@link PromptVerticalListContentView}. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) -public final class PromptContentListItemPlainText implements PromptContentListItemParcelable { +public final class PromptContentItemPlainText implements PromptContentItemParcelable { private final CharSequence mText; /** @@ -35,7 +35,7 @@ public final class PromptContentListItemPlainText implements PromptContentListIt * * @param text The text of this list item. */ - public PromptContentListItemPlainText(@NonNull CharSequence text) { + public PromptContentItemPlainText(@NonNull CharSequence text) { mText = text; } @@ -67,15 +67,15 @@ public final class PromptContentListItemPlainText implements PromptContentListIt * @see Parcelable.Creator */ @NonNull - public static final Creator<PromptContentListItemPlainText> CREATOR = new Creator<>() { + public static final Creator<PromptContentItemPlainText> CREATOR = new Creator<>() { @Override - public PromptContentListItemPlainText createFromParcel(Parcel in) { - return new PromptContentListItemPlainText(in.readCharSequence()); + public PromptContentItemPlainText createFromParcel(Parcel in) { + return new PromptContentItemPlainText(in.readCharSequence()); } @Override - public PromptContentListItemPlainText[] newArray(int size) { - return new PromptContentListItemPlainText[size]; + public PromptContentItemPlainText[] newArray(int size) { + return new PromptContentItemPlainText[size]; } }; } diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index c73ebd4dbe76..d788b37c781d 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -16,8 +16,10 @@ package android.hardware.biometrics; +import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; @@ -30,6 +32,8 @@ import java.util.List; */ public class PromptInfo implements Parcelable { + @DrawableRes private int mLogoRes = -1; + @Nullable private Bitmap mLogoBitmap; @NonNull private CharSequence mTitle; private boolean mUseDefaultTitle; @Nullable private CharSequence mSubtitle; @@ -56,6 +60,8 @@ public class PromptInfo implements Parcelable { } PromptInfo(Parcel in) { + mLogoRes = in.readInt(); + mLogoBitmap = in.readTypedObject(Bitmap.CREATOR); mTitle = in.readCharSequence(); mUseDefaultTitle = in.readBoolean(); mSubtitle = in.readCharSequence(); @@ -98,6 +104,8 @@ public class PromptInfo implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mLogoRes); + dest.writeTypedObject(mLogoBitmap, 0); dest.writeCharSequence(mTitle); dest.writeBoolean(mUseDefaultTitle); dest.writeCharSequence(mSubtitle); @@ -156,9 +164,30 @@ public class PromptInfo implements Parcelable { } return false; } + + /** + * Returns whether MANAGE_BIOMETRIC_DIALOG is contained. + */ + public boolean containsManageBioApiConfigurations() { + if (mLogoRes != -1) { + return true; + } else if (mLogoBitmap != null) { + return true; + } + return false; + } // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java) // Setters + public void setLogoRes(@DrawableRes int logoRes) { + mLogoRes = logoRes; + checkOnlyOneLogoSet(); + } + + public void setLogoBitmap(@NonNull Bitmap logoBitmap) { + mLogoBitmap = logoBitmap; + checkOnlyOneLogoSet(); + } public void setTitle(CharSequence title) { mTitle = title; @@ -244,6 +273,14 @@ public class PromptInfo implements Parcelable { } // Getters + @DrawableRes + public int getLogoRes() { + return mLogoRes; + } + + public Bitmap getLogoBitmap() { + return mLogoBitmap; + } public CharSequence getTitle() { return mTitle; @@ -337,4 +374,11 @@ public class PromptInfo implements Parcelable { public boolean isShowEmergencyCallButton() { return mShowEmergencyCallButton; } + + private void checkOnlyOneLogoSet() { + if (mLogoRes != -1 && mLogoBitmap != null) { + throw new IllegalStateException( + "Exclusively one of logo resource or logo bitmap can be set"); + } + } } diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java index f3cb189a5df5..f3e62907d845 100644 --- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java +++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java @@ -40,9 +40,9 @@ import java.util.List; * .setSubTitle(...) * .setContentView(new PromptVerticalListContentView.Builder() * .setDescription("test description") - * .addListItem(new PromptContentListItemPlainText("test item 1")) - * .addListItem(new PromptContentListItemPlainText("test item 2")) - * .addListItem(new PromptContentListItemBulletedText("test item 3")) + * .addListItem(new PromptContentItemPlainText("test item 1")) + * .addListItem(new PromptContentItemPlainText("test item 2")) + * .addListItem(new PromptContentItemBulletedText("test item 3")) * .build()) * .build(); * </pre> @@ -51,11 +51,11 @@ import java.util.List; public final class PromptVerticalListContentView implements PromptContentViewParcelable { private static final int MAX_ITEM_NUMBER = 20; private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640; - private final List<PromptContentListItemParcelable> mContentList; + private final List<PromptContentItemParcelable> mContentList; private final CharSequence mDescription; private PromptVerticalListContentView( - @NonNull List<PromptContentListItemParcelable> contentList, + @NonNull List<PromptContentItemParcelable> contentList, @NonNull CharSequence description) { mContentList = contentList; mDescription = description; @@ -63,8 +63,8 @@ public final class PromptVerticalListContentView implements PromptContentViewPar private PromptVerticalListContentView(Parcel in) { mContentList = in.readArrayList( - PromptContentListItemParcelable.class.getClassLoader(), - PromptContentListItemParcelable.class); + PromptContentItemParcelable.class.getClassLoader(), + PromptContentItemParcelable.class); mDescription = in.readCharSequence(); } @@ -94,13 +94,13 @@ public final class PromptVerticalListContentView implements PromptContentViewPar } /** - * Gets the list of ListItem on the content view, as set by - * {@link PromptVerticalListContentView.Builder#addListItem(PromptContentListItem)}. + * Gets the list of items on the content view, as set by + * {@link PromptVerticalListContentView.Builder#addListItem(PromptContentItem)}. * * @return The item list on the content view. */ @NonNull - public List<PromptContentListItem> getListItems() { + public List<PromptContentItem> getListItems() { return new ArrayList<>(mContentList); } @@ -142,7 +142,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar * A builder that collects arguments to be shown on the vertical list view. */ public static final class Builder { - private final List<PromptContentListItemParcelable> mContentList = new ArrayList<>(); + private final List<PromptContentItemParcelable> mContentList = new ArrayList<>(); private CharSequence mDescription; /** @@ -159,28 +159,50 @@ public final class PromptVerticalListContentView implements PromptContentViewPar /** * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in - * total. + * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER} + * characters. * * @param listItem The list item view to display * @return This builder. */ @NonNull - public Builder addListItem(@NonNull PromptContentListItem listItem) { + public Builder addListItem(@NonNull PromptContentItem listItem) { if (doesListItemExceedsCharLimit(listItem)) { throw new IllegalStateException( "The character number of list item exceeds " + MAX_EACH_ITEM_CHARACTER_NUMBER); } - mContentList.add((PromptContentListItemParcelable) listItem); + mContentList.add((PromptContentItemParcelable) listItem); return this; } - private boolean doesListItemExceedsCharLimit(PromptContentListItem listItem) { - if (listItem instanceof PromptContentListItemPlainText) { - return ((PromptContentListItemPlainText) listItem).getText().length() + + /** + * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in + * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER} + * characters. + * + * @param listItem The list item view to display + * @param index The position at which to add the item + * @return This builder. + */ + @NonNull + public Builder addListItem(@NonNull PromptContentItem listItem, int index) { + if (doesListItemExceedsCharLimit(listItem)) { + throw new IllegalStateException( + "The character number of list item exceeds " + + MAX_EACH_ITEM_CHARACTER_NUMBER); + } + mContentList.add(index, (PromptContentItemParcelable) listItem); + return this; + } + + private boolean doesListItemExceedsCharLimit(PromptContentItem listItem) { + if (listItem instanceof PromptContentItemPlainText) { + return ((PromptContentItemPlainText) listItem).getText().length() > MAX_EACH_ITEM_CHARACTER_NUMBER; - } else if (listItem instanceof PromptContentListItemBulletedText) { - return ((PromptContentListItemBulletedText) listItem).getText().length() + } else if (listItem instanceof PromptContentItemBulletedText) { + return ((PromptContentItemBulletedText) listItem).getText().length() > MAX_EACH_ITEM_CHARACTER_NUMBER; } else { return false; diff --git a/core/java/android/hardware/radio/TEST_MAPPING b/core/java/android/hardware/radio/TEST_MAPPING new file mode 100644 index 000000000000..ee4eeb634c84 --- /dev/null +++ b/core/java/android/hardware/radio/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "frameworks/base/core/tests/BroadcastRadioTests" + } + ] +} diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 25fba60b9bb5..b9bb059bef42 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -17,9 +17,11 @@ package android.os; import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC; +import static android.os.Flags.FLAG_BATTERY_PART_STATUS_API; import android.Manifest.permission; import android.annotation.FlaggedApi; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -236,6 +238,31 @@ public class BatteryManager { public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4 + // values for "battery part status" property + /** + * Battery part status is not supported. + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int PART_STATUS_UNSUPPORTED = 0; + + /** + * Battery is the original device battery. + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int PART_STATUS_ORIGINAL = 1; + + /** + * Battery has been replaced. + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int PART_STATUS_REPLACED = 2; + /** @hide */ @SuppressLint("UnflaggedApi") // TestApi without associated feature. @TestApi @@ -366,6 +393,32 @@ public class BatteryManager { @FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; + /** + * Battery part serial number. + * + * <p class="note"> + * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission. + * + * @hide + */ + @RequiresPermission(permission.BATTERY_STATS) + @SystemApi + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11; + + /** + * Battery part status from a BATTERY_PART_STATUS_* value. + * + * <p class="note"> + * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission. + * + * @hide + */ + @RequiresPermission(permission.BATTERY_STATS) + @SystemApi + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_PROPERTY_PART_STATUS = 12; + private final Context mContext; private final IBatteryStats mBatteryStats; private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar; @@ -431,6 +484,25 @@ public class BatteryManager { } /** + * Same as queryProperty, but for strings. + */ + private String queryStringProperty(int id) { + if (mBatteryPropertiesRegistrar == null) { + return null; + } + + try { + BatteryProperty prop = new BatteryProperty(); + if (mBatteryPropertiesRegistrar.getProperty(id, prop) == 0) { + return prop.getString(); + } + return null; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Return the value of a battery property of integer type. * * @param id identifier of the requested property @@ -464,6 +536,21 @@ public class BatteryManager { } /** + * Return the value of a battery property of String type. If the + * platform does not provide the property queried, this value will + * be null. + * + * @param id identifier of the requested property. + * + * @return the property value, or null if not supported. + */ + @Nullable + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public String getStringProperty(int id) { + return queryStringProperty(id); + } + + /** * Return true if the plugType given is wired * @param plugType {@link #BATTERY_PLUGGED_AC}, {@link #BATTERY_PLUGGED_USB}, * or {@link #BATTERY_PLUGGED_WIRELESS} diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java index b40988a938bc..464577f58241 100644 --- a/core/java/android/os/BatteryProperty.java +++ b/core/java/android/os/BatteryProperty.java @@ -28,12 +28,14 @@ import android.os.Parcelable; */ public class BatteryProperty implements Parcelable { private long mValueLong; + private String mValueString; /** * @hide */ public BatteryProperty() { mValueLong = Long.MIN_VALUE; + mValueString = null; } /** @@ -46,14 +48,23 @@ public class BatteryProperty implements Parcelable { /** * @hide */ + public String getString() { + return mValueString; + } + + /** + * @hide + */ public void setLong(long val) { mValueLong = val; } - /* - * Parcel read/write code must be kept in sync with - * frameworks/native/services/batteryservice/BatteryProperty.cpp + /** + * @hide */ + public void setString(String val) { + mValueString = val; + } private BatteryProperty(Parcel p) { readFromParcel(p); @@ -61,10 +72,12 @@ public class BatteryProperty implements Parcelable { public void readFromParcel(Parcel p) { mValueLong = p.readLong(); + mValueString = p.readString8(); } public void writeToParcel(Parcel p, int flags) { p.writeLong(mValueLong); + p.writeString8(mValueString); } public static final @android.annotation.NonNull Parcelable.Creator<BatteryProperty> CREATOR diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 0db90bff48fd..82518bfbfd8d 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -106,3 +106,11 @@ flag { bug: "305311707" is_fixed_read_only: true } + +flag { + name: "battery_part_status_api" + namespace: "phoenix" + description: "Feature flag for adding Health HAL v3 APIs." + is_fixed_read_only: true + bug: "309792384" +} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index bced21730a34..db8f52c307a1 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -45,7 +45,8 @@ flag { } flag { - name: "enhanced_confirmation_mode_apis" + name: "enhanced_confirmation_mode_apis_enabled" + is_fixed_read_only: true namespace: "permissions" description: "enable enhanced confirmation mode apis" bug: "310220212" @@ -85,3 +86,10 @@ flag { description: "This flag is used to enabled the Wallet Role for all users on the device" bug: "283989236" } + +flag { + name: "runtime_permission_appops_mapping" + namespace: "permissions" + description: "Use runtime permission state to determine appop state" + bug: "266164193" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7d84bb390853..7d94dd24c05e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3176,15 +3176,7 @@ public final class Settings { } public void destroy() { - try { - // If this process is the system server process, mArray is the same object as - // the memory int array kept inside SettingsProvider, so skipping the close() - if (!Settings.isInSystemServer() && !mArray.isClosed()) { - mArray.close(); - } - } catch (IOException e) { - Log.e(TAG, "Error closing backing array", e); - } + maybeCloseGenerationArray(mArray); } @Override @@ -3197,6 +3189,21 @@ public final class Settings { } } + private static void maybeCloseGenerationArray(@Nullable MemoryIntArray array) { + if (array == null) { + return; + } + try { + // If this process is the system server process, the MemoryIntArray received from Parcel + // is the same object as the one kept inside SettingsProvider, so skipping the close(). + if (!Settings.isInSystemServer() && !array.isClosed()) { + array.close(); + } + } catch (IOException e) { + Log.e(TAG, "Error closing the generation tracking array", e); + } + } + private static final class ContentProviderHolder { private final Object mLock = new Object(); @@ -3236,9 +3243,17 @@ public final class Settings { private static final String NAME_EQ_PLACEHOLDER = "name=?"; + // Cached values of queried settings. + // Key is the setting's name, value is the setting's value. // Must synchronize on 'this' to access mValues and mValuesVersion. private final ArrayMap<String, String> mValues = new ArrayMap<>(); + // Cached values for queried prefixes. + // Key is the prefix, value is all of the settings under the prefix, mapped from a setting's + // name to a setting's value. The name string doesn't include the prefix. + // Must synchronize on 'this' to access. + private final ArrayMap<String, ArrayMap<String, String>> mPrefixToValues = new ArrayMap<>(); + private final Uri mUri; @UnsupportedAppUsage private final ContentProviderHolder mProviderHolder; @@ -3496,6 +3511,8 @@ public final class Settings { mGenerationTrackers.put(name, new GenerationTracker(name, array, index, generation, mGenerationTrackerErrorHandler)); + } else { + maybeCloseGenerationArray(array); } } if (mGenerationTrackers.get(name) != null @@ -3583,15 +3600,13 @@ public final class Settings { || applicationInfo.isSignedWithPlatformKey(); } - private ArrayMap<String, String> getStringsForPrefixStripPrefix( - ContentResolver cr, String prefix, String[] names) { + private Map<String, String> getStringsForPrefixStripPrefix( + ContentResolver cr, String prefix, List<String> names) { String namespace = prefix.substring(0, prefix.length() - 1); ArrayMap<String, String> keyValues = new ArrayMap<>(); int substringLength = prefix.length(); - int currentGeneration = -1; boolean needsGenerationTracker = false; - synchronized (NameValueCache.this) { final GenerationTracker generationTracker = mGenerationTrackers.get(prefix); if (generationTracker != null) { @@ -3605,40 +3620,22 @@ public final class Settings { // generation tracker and request a new one generationTracker.destroy(); mGenerationTrackers.remove(prefix); - for (int i = mValues.size() - 1; i >= 0; i--) { - String key = mValues.keyAt(i); - if (key.startsWith(prefix)) { - mValues.remove(key); - } - } + mPrefixToValues.remove(prefix); needsGenerationTracker = true; } else { - boolean prefixCached = mValues.containsKey(prefix); - if (prefixCached) { - if (DEBUG) { - Log.i(TAG, "Cache hit for prefix:" + prefix); - } - if (names.length > 0) { + final ArrayMap<String, String> cachedSettings = mPrefixToValues.get(prefix); + if (cachedSettings != null) { + if (!names.isEmpty()) { for (String name : names) { - // mValues can contain "null" values, need to use containsKey. - if (mValues.containsKey(name)) { + // The cache can contain "null" values, need to use containsKey. + if (cachedSettings.containsKey(name)) { keyValues.put( - name.substring(substringLength), - mValues.get(name)); + name, + cachedSettings.get(name)); } } } else { - for (int i = 0; i < mValues.size(); ++i) { - String key = mValues.keyAt(i); - // Explicitly exclude the prefix as it is only there to - // signal that the prefix has been cached. - if (key.startsWith(prefix) && !key.equals(prefix)) { - String value = mValues.valueAt(i); - keyValues.put( - key.substring(substringLength), - value); - } - } + keyValues.putAll(cachedSettings); } return keyValues; } @@ -3648,7 +3645,6 @@ public final class Settings { needsGenerationTracker = true; } } - if (mCallListCommand == null) { // No list command specified, return empty map return keyValues; @@ -3693,20 +3689,23 @@ public final class Settings { } // All flags for the namespace - Map<String, String> flagsToValues = + HashMap<String, String> flagsToValues = (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, java.util.HashMap.class); + if (flagsToValues == null) { + return keyValues; + } // Only the flags requested by the caller - if (names.length > 0) { + if (!names.isEmpty()) { for (String name : names) { // flagsToValues can contain "null" values, need to use containsKey. - if (flagsToValues.containsKey(name)) { + final String key = Config.createCompositeName(namespace, name); + if (flagsToValues.containsKey(key)) { keyValues.put( - name.substring(substringLength), - flagsToValues.get(name)); + name, + flagsToValues.get(key)); } } } else { - keyValues.ensureCapacity(keyValues.size() + flagsToValues.size()); for (Map.Entry<String, String> flag : flagsToValues.entrySet()) { keyValues.put( flag.getKey().substring(substringLength), @@ -3733,6 +3732,8 @@ public final class Settings { new GenerationTracker(prefix, array, index, generation, mGenerationTrackerErrorHandler)); currentGeneration = generation; + } else { + maybeCloseGenerationArray(array); } } if (mGenerationTrackers.get(prefix) != null && currentGeneration @@ -3740,10 +3741,18 @@ public final class Settings { if (DEBUG) { Log.i(TAG, "Updating cache for prefix:" + prefix); } - // cache the complete list of flags for the namespace - mValues.putAll(flagsToValues); - // Adding the prefix as a signal that the prefix is cached. - mValues.put(prefix, null); + // Cache the complete list of flags for the namespace for bulk queries. + // In this cached list, the setting's name doesn't include the prefix. + ArrayMap<String, String> namesToValues = + new ArrayMap<>(flagsToValues.size() + 1); + for (Map.Entry<String, String> flag : flagsToValues.entrySet()) { + namesToValues.put( + flag.getKey().substring(substringLength), + flag.getValue()); + } + // Legacy behavior, we return <"", null> when queried with name = "" + namesToValues.put("", null); + mPrefixToValues.put(prefix, namesToValues); } } return keyValues; @@ -19907,16 +19916,9 @@ public final class Settings { @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public static Map<String, String> getStrings(@NonNull ContentResolver resolver, @NonNull String namespace, @NonNull List<String> names) { - String[] compositeNames = new String[names.size()]; - for (int i = 0, size = names.size(); i < size; ++i) { - compositeNames[i] = createCompositeName(namespace, names.get(i)); - } - String prefix = createPrefix(namespace); - ArrayMap<String, String> keyValues = sNameValueCache.getStringsForPrefixStripPrefix( - resolver, prefix, compositeNames); - return keyValues; + return sNameValueCache.getStringsForPrefixStripPrefix(resolver, prefix, names); } /** @@ -20238,7 +20240,7 @@ public final class Settings { } } - private static String createCompositeName(@NonNull String namespace, @NonNull String name) { + static String createCompositeName(@NonNull String namespace, @NonNull String name) { Preconditions.checkNotNull(namespace); Preconditions.checkNotNull(name); var sb = new StringBuilder(namespace.length() + 1 + name.length()); diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING index 8b4a99e38299..d5ac7a7de461 100644 --- a/core/java/android/provider/TEST_MAPPING +++ b/core/java/android/provider/TEST_MAPPING @@ -28,5 +28,10 @@ } ] } + ], + "postsubmit": [ + { + "name": "CtsDeviceConfigTestCases" + } ] } diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 30524a1132fa..1994058441d5 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -53,7 +53,7 @@ flag { flag { name: "frp_enforcement" - namespace: "android_hw_security" + namespace: "hardware_backed_security" description: "This flag controls whether PDB enforces FRP" bug: "290312729" is_fixed_read_only: true diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java index 03ebae5c5199..90049e6a934a 100644 --- a/core/java/android/service/notification/ZenDeviceEffects.java +++ b/core/java/android/service/notification/ZenDeviceEffects.java @@ -20,7 +20,6 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.app.Flags; import android.os.Parcel; import android.os.Parcelable; @@ -37,8 +36,8 @@ import java.util.Objects; @FlaggedApi(Flags.FLAG_MODES_API) public final class ZenDeviceEffects implements Parcelable { - /** Used to track which rule variables have been modified by the user. - * Should be checked against the bitmask {@link #getUserModifiedFields()}. + /** + * Enum for the user-modifiable fields in this object. * @hide */ @IntDef(flag = true, prefix = { "FIELD_" }, value = { @@ -59,52 +58,42 @@ public final class ZenDeviceEffects implements Parcelable { /** * @hide */ - @TestApi public static final int FIELD_GRAYSCALE = 1 << 0; /** * @hide */ - @TestApi public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 1 << 1; /** * @hide */ - @TestApi public static final int FIELD_DIM_WALLPAPER = 1 << 2; /** * @hide */ - @TestApi public static final int FIELD_NIGHT_MODE = 1 << 3; /** * @hide */ - @TestApi public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 1 << 4; /** * @hide */ - @TestApi public static final int FIELD_DISABLE_TAP_TO_WAKE = 1 << 5; /** * @hide */ - @TestApi public static final int FIELD_DISABLE_TILT_TO_WAKE = 1 << 6; /** * @hide */ - @TestApi public static final int FIELD_DISABLE_TOUCH = 1 << 7; /** * @hide */ - @TestApi public static final int FIELD_MINIMIZE_RADIO_USAGE = 1 << 8; /** * @hide */ - @TestApi public static final int FIELD_MAXIMIZE_DOZE = 1 << 9; private final boolean mGrayscale; @@ -119,13 +108,10 @@ public final class ZenDeviceEffects implements Parcelable { private final boolean mMinimizeRadioUsage; private final boolean mMaximizeDoze; - private final @ModifiableField int mUserModifiedFields; // Bitwise representation - private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay, boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness, boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch, - boolean minimizeRadioUsage, boolean maximizeDoze, - @ModifiableField int userModifiedFields) { + boolean minimizeRadioUsage, boolean maximizeDoze) { mGrayscale = grayscale; mSuppressAmbientDisplay = suppressAmbientDisplay; mDimWallpaper = dimWallpaper; @@ -136,7 +122,6 @@ public final class ZenDeviceEffects implements Parcelable { mDisableTouch = disableTouch; mMinimizeRadioUsage = minimizeRadioUsage; mMaximizeDoze = maximizeDoze; - mUserModifiedFields = userModifiedFields; } @Override @@ -153,15 +138,14 @@ public final class ZenDeviceEffects implements Parcelable { && this.mDisableTiltToWake == that.mDisableTiltToWake && this.mDisableTouch == that.mDisableTouch && this.mMinimizeRadioUsage == that.mMinimizeRadioUsage - && this.mMaximizeDoze == that.mMaximizeDoze - && this.mUserModifiedFields == that.mUserModifiedFields; + && this.mMaximizeDoze == that.mMaximizeDoze; } @Override public int hashCode() { return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode, mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch, - mMinimizeRadioUsage, mMaximizeDoze, mUserModifiedFields); + mMinimizeRadioUsage, mMaximizeDoze); } @Override @@ -177,11 +161,11 @@ public final class ZenDeviceEffects implements Parcelable { if (mDisableTouch) effects.add("disableTouch"); if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage"); if (mMaximizeDoze) effects.add("maximizeDoze"); - return "[" + String.join(", ", effects) + "]" - + " userModifiedFields: " + modifiedFieldsToString(mUserModifiedFields); + return "[" + String.join(", ", effects) + "]"; } - private String modifiedFieldsToString(int bitmask) { + /** @hide */ + public static String fieldsToString(@ModifiableField int bitmask) { ArrayList<String> modified = new ArrayList<>(); if ((bitmask & FIELD_GRAYSCALE) != 0) { modified.add("FIELD_GRAYSCALE"); @@ -312,7 +296,7 @@ public final class ZenDeviceEffects implements Parcelable { return new ZenDeviceEffects(in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), - in.readBoolean(), in.readInt()); + in.readBoolean()); } @Override @@ -321,16 +305,6 @@ public final class ZenDeviceEffects implements Parcelable { } }; - /** - * Gets the bitmask representing which fields are user modified. Bits are set using - * {@link ModifiableField}. - * @hide - */ - @TestApi - public @ModifiableField int getUserModifiedFields() { - return mUserModifiedFields; - } - @Override public int describeContents() { return 0; @@ -348,7 +322,6 @@ public final class ZenDeviceEffects implements Parcelable { dest.writeBoolean(mDisableTouch); dest.writeBoolean(mMinimizeRadioUsage); dest.writeBoolean(mMaximizeDoze); - dest.writeInt(mUserModifiedFields); } /** Builder class for {@link ZenDeviceEffects} objects. */ @@ -365,7 +338,6 @@ public final class ZenDeviceEffects implements Parcelable { private boolean mDisableTouch; private boolean mMinimizeRadioUsage; private boolean mMaximizeDoze; - private @ModifiableField int mUserModifiedFields; /** * Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled). @@ -388,7 +360,6 @@ public final class ZenDeviceEffects implements Parcelable { mDisableTouch = zenDeviceEffects.shouldDisableTouch(); mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage(); mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze(); - mUserModifiedFields = zenDeviceEffects.mUserModifiedFields; } /** @@ -510,24 +481,13 @@ public final class ZenDeviceEffects implements Parcelable { return this; } - /** - * Sets the bitmask representing which fields are user modified. See the FIELD_ constants. - * @hide - */ - @TestApi - @NonNull - public Builder setUserModifiedFields(@ModifiableField int userModifiedFields) { - mUserModifiedFields = userModifiedFields; - return this; - } - /** Builds a {@link ZenDeviceEffects} object based on the builder's state. */ @NonNull public ZenDeviceEffects build() { return new ZenDeviceEffects(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode, mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch, mMinimizeRadioUsage, - mMaximizeDoze, mUserModifiedFields); + mMaximizeDoze); } } } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index d4d602da0071..c479877fe98e 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -206,7 +206,7 @@ public class ZenModeConfig implements Parcelable { private static final String ALLOW_ATT_CONV = "convos"; private static final String ALLOW_ATT_CONV_FROM = "convosFrom"; private static final String ALLOW_ATT_CHANNELS = "priorityChannels"; - private static final String USER_MODIFIED_FIELDS = "policyUserModifiedFields"; + private static final String POLICY_USER_MODIFIED_FIELDS = "policyUserModifiedFields"; private static final String DISALLOW_TAG = "disallow"; private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects"; private static final String STATE_TAG = "state"; @@ -806,6 +806,9 @@ public class ZenModeConfig implements Parcelable { rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC); rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN); rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0); + rt.zenPolicyUserModifiedFields = safeInt(parser, POLICY_USER_MODIFIED_FIELDS, 0); + rt.zenDeviceEffectsUserModifiedFields = safeInt(parser, + DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0); Long deletionInstant = tryParseLong( parser.getAttributeValue(null, RULE_ATT_DELETION_INSTANT), null); if (deletionInstant != null) { @@ -858,6 +861,9 @@ public class ZenModeConfig implements Parcelable { } out.attributeInt(null, RULE_ATT_TYPE, rule.type); out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields); + out.attributeInt(null, POLICY_USER_MODIFIED_FIELDS, rule.zenPolicyUserModifiedFields); + out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS, + rule.zenDeviceEffectsUserModifiedFields); if (rule.deletionInstant != null) { out.attributeLong(null, RULE_ATT_DELETION_INSTANT, rule.deletionInstant.toEpochMilli()); @@ -924,7 +930,6 @@ public class ZenModeConfig implements Parcelable { builder.allowPriorityChannels(channels == ZenPolicy.STATE_ALLOW); policySet = true; } - builder.setUserModifiedFields(safeInt(parser, USER_MODIFIED_FIELDS, 0)); } if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) { @@ -1037,7 +1042,6 @@ public class ZenModeConfig implements Parcelable { if (Flags.modesApi()) { writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannels(), out); - out.attributeInt(null, USER_MODIFIED_FIELDS, policy.getUserModifiedFields()); } } @@ -1083,7 +1087,6 @@ public class ZenModeConfig implements Parcelable { .setShouldMinimizeRadioUsage( safeBoolean(parser, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, false)) .setShouldMaximizeDoze(safeBoolean(parser, DEVICE_EFFECT_MAXIMIZE_DOZE, false)) - .setUserModifiedFields(safeInt(parser, DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0)) .build(); return deviceEffects.hasEffects() ? deviceEffects : null; @@ -1108,8 +1111,6 @@ public class ZenModeConfig implements Parcelable { writeBooleanIfTrue(out, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, deviceEffects.shouldMinimizeRadioUsage()); writeBooleanIfTrue(out, DEVICE_EFFECT_MAXIMIZE_DOZE, deviceEffects.shouldMaximizeDoze()); - out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS, - deviceEffects.getUserModifiedFields()); } private static void writeBooleanIfTrue(TypedXmlSerializer out, String att, boolean value) @@ -2041,7 +2042,9 @@ public class ZenModeConfig implements Parcelable { public String triggerDescription; public String iconResName; public boolean allowManualInvocation; - public int userModifiedFields; + @AutomaticZenRule.ModifiableField public int userModifiedFields; + @ZenPolicy.ModifiableField public int zenPolicyUserModifiedFields; + @ZenDeviceEffects.ModifiableField public int zenDeviceEffectsUserModifiedFields; @Nullable public Instant deletionInstant; // Only set on deleted rules. public ZenRule() { } @@ -2076,6 +2079,8 @@ public class ZenModeConfig implements Parcelable { triggerDescription = source.readString(); type = source.readInt(); userModifiedFields = source.readInt(); + zenPolicyUserModifiedFields = source.readInt(); + zenDeviceEffectsUserModifiedFields = source.readInt(); if (source.readInt() == 1) { deletionInstant = Instant.ofEpochMilli(source.readLong()); } @@ -2083,15 +2088,21 @@ public class ZenModeConfig implements Parcelable { } /** - * @see AutomaticZenRule#canUpdate() + * Whether this ZenRule can be updated by an app. In general, rules that have been + * customized by the user cannot be further updated by an app, with some exceptions: + * <ul> + * <li>Non user-configurable fields, like type, icon, configurationActivity, etc. + * <li>Name, if the name was not specifically modified by the user (to support language + * switches). + * </ul> */ @FlaggedApi(Flags.FLAG_MODES_API) public boolean canBeUpdatedByApp() { // The rule is considered updateable if its bitmask has no user modifications, and // the bitmasks of the policy and device effects have no modification. return userModifiedFields == 0 - && (zenPolicy == null || zenPolicy.getUserModifiedFields() == 0) - && (zenDeviceEffects == null || zenDeviceEffects.getUserModifiedFields() == 0); + && zenPolicyUserModifiedFields == 0 + && zenDeviceEffectsUserModifiedFields == 0; } @Override @@ -2139,6 +2150,8 @@ public class ZenModeConfig implements Parcelable { dest.writeString(triggerDescription); dest.writeInt(type); dest.writeInt(userModifiedFields); + dest.writeInt(zenPolicyUserModifiedFields); + dest.writeInt(zenDeviceEffectsUserModifiedFields); if (deletionInstant != null) { dest.writeInt(1); dest.writeLong(deletionInstant.toEpochMilli()); @@ -2173,8 +2186,20 @@ public class ZenModeConfig implements Parcelable { .append(",allowManualInvocation=").append(allowManualInvocation) .append(",iconResName=").append(iconResName) .append(",triggerDescription=").append(triggerDescription) - .append(",type=").append(type) - .append(",userModifiedFields=").append(userModifiedFields); + .append(",type=").append(type); + if (userModifiedFields != 0) { + sb.append(",userModifiedFields=") + .append(AutomaticZenRule.fieldsToString(userModifiedFields)); + } + if (zenPolicyUserModifiedFields != 0) { + sb.append(",zenPolicyUserModifiedFields=") + .append(ZenPolicy.fieldsToString(zenPolicyUserModifiedFields)); + } + if (zenDeviceEffectsUserModifiedFields != 0) { + sb.append(",zenDeviceEffectsUserModifiedFields=") + .append(ZenDeviceEffects.fieldsToString( + zenDeviceEffectsUserModifiedFields)); + } if (deletionInstant != null) { sb.append(",deletionInstant=").append(deletionInstant); } @@ -2238,6 +2263,9 @@ public class ZenModeConfig implements Parcelable { && Objects.equals(other.triggerDescription, triggerDescription) && other.type == type && other.userModifiedFields == userModifiedFields + && other.zenPolicyUserModifiedFields == zenPolicyUserModifiedFields + && other.zenDeviceEffectsUserModifiedFields + == zenDeviceEffectsUserModifiedFields && Objects.equals(other.deletionInstant, deletionInstant); } @@ -2250,7 +2278,8 @@ public class ZenModeConfig implements Parcelable { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, zenDeviceEffects, modified, allowManualInvocation, iconResName, - triggerDescription, type, userModifiedFields, deletionInstant); + triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields, + zenDeviceEffectsUserModifiedFields, deletionInstant); } return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, modified); diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index 2cda7c882cdb..fb491d010f54 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -45,8 +45,8 @@ import java.util.Objects; */ public final class ZenPolicy implements Parcelable { - /** Used to track which rule variables have been modified by the user. - * Should be checked against the bitmask {@link #getUserModifiedFields()}. + /** + * Enum for the user-modifiable fields in this object. * @hide */ @IntDef(flag = true, prefix = { "FIELD_" }, value = { @@ -76,7 +76,6 @@ public final class ZenPolicy implements Parcelable { * the same time. * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_MESSAGES = 1 << 0; /** @@ -84,7 +83,6 @@ public final class ZenPolicy implements Parcelable { * the same time. * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_CALLS = 1 << 1; /** @@ -92,13 +90,11 @@ public final class ZenPolicy implements Parcelable { * set at the same time. * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_CONVERSATIONS = 1 << 2; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_ALLOW_CHANNELS = 1 << 3; /** @@ -109,73 +105,61 @@ public final class ZenPolicy implements Parcelable { /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 1 << 5; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 6; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 1 << 7; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 1 << 8; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 1 << 9; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1 << 10; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_LIGHTS = 1 << 11; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_PEEK = 1 << 12; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 1 << 13; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_BADGE = 1 << 14; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_AMBIENT = 1 << 15; /** * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_MODES_API) public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 1 << 16; @@ -186,7 +170,6 @@ public final class ZenPolicy implements Parcelable { private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET; @FlaggedApi(Flags.FLAG_MODES_API) private @ChannelType int mAllowChannels = CHANNEL_POLICY_UNSET; - private final @ModifiableField int mUserModifiedFields; // Bitwise representation /** @hide */ @IntDef(prefix = { "PRIORITY_CATEGORY_" }, value = { @@ -388,22 +371,19 @@ public final class ZenPolicy implements Parcelable { public ZenPolicy() { mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0)); mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0)); - mUserModifiedFields = 0; } /** @hide */ @FlaggedApi(Flags.FLAG_MODES_API) public ZenPolicy(List<Integer> priorityCategories, List<Integer> visualEffects, @PeopleType int priorityMessages, @PeopleType int priorityCalls, - @ConversationSenders int conversationSenders, @ChannelType int allowChannels, - @ModifiableField int userModifiedFields) { + @ConversationSenders int conversationSenders, @ChannelType int allowChannels) { mPriorityCategories = priorityCategories; mVisualEffects = visualEffects; mPriorityMessages = priorityMessages; mPriorityCalls = priorityCalls; mConversationSenders = conversationSenders; mAllowChannels = allowChannels; - mUserModifiedFields = userModifiedFields; } /** @@ -633,8 +613,6 @@ public final class ZenPolicy implements Parcelable { * is not set, it is (@link STATE_UNSET} and will not change the current set policy. */ public static final class Builder { - private @ModifiableField int mUserModifiedFields; - private ZenPolicy mZenPolicy; public Builder() { @@ -649,9 +627,6 @@ public final class ZenPolicy implements Parcelable { public Builder(@Nullable ZenPolicy policy) { if (policy != null) { mZenPolicy = policy.copy(); - if (Flags.modesApi()) { - mUserModifiedFields = policy.mUserModifiedFields; - } } else { mZenPolicy = new ZenPolicy(); } @@ -662,11 +637,10 @@ public final class ZenPolicy implements Parcelable { */ public @NonNull ZenPolicy build() { if (Flags.modesApi()) { - return new ZenPolicy(new ArrayList<Integer>(mZenPolicy.mPriorityCategories), - new ArrayList<Integer>(mZenPolicy.mVisualEffects), + return new ZenPolicy(new ArrayList<>(mZenPolicy.mPriorityCategories), + new ArrayList<>(mZenPolicy.mVisualEffects), mZenPolicy.mPriorityMessages, mZenPolicy.mPriorityCalls, - mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels, - mUserModifiedFields); + mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels); } else { return mZenPolicy.copy(); } @@ -1025,28 +999,6 @@ public final class ZenPolicy implements Parcelable { mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE; return this; } - - /** - * Sets the user modified fields bitmask. - * @hide - */ - @TestApi - @FlaggedApi(Flags.FLAG_MODES_API) - public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) { - mUserModifiedFields = userModifiedFields; - return this; - } - } - - /** - Gets the bitmask representing which fields are user modified. Bits are set using - * {@link ModifiableField}. - * @hide - */ - @TestApi - @FlaggedApi(Flags.FLAG_MODES_API) - public @ModifiableField int getUserModifiedFields() { - return mUserModifiedFields; } @Override @@ -1063,7 +1015,6 @@ public final class ZenPolicy implements Parcelable { dest.writeInt(mConversationSenders); if (Flags.modesApi()) { dest.writeInt(mAllowChannels); - dest.writeInt(mUserModifiedFields); } } @@ -1079,7 +1030,7 @@ public final class ZenPolicy implements Parcelable { trimList(source.readArrayList(Integer.class.getClassLoader(), Integer.class), NUM_VISUAL_EFFECTS), source.readInt(), source.readInt(), source.readInt(), - source.readInt(), source.readInt() + source.readInt() ); } else { policy = new ZenPolicy(); @@ -1114,14 +1065,12 @@ public final class ZenPolicy implements Parcelable { conversationTypeToString(mConversationSenders)); if (Flags.modesApi()) { sb.append(", allowChannels=").append(channelTypeToString(mAllowChannels)); - sb.append(", userModifiedFields=") - .append(modifiedFieldsToString(mUserModifiedFields)); } return sb.append('}').toString(); } - @FlaggedApi(Flags.FLAG_MODES_API) - private String modifiedFieldsToString(@ModifiableField int bitmask) { + /** @hide */ + public static String fieldsToString(@ModifiableField int bitmask) { ArrayList<String> modified = new ArrayList<>(); if ((bitmask & FIELD_MESSAGES) != 0) { modified.add("FIELD_MESSAGES"); @@ -1332,8 +1281,7 @@ public final class ZenPolicy implements Parcelable { && other.mPriorityMessages == mPriorityMessages && other.mConversationSenders == mConversationSenders; if (Flags.modesApi()) { - return eq && other.mAllowChannels == mAllowChannels - && other.mUserModifiedFields == mUserModifiedFields; + return eq && other.mAllowChannels == mAllowChannels; } return eq; } @@ -1342,7 +1290,7 @@ public final class ZenPolicy implements Parcelable { public int hashCode() { if (Flags.modesApi()) { return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, - mPriorityMessages, mConversationSenders, mAllowChannels, mUserModifiedFields); + mPriorityMessages, mConversationSenders, mAllowChannels); } return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages, mConversationSenders); diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java index adc54f5b5a8c..f2bdbf67e76e 100644 --- a/core/java/android/service/voice/VisualQueryDetector.java +++ b/core/java/android/service/voice/VisualQueryDetector.java @@ -325,7 +325,7 @@ public class VisualQueryDetector { Slog.v(TAG, "BinderCallback#onQueryDetected"); Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { - mCallback.onQueryDetected(partialQuery); + mExecutor.execute(()->mCallback.onQueryDetected(partialQuery)); } }); } @@ -335,7 +335,7 @@ public class VisualQueryDetector { Slog.v(TAG, "BinderCallback#onQueryFinished"); Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { - mCallback.onQueryFinished(); + mExecutor.execute(()->mCallback.onQueryFinished()); } }); } @@ -345,7 +345,7 @@ public class VisualQueryDetector { Slog.v(TAG, "BinderCallback#onQueryRejected"); Binder.withCleanCallingIdentity(() -> { synchronized (mLock) { - mCallback.onQueryRejected(); + mExecutor.execute(()->mCallback.onQueryRejected()); } }); } diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index cf1156db55e5..fb57921b1529 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -1681,6 +1681,10 @@ public class PhoneStateListener { @EmergencyCallbackModeStopReason int reason) { // not support. Can't override. Use TelephonyCallback. } + + public final void onSimultaneousCallingStateChanged(int[] subIds) { + // not supported on the deprecated interface - Use TelephonyCallback instead + } } private void log(String s) { diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index 19bcf28d6b83..dc6a035a8176 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -18,6 +18,7 @@ package android.telephony; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -33,15 +34,19 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IPhoneStateListener; +import com.android.internal.telephony.flags.Flags; import dalvik.system.VMRuntime; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.Executor; +import java.util.stream.Collectors; /** * A callback class for monitoring changes in specific telephony states @@ -627,6 +632,18 @@ public class TelephonyCallback { public static final int EVENT_EMERGENCY_CALLBACK_MODE_CHANGED = 40; /** + * Event for listening to changes in simultaneous cellular calling subscriptions. + * + * @see SimultaneousCellularCallingSupportListener + * + * @hide + */ + @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @SystemApi + public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41; + + /** * @hide */ @IntDef(prefix = {"EVENT_"}, value = { @@ -669,7 +686,8 @@ public class TelephonyCallback { EVENT_LINK_CAPACITY_ESTIMATE_CHANGED, EVENT_TRIGGER_NOTIFY_ANBR, EVENT_MEDIA_QUALITY_STATUS_CHANGED, - EVENT_EMERGENCY_CALLBACK_MODE_CHANGED + EVENT_EMERGENCY_CALLBACK_MODE_CHANGED, + EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED }) @Retention(RetentionPolicy.SOURCE) public @interface TelephonyEvent { @@ -1373,6 +1391,44 @@ public class TelephonyCallback { } /** + * Interface for listening to changes in the simultaneous cellular calling state for active + * cellular subscriptions. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) + @SystemApi + public interface SimultaneousCellularCallingSupportListener { + /** + * Notify the Listener that the subscriptions available for simultaneous <b>cellular</b> + * calling have changed. + * <p> + * If we have an ongoing <b>cellular</b> call on one subscription in this Set, a + * simultaneous incoming or outgoing <b>cellular</b> call is possible on any of the + * subscriptions in this Set. On a traditional Dual Sim Dual Standby device, simultaneous + * calling is not possible between subscriptions, where on a Dual Sim Dual Active device, + * simultaneous calling may be possible between subscriptions in certain network conditions. + * <p> + * Note: This listener only tracks the capability of the modem to perform simultaneous + * cellular calls and does not track the simultaneous calling state of scenarios based on + * multiple IMS registration over multiple transports (WiFi/Internet calling). + * <p> + * Note: This listener fires for all changes to cellular calling subscriptions independent + * of which subscription it is registered on. + * + * @param simultaneousCallingSubscriptionIds The Set of subscription IDs that support + * simultaneous calling. If there is an ongoing call on a subscription in this Set, then a + * simultaneous incoming or outgoing call is only possible for other subscriptions in this + * Set. If there is an ongoing call on a subscription that is not in this Set, then + * simultaneous calling is not possible at the current time. + * + */ + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + void onSimultaneousCellularCallingSubscriptionsChanged( + @NonNull Set<Integer> simultaneousCallingSubscriptionIds); + } + + /** * Interface for call attributes listener. * * @hide @@ -1976,6 +2032,17 @@ public class TelephonyCallback { allowedNetworkType))); } + public void onSimultaneousCallingStateChanged(int[] subIds) { + SimultaneousCellularCallingSupportListener listener = + (SimultaneousCellularCallingSupportListener) mTelephonyCallbackWeakRef.get(); + if (listener == null) return; + + Binder.withCleanCallingIdentity( + () -> mExecutor.execute( + () -> listener.onSimultaneousCellularCallingSubscriptionsChanged( + Arrays.stream(subIds).boxed().collect(Collectors.toSet())))); + } + public void onLinkCapacityEstimateChanged( List<LinkCapacityEstimate> linkCapacityEstimateList) { LinkCapacityEstimateChangedListener listener = diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 886727ea43ef..0de450519646 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -994,6 +994,21 @@ public class TelephonyRegistryManager { } } + /** + * Notify external listeners that the subscriptions supporting simultaneous cellular calling + * have changed. + * @param subIds The new set of subIds supporting simultaneous cellular calling. + */ + public void notifySimultaneousCellularCallingSubscriptionsChanged(Set<Integer> subIds) { + try { + sRegistry.notifySimultaneousCellularCallingSubscriptionsChanged( + subIds.stream().mapToInt(i -> i).toArray()); + } catch (RemoteException ex) { + // system server crash + throw ex.rethrowFromSystemServer(); + } + } + public @NonNull Set<Integer> getEventsFromCallback( @NonNull TelephonyCallback telephonyCallback) { Set<Integer> eventList = new ArraySet<>(); @@ -1135,7 +1150,11 @@ public class TelephonyRegistryManager { eventList.add(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED); } - + if (telephonyCallback + instanceof TelephonyCallback.SimultaneousCellularCallingSupportListener) { + eventList.add( + TelephonyCallback.EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED); + } return eventList; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 257ecc565c87..c98d1d7ecaea 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -15015,6 +15015,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** @hide */ + @Nullable View getSelfOrParentImportantForA11y() { if (isImportantForAccessibility()) return this; ViewParent parent = getParentForAccessibility(); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index c66f3c8032fd..57174de8a62c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -26,6 +26,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -87,6 +88,7 @@ import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_B import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW; import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; +import static android.view.accessibility.Flags.fixMergedContentChangeEvent; import static android.view.accessibility.Flags.forceInvertColor; import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; @@ -1009,8 +1011,10 @@ public final class ViewRootImpl implements ViewParent, // Used to check if there were any view invalidations in // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME). private boolean mHasInvalidation = false; - // Used to check if it is in the touch boosting period. + // Used to check if it is in the frame rate boosting period. private boolean mIsFrameRateBoosting = false; + // Used to check if it is in touch boosting period. + private boolean mIsTouchBoosting = false; // Used to check if there is a message in the message queue // for idleness handling. private boolean mHasIdledMessage = false; @@ -6425,11 +6429,12 @@ public final class ViewRootImpl implements ViewParent, * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). */ mIsFrameRateBoosting = false; + mIsTouchBoosting = false; setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory, mLastPreferredFrameRateCategory)); break; case MSG_CHECK_INVALIDATION_IDLE: - if (!mHasInvalidation && !mIsFrameRateBoosting) { + if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) { mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; setPreferredFrameRateCategory(mPreferredFrameRateCategory); mHasIdledMessage = false; @@ -7452,7 +7457,7 @@ public final class ViewRootImpl implements ViewParent, // For the variable refresh rate project if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { // set the frame rate to the maximum value. - mIsFrameRateBoosting = true; + mIsTouchBoosting = true; setPreferredFrameRateCategory(mPreferredFrameRateCategory); } /** @@ -11509,6 +11514,15 @@ public final class ViewRootImpl implements ViewParent, event.setContentChangeTypes(mChangeTypes); if (mAction.isPresent()) event.setAction(mAction.getAsInt()); if (AccessibilityEvent.DEBUG_ORIGIN) event.originStackTrace = mOrigin; + + if (fixMergedContentChangeEvent()) { + if ((mChangeTypes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) { + final View importantParent = source.getSelfOrParentImportantForA11y(); + if (importantParent != null) { + source = importantParent; + } + } + } source.sendAccessibilityEventUnchecked(event); } else { mLastEventTimeMillis = 0; @@ -11543,14 +11557,29 @@ public final class ViewRootImpl implements ViewParent, } if (mSource != null) { - // If there is no common predecessor, then mSource points to - // a removed view, hence in this case always prefer the source. - View predecessor = getCommonPredecessor(mSource, source); - if (predecessor != null) { - predecessor = predecessor.getSelfOrParentImportantForA11y(); + if (fixMergedContentChangeEvent()) { + View newSource = getCommonPredecessor(mSource, source); + if (newSource == null) { + // If there is no common predecessor, then mSource points to + // a removed view, hence in this case always prefer the source. + newSource = source; + } + + mChangeTypes |= changeType; + if (mSource != newSource) { + mChangeTypes |= AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE; + mSource = newSource; + } + } else { + // If there is no common predecessor, then mSource points to + // a removed view, hence in this case always prefer the source. + View predecessor = getCommonPredecessor(mSource, source); + if (predecessor != null) { + predecessor = predecessor.getSelfOrParentImportantForA11y(); + } + mSource = (predecessor != null) ? predecessor : source; + mChangeTypes |= changeType; } - mSource = (predecessor != null) ? predecessor : source; - mChangeTypes |= changeType; final int performingAction = mAccessibilityManager.getPerformingAction(); if (performingAction != 0) { @@ -12177,8 +12206,16 @@ public final class ViewRootImpl implements ViewParent, return; } - int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning - ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; + int frameRateCategory = mIsTouchBoosting + ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory; + + // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT + // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction. + // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction + // (e.g., Window Initialization). + if (mIsFrameRateBoosting || mInsetsAnimationRunning) { + frameRateCategory = FRAME_RATE_CATEGORY_HIGH; + } try { if (mLastPreferredFrameRateCategory != frameRateCategory) { diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index c7355c144c5f..efae57c9946c 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -53,6 +53,13 @@ flag { flag { namespace: "accessibility" + name: "fix_merged_content_change_event" + description: "Fixes event type and source of content change event merged in ViewRootImpl" + bug: "277305460" +} + +flag { + namespace: "accessibility" name: "flash_notification_system_api" description: "Makes flash notification APIs as system APIs for calling from mainline module" bug: "303131332" diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index bceb8726b1cb..feae173f3e61 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -421,8 +421,11 @@ public final class TransitionInfo implements Parcelable { final String perChangeLineStart = shouldPrettyPrint ? "\n" + innerPrefix : ""; StringBuilder sb = new StringBuilder(); sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType)) - .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack) - .append(" r=["); + .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack); + if (mOptions != null) { + sb.append(" opt=").append(mOptions); + } + sb.append(" r=["); for (int i = 0; i < mRoots.size(); ++i) { if (i > 0) { sb.append(','); @@ -1211,21 +1214,31 @@ public final class TransitionInfo implements Parcelable { @NonNull private static String typeToString(int mode) { - switch(mode) { - case ANIM_CUSTOM: return "ANIM_CUSTOM"; - case ANIM_CLIP_REVEAL: return "ANIM_CLIP_REVEAL"; - case ANIM_SCALE_UP: return "ANIM_SCALE_UP"; - case ANIM_THUMBNAIL_SCALE_UP: return "ANIM_THUMBNAIL_SCALE_UP"; - case ANIM_THUMBNAIL_SCALE_DOWN: return "ANIM_THUMBNAIL_SCALE_DOWN"; - case ANIM_OPEN_CROSS_PROFILE_APPS: return "ANIM_OPEN_CROSS_PROFILE_APPS"; - default: return "<unknown:" + mode + ">"; - } + return switch (mode) { + case ANIM_CUSTOM -> "CUSTOM"; + case ANIM_SCALE_UP -> "SCALE_UP"; + case ANIM_THUMBNAIL_SCALE_UP -> "THUMBNAIL_SCALE_UP"; + case ANIM_THUMBNAIL_SCALE_DOWN -> "THUMBNAIL_SCALE_DOWN"; + case ANIM_SCENE_TRANSITION -> "SCENE_TRANSITION"; + case ANIM_CLIP_REVEAL -> "CLIP_REVEAL"; + case ANIM_OPEN_CROSS_PROFILE_APPS -> "OPEN_CROSS_PROFILE_APPS"; + case ANIM_FROM_STYLE -> "FROM_STYLE"; + default -> "<" + mode + ">"; + }; } @Override public String toString() { - return "{ AnimationOptions type= " + typeToString(mType) + " package=" + mPackageName - + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}"; + final StringBuilder sb = new StringBuilder(32); + sb.append("{t=").append(typeToString(mType)); + if (mOverrideTaskTransition) { + sb.append(" overrideTask=true"); + } + if (!mTransitionBounds.isEmpty()) { + sb.append(" bounds=").append(mTransitionBounds); + } + sb.append('}'); + return sb.toString(); } /** Customized activity transition. */ diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index bb16ad2cb8de..2b96ee6705fd 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -69,11 +69,10 @@ flag { } flag { - name: "predictive_back_system_animations" + name: "predictive_back_system_anims" namespace: "systemui" description: "Predictive back for system animations" - bug: "319421778" - is_fixed_read_only: true + bug: "320510464" } flag { diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java index c3bcfa6b2cc2..eca6f58dd50f 100644 --- a/core/java/com/android/internal/os/MonotonicClock.java +++ b/core/java/com/android/internal/os/MonotonicClock.java @@ -17,11 +17,11 @@ package com.android.internal.os; import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.AtomicFile; import android.util.Log; import android.util.Xml; -import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -49,20 +49,27 @@ public class MonotonicClock { private final AtomicFile mFile; private final Clock mClock; - private long mTimeshift; + private final long mTimeshift; public static final long UNDEFINED = -1; public MonotonicClock(File file) { - mFile = new AtomicFile(file); - mClock = Clock.SYSTEM_CLOCK; - read(); + this (file, Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK); } public MonotonicClock(long monotonicTime, @NonNull Clock clock) { + this(null, monotonicTime, clock); + } + + public MonotonicClock(@Nullable File file, long monotonicTime, @NonNull Clock clock) { mClock = clock; - mFile = null; - mTimeshift = monotonicTime - mClock.elapsedRealtime(); + if (file != null) { + mFile = new AtomicFile(file); + mTimeshift = read(monotonicTime - mClock.elapsedRealtime()); + } else { + mFile = null; + mTimeshift = monotonicTime - mClock.elapsedRealtime(); + } } /** @@ -81,15 +88,16 @@ public class MonotonicClock { return mTimeshift + elapsedRealtimeMs; } - private void read() { + private long read(long defaultTimeshift) { if (!mFile.exists()) { - return; + return defaultTimeshift; } try { - readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser()); + return readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser()); } catch (IOException e) { Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e); + return defaultTimeshift; } } @@ -102,18 +110,21 @@ public class MonotonicClock { return; } - try (FileOutputStream out = mFile.startWrite()) { + FileOutputStream out = null; + try { + out = mFile.startWrite(); writeXml(out, Xml.newBinarySerializer()); + mFile.finishWrite(out); } catch (IOException e) { Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e); + mFile.failWrite(out); } } /** * Parses an XML file containing the persistent state of the monotonic clock. */ - @VisibleForTesting - public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException { + private long readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException { long savedTimeshift = 0; try { parser.setInput(inputStream, StandardCharsets.UTF_8.name()); @@ -128,14 +139,13 @@ public class MonotonicClock { } catch (XmlPullParserException e) { throw new IOException(e); } - mTimeshift = savedTimeshift - mClock.elapsedRealtime(); + return savedTimeshift - mClock.elapsedRealtime(); } /** * Creates an XML file containing the persistent state of the monotonic clock. */ - @VisibleForTesting - public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException { + private void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException { serializer.setOutput(out, StandardCharsets.UTF_8.name()); serializer.startDocument(null, true); serializer.startTag(null, XML_TAG_MONOTONIC_TIME); diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl index 03cfd4f2ab91..969f95db002d 100644 --- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -80,4 +80,5 @@ oneway interface IPhoneStateListener { void onMediaQualityStatusChanged(in MediaQualityStatus mediaQualityStatus); void onCallBackModeStarted(int type); void onCallBackModeStopped(int type, int reason); + void onSimultaneousCallingStateChanged(in int[] subIds); } diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index aab22421b334..0203ea49f252 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -104,6 +104,7 @@ interface ITelephonyRegistry { void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType); void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId, in List<LinkCapacityEstimate> linkCapacityEstimateList); + void notifySimultaneousCellularCallingSubscriptionsChanged(in int[] subIds); void addCarrierPrivilegesCallback( int phoneId, ICarrierPrivilegesCallback callback, String pkg, String featureId); diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java index 04886598de35..342ba1b67e4d 100644 --- a/core/java/com/android/internal/util/RingBuffer.java +++ b/core/java/com/android/internal/util/RingBuffer.java @@ -19,7 +19,10 @@ package com.android.internal.util; import static com.android.internal.util.Preconditions.checkArgumentPositive; import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; import java.util.Arrays; +import java.util.function.IntFunction; +import java.util.function.Supplier; /** * A simple ring buffer structure with bounded capacity backed by an array. @@ -30,16 +33,35 @@ import java.util.Arrays; @android.ravenwood.annotation.RavenwoodKeepWholeClass public class RingBuffer<T> { + private final Supplier<T> mNewItem; // Array for storing events. private final T[] mBuffer; // Cursor keeping track of the logical end of the array. This cursor never // wraps and instead keeps track of the total number of append() operations. private long mCursor = 0; + /** + * @deprecated This uses reflection to create new instances. + * Use {@link #RingBuffer(Supplier, IntFunction, int)}} instead. + */ + @Deprecated public RingBuffer(Class<T> c, int capacity) { + this(() -> (T) createNewItem(c), cap -> (T[]) Array.newInstance(c, cap), capacity); + } + + private static Object createNewItem(Class c) { + try { + return c.getDeclaredConstructor().newInstance(); + } catch (IllegalAccessException | InstantiationException | NoSuchMethodException + | InvocationTargetException e) { + return null; + } + } + + public RingBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) { checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity"); - // Java cannot create generic arrays without a runtime hint. - mBuffer = (T[]) Array.newInstance(c, capacity); + mBuffer = newBacking.apply(capacity); + mNewItem = newItem; } public int size() { @@ -69,22 +91,11 @@ public class RingBuffer<T> { public T getNextSlot() { final int nextSlotIdx = indexOf(mCursor++); if (mBuffer[nextSlotIdx] == null) { - mBuffer[nextSlotIdx] = createNewItem(); + mBuffer[nextSlotIdx] = mNewItem.get(); } return mBuffer[nextSlotIdx]; } - /** - * @return a new object of type <T> or null if a new object could not be created. - */ - protected T createNewItem() { - try { - return (T) mBuffer.getClass().getComponentType().newInstance(); - } catch (IllegalAccessException | InstantiationException e) { - return null; - } - } - public T[] toArray() { // Only generic way to create a T[] from another T[] T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass()); diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp index 5b95ee79f330..f6fe3dd842da 100644 --- a/core/jni/android_hardware_OverlayProperties.cpp +++ b/core/jni/android_hardware_OverlayProperties.cpp @@ -61,13 +61,12 @@ static jboolean android_hardware_OverlayProperties_supportFp16ForHdr(JNIEnv* env static_cast<int32_t>(HAL_PIXEL_FORMAT_RGBA_FP16)) != i.pixelFormats.end() && std::find(i.standards.begin(), i.standards.end(), - static_cast<int32_t>(HAL_DATASPACE_STANDARD_BT2020)) != + static_cast<int32_t>(HAL_DATASPACE_STANDARD_BT709)) != i.standards.end() && std::find(i.transfers.begin(), i.transfers.end(), - static_cast<int32_t>(HAL_DATASPACE_TRANSFER_ST2084)) != - i.transfers.end() && + static_cast<int32_t>(HAL_DATASPACE_TRANSFER_SRGB)) != i.transfers.end() && std::find(i.ranges.begin(), i.ranges.end(), - static_cast<int32_t>(HAL_DATASPACE_RANGE_FULL)) != i.ranges.end()) { + static_cast<int32_t>(HAL_DATASPACE_RANGE_EXTENDED)) != i.ranges.end()) { return true; } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5f3f6419418a..4a82675715d8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7896,6 +7896,18 @@ <permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" android:protectionLevel="signature|role" /> + <!-- @SystemApi + @FlaggedApi("android.app.bic_client") + Allows app to call BackgroundInstallControlManager API to retrieve silently installed apps + for all users on device. + <p>Apps with a BackgroundInstallControlManager client will not be able to call any API without + this permission. + <p>Protection level: signature|role + @hide + --> + <permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" + android:protectionLevel="signature|role" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml index f3acab063bbf..fe12f6ee7836 100644 --- a/core/res/res/drawable-nodpi/platlogo.xml +++ b/core/res/res/drawable-nodpi/platlogo.xml @@ -1,5 +1,5 @@ <!-- -Copyright (C) 2021 The Android Open Source Project +Copyright (C) 2024 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,179 +20,103 @@ Copyright (C) 2021 The Android Open Source Project android:viewportWidth="512" android:viewportHeight="512"> <path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"> + android:pathData="M256.22,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98s-24.52,-54.37 -11.33,-77.21c13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21s-103.26,178.86 -120.65,208.98c-17.39,30.12 -34.83,48.42 -61.2,48.42Z" + android:strokeWidth="0"> <aapt:attr name="android:fillColor"> <gradient - android:startX="256" - android:startY="21.81" - android:endX="256" - android:endY="350.42" + android:startX="56.22" + android:startY="256" + android:endX="456.22" + android:endY="256" android:type="linear"> <item android:offset="0" android:color="#FF073042"/> <item android:offset="1" android:color="#FF073042"/> </gradient> </aapt:attr> </path> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z" - android:fillColor="#3ddc84"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z" - android:fillColor="#3ddc84"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z" - android:fillColor="#3ddc84"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M171.92,216.82h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M188.8,275.73h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M369.04,337.63h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M285.93,252.22h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M318.96,218.84h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M294.05,288.55h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M330.82,273.31h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M188.8,298.95h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M220.14,238.94h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M272.1,318.9h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M293.34,349.25h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M161.05,254.24h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M378.92,192h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M137.87,323.7h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0" - android:strokeWidth="56.561" - android:fillColor="#00000000" - android:strokeColor="#f86734"/> <path - android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z" + android:pathData="M198.85,168.57l2.03,-6.03c12.55,70.48 45.87,256.56 45.87,45.41 0,-0.88 0.65,-1.63 1.46,-1.63h11.45c0.81,0 1.46,0.75 1.46,1.63 -0.18,369.8 -62.28,-39.37 -62.28,-39.37Z" + android:strokeWidth="0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M186.69,167.97l-23.69,41.03c-1.36,2.36 -0.55,5.37 1.8,6.73 2.36,1.36 5.37,0.55 6.73,-1.8l23.99,-41.55c38.76,17.38 83.1,17.38 121.86,0l23.99,41.55c1.41,2.33 4.44,3.07 6.77,1.66 2.26,-1.37 3.04,-4.28 1.76,-6.59l-23.69,-41.03c40.68,-22.12 68.5,-63.31 72.57,-111.97H114.11c4.07,48.65 31.89,89.85 72.57,111.97Z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M248.46,168.59L259.2,168.59A1.92,1.92 0,0 1,261.12 170.5L261.12,195.83A1.92,1.92 0,0 1,259.2 197.75L248.46,197.75A1.92,1.92 0,0 1,246.54 195.83L246.54,170.5A1.92,1.92 0,0 1,248.46 168.59z" + android:strokeWidth="0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M248.32,152.92L259.34,152.92A1.78,1.78 0,0 1,261.12 154.7L261.12,158.43A1.78,1.78 0,0 1,259.34 160.21L248.32,160.21A1.78,1.78 0,0 1,246.54 158.43L246.54,154.7A1.78,1.78 0,0 1,248.32 152.92z" + android:strokeWidth="0" android:fillColor="#3ddc84"/> <path - android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z" + android:pathData="M159.03,176.91h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M188.8,275.73h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M373.41,158.93h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M112.1,129.34h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M285.93,252.22h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M318.96,218.84h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M294.05,288.55h4.04v4.04h-4.04z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z" + android:pathData="M330.82,263.31h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z" + android:pathData="M188.8,298.95h4.04v4.04h-4.04z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z" + android:pathData="M224.18,216.82h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z" + android:pathData="M272.1,318.9h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z" + android:pathData="M293.34,339.25h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z" + android:pathData="M165.09,236.28h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z" + android:pathData="M378.92,192h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z" + android:pathData="M204.28,314.86h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> + <path + android:pathData="M253.83,118.47c-6.04,0 -10.93,4.76 -10.93,10.62v13.1c0,1.13 0.92,2.05 2.05,2.05h0c1.13,0 2.05,-0.92 2.05,-2.05v-3.53c0,-2.27 1.84,-4.1 4.1,-4.1h5.47c2.27,0 4.1,1.84 4.1,4.1v3.53c0,1.13 0.92,2.05 2.05,2.05s2.05,-0.92 2.05,-2.05v-13.1c0,-5.86 -4.9,-10.62 -10.93,-10.62Z" + android:strokeWidth="0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M256,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98 -17.39,-30.12 -24.52,-54.37 -11.33,-77.21 13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21 -17.39,30.12 -103.26,178.86 -120.65,208.98 -17.39,30.12 -34.83,48.42 -61.2,48.42Z" + android:strokeWidth="55" + android:fillColor="#00000000" + android:strokeColor="#f86733"/> </vector> - diff --git a/core/tests/BroadcastRadioTests/TEST_MAPPING b/core/tests/BroadcastRadioTests/TEST_MAPPING new file mode 100644 index 000000000000..b085a27b25b8 --- /dev/null +++ b/core/tests/BroadcastRadioTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "BroadcastRadioTests" + } + ] +} diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java index 9d85b65d4dac..1925588e8904 100644 --- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java +++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java @@ -16,8 +16,6 @@ package android.app; -import static com.google.common.truth.Truth.assertThat; - import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; @@ -28,8 +26,6 @@ import android.net.Uri; import android.os.Parcel; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; -import android.service.notification.ZenDeviceEffects; -import android.service.notification.ZenPolicy; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -230,66 +226,4 @@ public class AutomaticZenRuleTest { assertThrows(IllegalArgumentException.class, () -> builder.setType(100)); } - - @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void testCanUpdate_nullPolicyAndDeviceEffects() { - AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name", - Uri.parse("uri://short")); - - AutomaticZenRule rule = builder.setUserModifiedFields(0) - .setZenPolicy(null) - .setDeviceEffects(null) - .build(); - - assertThat(rule.canUpdate()).isTrue(); - - rule = builder.setUserModifiedFields(1).build(); - assertThat(rule.canUpdate()).isFalse(); - } - - @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void testCanUpdate_policyModified() { - ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0); - ZenPolicy policy = policyBuilder.build(); - - AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name", - Uri.parse("uri://short")); - AutomaticZenRule rule = builder.setUserModifiedFields(0) - .setZenPolicy(policy) - .setDeviceEffects(null).build(); - - // Newly created ZenPolicy is not user modified. - assertThat(policy.getUserModifiedFields()).isEqualTo(0); - assertThat(rule.canUpdate()).isTrue(); - - policy = policyBuilder.setUserModifiedFields(1).build(); - assertThat(policy.getUserModifiedFields()).isEqualTo(1); - rule = builder.setZenPolicy(policy).build(); - assertThat(rule.canUpdate()).isFalse(); - } - - @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void testCanUpdate_deviceEffectsModified() { - ZenDeviceEffects.Builder deviceEffectsBuilder = - new ZenDeviceEffects.Builder().setUserModifiedFields(0); - ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build(); - - AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name", - Uri.parse("uri://short")); - AutomaticZenRule rule = builder.setUserModifiedFields(0) - .setZenPolicy(null) - .setDeviceEffects(deviceEffects).build(); - - // Newly created ZenDeviceEffects is not user modified. - assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(0); - assertThat(rule.canUpdate()).isTrue(); - - deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build(); - assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1); - rule = builder.setDeviceEffects(deviceEffects).build(); - assertThat(rule.canUpdate()).isFalse(); - } } diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt index 1617eda6a77c..e32a57b1aefe 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt @@ -48,7 +48,7 @@ class FontScaleConverterFactoryTest { @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() - private lateinit var defaultLookupTables: SparseArray<FontScaleConverter> + private var defaultLookupTables: SparseArray<FontScaleConverter>? = null @Before fun setup() { @@ -58,7 +58,9 @@ class FontScaleConverterFactoryTest { @After fun teardown() { // Restore the default tables (since some tests will have added extras to the cache) - FontScaleConverterFactory.sLookupTables = defaultLookupTables + if (defaultLookupTables != null) { + FontScaleConverterFactory.sLookupTables = defaultLookupTables!! + } } @Test diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index cf3eb12498ca..cfbda84f24e1 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -19,6 +19,7 @@ package android.view; import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR; import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; @@ -575,8 +576,13 @@ public class ViewRootImplTest { assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_HIGH_HINT); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java index 0742052cce53..ec4c563e469e 100644 --- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java +++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java @@ -18,39 +18,67 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; -import android.util.Xml; - import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; import java.io.IOException; +import java.nio.file.Files; @RunWith(AndroidJUnit4.class) @SmallTest public class MonotonicClockTest { private final MockClock mClock = new MockClock(); + private File mFile; + + @Before + public void setup() throws IOException { + File systemDir = Files.createTempDirectory("MonotonicClockTest").toFile(); + mFile = new File(systemDir, "test_monotonic_clock.xml"); + if (mFile.exists()) { + assertThat(mFile.delete()).isTrue(); + } + } @Test public void persistence() throws IOException { - MonotonicClock monotonicClock = new MonotonicClock(1000, mClock); + MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock); mClock.realtime = 234; assertThat(monotonicClock.monotonicTime()).isEqualTo(1234); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - monotonicClock.writeXml(out, Xml.newBinarySerializer()); + monotonicClock.write(); mClock.realtime = 42; - MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - newMonotonicClock.readXml(in, Xml.newBinaryPullParser()); + MonotonicClock newMonotonicClock = new MonotonicClock(mFile, 0, mClock); mClock.realtime = 2000; assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000); } + + @Test + public void constructor() { + MonotonicClock monotonicClock = new MonotonicClock(null, 1000, mClock); + mClock.realtime = 234; + + assertThat(monotonicClock.monotonicTime()).isEqualTo(1234); + } + + @Test + public void corruptedFile() throws IOException { + // Create an invalid binary XML file to cause IOException: "Unexpected magic number" + try (FileWriter w = new FileWriter(mFile)) { + w.write("garbage"); + } + + MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock); + mClock.realtime = 234; + + assertThat(monotonicClock.monotonicTime()).isEqualTo(1234); + } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index a1ea2b8ce032..4be75f83422e 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -426,6 +426,7 @@ applications that come with the platform <!-- Permissions required for CTS test - android.server.biometrics --> <permission name="android.permission.USE_BIOMETRIC" /> <permission name="android.permission.TEST_BIOMETRIC" /> + <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" /> <!-- Permissions required for CTS test - CtsContactsProviderTestCases --> <permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" /> <!-- Permissions required for CTS test - CtsHdmiCecHostTestCases --> @@ -541,6 +542,8 @@ applications that come with the platform <permission name="android.permission.GET_BINDING_UID_IMPORTANCE"/> <!-- Permission required for CTS test NotificationManagerZenTest --> <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> + <!-- Permission required for BinaryTransparencyService shell API and host test --> + <permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index ddae673e1084..b21bf11088e2 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -23,9 +23,7 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.compat.CompatChanges; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledSince; +import android.app.ActivityThread; import android.os.Build; import android.os.LocaleList; import android.os.Parcel; @@ -43,15 +41,6 @@ import java.util.Objects; * line-break property</a> for more information. */ public final class LineBreakConfig implements Parcelable { - - /** - * A feature ID for automatic line break word style. - * @hide - */ - @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) - public static final long WORD_STYLE_AUTO = 280005585L; - /** * No hyphenation preference is specified. * @@ -487,8 +476,15 @@ public final class LineBreakConfig implements Parcelable { * @hide */ public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) { - final int defaultStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO) - ? LINE_BREAK_STYLE_AUTO : LINE_BREAK_STYLE_NONE; + final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo() + .targetSdkVersion; + final int defaultStyle; + final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM; + if (targetSdkVersion >= vicVersion) { + defaultStyle = LINE_BREAK_STYLE_AUTO; + } else { + defaultStyle = LINE_BREAK_STYLE_NONE; + } if (config == null) { return defaultStyle; } @@ -515,8 +511,15 @@ public final class LineBreakConfig implements Parcelable { */ public static @LineBreakWordStyle int getResolvedLineBreakWordStyle( @Nullable LineBreakConfig config) { - final int defaultWordStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO) - ? LINE_BREAK_WORD_STYLE_AUTO : LINE_BREAK_WORD_STYLE_NONE; + final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo() + .targetSdkVersion; + final int defaultWordStyle; + final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM; + if (targetSdkVersion >= vicVersion) { + defaultWordStyle = LINE_BREAK_WORD_STYLE_AUTO; + } else { + defaultWordStyle = LINE_BREAK_WORD_STYLE_NONE; + } if (config == null) { return defaultWordStyle; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index bb433dbbd2ce..e7f6f0d61847 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -17,7 +17,7 @@ package com.android.wm.shell.back; import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME; -import static com.android.window.flags.Flags.predictiveBackSystemAnimations; +import static com.android.window.flags.Flags.predictiveBackSystemAnims; import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; @@ -244,7 +244,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void setupAnimationDeveloperSettingsObserver( @NonNull ContentResolver contentResolver, @NonNull @ShellBackgroundThread final Handler backgroundHandler) { - if (predictiveBackSystemAnimations()) { + if (predictiveBackSystemAnims()) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore " + "developer settings flag is ignored and no content observer registered"); return; @@ -267,7 +267,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ @ShellBackgroundThread private void updateEnableAnimationFromFlags() { - boolean isEnabled = predictiveBackSystemAnimations() || isDeveloperSettingEnabled(); + boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled(); mEnableAnimations.set(isEnabled); ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index e48732801094..bb0dd95b042f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -769,8 +769,10 @@ public class StackAnimationController extends boolean swapped = false; for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) { View view = bubbleViews.get(newIndex); - final int oldIndex = mLayout.indexOfChild(view); - swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after); + if (view != null) { + final int oldIndex = mLayout.indexOfChild(view); + swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after); + } } if (!swapped) { // All bubbles were at the right position. Make sure badges and z order is correct. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 12114519d086..bd8ce803c591 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -26,6 +26,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; +import android.view.Gravity; import android.view.TouchDelegate; import android.view.View; import android.view.ViewTreeObserver; @@ -74,10 +75,6 @@ public class BubbleBarLayerView extends FrameLayout private DismissView mDismissView; private @Nullable Consumer<String> mUnBubbleConversationCallback; - // TODO(b/273310265) - currently the view is always on the right, need to update for RTL. - /** Whether the expanded view is displaying on the left of the screen or not. */ - private boolean mOnLeft = false; - /** Whether a bubble is expanded. */ private boolean mIsExpanded = false; @@ -154,10 +151,10 @@ public class BubbleBarLayerView extends FrameLayout return mIsExpanded; } - // (TODO: b/273310265): BubblePositioner should be source of truth when this work is done. + // TODO(b/313661121) - when dragging is implemented, check user setting first /** Whether the expanded view is positioned on the left or right side of the screen. */ public boolean isOnLeft() { - return mOnLeft; + return getLayoutDirection() == LAYOUT_DIRECTION_RTL; } /** Shows the expanded view of the provided bubble. */ @@ -216,7 +213,7 @@ public class BubbleBarLayerView extends FrameLayout return Unit.INSTANCE; }); - addView(mExpandedView, new FrameLayout.LayoutParams(width, height)); + addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT)); } if (mEducationViewController.isEducationVisible()) { @@ -311,7 +308,7 @@ public class BubbleBarLayerView extends FrameLayout lp.width = width; lp.height = height; mExpandedView.setLayoutParams(lp); - if (mOnLeft) { + if (isOnLeft()) { mExpandedView.setX(mPositioner.getInsets().left + padding); } else { mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 1a793a16f254..b2eeea7048bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -271,6 +271,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { if (e.findPointerIndex(mDragPointerId) == -1) { mDragPointerId = e.getPointerId(0); } + final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + // If a decor's resize drag zone is active, don't also try to reposition it. + if (decoration.isHandlingDragResize()) break; final int dragPointerIdx = e.findPointerIndex(mDragPointerId); mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 1debb02e86af..5a74255df49a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -286,6 +286,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL closeBackground.setTintList(buttonTintColor); } + boolean isHandlingDragResize() { + return mDragResizeListener.isHandlingDragResize(); + } + private void closeDragResizeListener() { if (mDragResizeListener == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index aabc1cfb5875..554b1fb99550 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -491,8 +491,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return true; } case MotionEvent.ACTION_MOVE: { + mShouldClick = false; final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + // If a decor's resize drag zone is active, don't also try to reposition it. + if (decoration.isHandlingDragResize()) break; decoration.closeMaximizeMenu(); if (e.findPointerIndex(mDragPointerId) == -1) { mDragPointerId = e.getPointerId(0); @@ -505,7 +508,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { e.getRawX(dragPointerIdx), newTaskBounds)); mIsDragging = true; - mShouldClick = false; return true; } case MotionEvent.ACTION_UP: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 53f806ccabee..20233331997f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -387,6 +387,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return mHandleMenu != null; } + boolean isHandlingDragResize() { + return mDragResizeListener.isHandlingDragResize(); + } + private void loadAppInfo() { String packageName = mTaskInfo.realActivity.getPackageName(); PackageManager pm = mContext.getApplicationContext().getPackageManager(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 8511a21d4294..8b38f991a2db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -320,6 +320,10 @@ class DragResizeInputListener implements AutoCloseable { } } + boolean isHandlingDragResize() { + return mInputEventReceiver.isHandlingEvents(); + } + @Override public void close() { mInputEventReceiver.dispose(); @@ -386,6 +390,10 @@ class DragResizeInputListener implements AutoCloseable { finishInputEvent(inputEvent, handleInputEvent(inputEvent)); } + boolean isHandlingEvents() { + return mShouldHandleEvents; + } + private boolean handleInputEvent(InputEvent inputEvent) { if (!(inputEvent instanceof MotionEvent)) { return false; @@ -409,7 +417,6 @@ class DragResizeInputListener implements AutoCloseable { mShouldHandleEvents = isInResizeHandleBounds(x, y); } if (mShouldHandleEvents) { - mInputManager.pilferPointers(mInputChannel.getToken()); mDragPointerId = e.getPointerId(0); float rawX = e.getRawX(0); float rawY = e.getRawY(0); @@ -427,6 +434,7 @@ class DragResizeInputListener implements AutoCloseable { if (!mShouldHandleEvents) { break; } + mInputManager.pilferPointers(mInputChannel.getToken()); int dragPointerIndex = e.findPointerIndex(mDragPointerId); float rawX = e.getRawX(dragPointerIndex); float rawY = e.getRawY(dragPointerIndex); @@ -437,6 +445,7 @@ class DragResizeInputListener implements AutoCloseable { } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { + mInputManager.pilferPointers(mInputChannel.getToken()); if (mShouldHandleEvents) { int dragPointerIndex = e.findPointerIndex(mDragPointerId); final Rect taskBounds = mCallback.onDragPositioningEnd( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index c1b18f959641..7c6e69eb1ec9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -61,6 +61,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, private int mCtrlType; private boolean mIsResizingOrAnimatingResize; @Surface.Rotation private int mRotation; + private boolean mVeilIsVisible; public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, DesktopModeWindowDecoration windowDecoration, @@ -94,7 +95,6 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds()); mRepositionStartPoint.set(x, y); if (isResizing()) { - mDesktopWindowDecoration.showResizeVeil(mTaskBoundsAtDragStart); if (!mDesktopWindowDecoration.mTaskInfo.isFocused) { WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true); @@ -119,8 +119,13 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType, mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, mDisplayController, mDesktopWindowDecoration)) { - mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds); mIsResizingOrAnimatingResize = true; + if (!mVeilIsVisible) { + mDesktopWindowDecoration.showResizeVeil(mRepositionTaskBounds); + mVeilIsVisible = true; + } else { + mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds); + } } else if (mCtrlType == CTRL_TYPE_UNDEFINED) { final SurfaceControl.Transaction t = mTransactionSupplier.get(); DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration, @@ -143,7 +148,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds); mTransitions.startTransition(TRANSIT_CHANGE, wct, this); - } else { + } else if (mVeilIsVisible) { // If bounds haven't changed, perform necessary veil reset here as startAnimation // won't be called. mDesktopWindowDecoration.hideResizeVeil(); @@ -163,6 +168,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, mCtrlType = CTRL_TYPE_UNDEFINED; mTaskBoundsAtDragStart.setEmpty(); mRepositionStartPoint.set(0, 0); + mVeilIsVisible = false; return new Rect(mRepositionTaskBounds); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index 08412101c30c..86253f35a51d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -144,13 +144,13 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_noMove_showsResizeVeil() { + fun testDragResize_noMove_doesNotShowResizeVeil() { taskPositioner.onDragPositioningStart( CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat() ) - verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS) + verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS) taskPositioner.onDragPositioningEnd( STARTING_BOUNDS.left.toFloat(), @@ -162,7 +162,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && change.configuration.windowConfiguration.bounds == STARTING_BOUNDS}}, eq(taskPositioner)) - verify(mockDesktopWindowDecoration).hideResizeVeil() + verify(mockDesktopWindowDecoration, never()).hideResizeVeil() } @Test @@ -212,7 +212,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat() ) - verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS) taskPositioner.onDragPositioningMove( STARTING_BOUNDS.right.toFloat() + 10, @@ -222,6 +221,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { val rectAfterMove = Rect(STARTING_BOUNDS) rectAfterMove.right += 10 rectAfterMove.top += 10 + verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove) verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> return@argThat wct.changes.any { (token, change) -> token == taskBinder && @@ -237,7 +237,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { val rectAfterEnd = Rect(rectAfterMove) rectAfterEnd.right += 10 rectAfterEnd.top += 10 - verify(mockDesktopWindowDecoration, times(2)).updateResizeVeil(any()) + verify(mockDesktopWindowDecoration).updateResizeVeil(any()) verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct -> return@argThat wct.changes.any { (token, change) -> token == taskBinder && @@ -253,7 +253,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat() ) - verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS) taskPositioner.onDragPositioningMove( STARTING_BOUNDS.left.toFloat(), diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index eebf8aabd89c..b40b73c111d0 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -716,7 +716,6 @@ cc_test { ], shared_libs: [ "libmemunreachable", - "server_configurable_flags", ], srcs: [ "tests/unit/main.cpp", diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index d4af0872e31e..a5a841e07d7a 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -23,6 +23,7 @@ #include <mutex> +#include "Properties.h" #include "utils/Macros.h" namespace android { @@ -60,7 +61,13 @@ public: static void setWideColorDataspace(ADataSpace dataspace); static void setSupportFp16ForHdr(bool supportFp16ForHdr); - static bool isSupportFp16ForHdr() { return get()->mSupportFp16ForHdr; }; + static bool isSupportFp16ForHdr() { + if (!Properties::hdr10bitPlus) { + return false; + } + + return get()->mSupportFp16ForHdr; + }; static void setSupportMixedColorSpaces(bool supportMixedColorSpaces); static bool isSupportMixedColorSpaces() { return get()->mSupportMixedColorSpaces; }; diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index d58c872dbc56..755332ff66fd 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -38,6 +38,9 @@ namespace hwui_flags { constexpr bool clip_surfaceviews() { return false; } +constexpr bool hdr_10bit_plus() { + return false; +} } // namespace hwui_flags #endif @@ -105,6 +108,7 @@ bool Properties::isSystemOrPersistent = false; float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number bool Properties::clipSurfaceViews = false; +bool Properties::hdr10bitPlus = false; StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI; @@ -177,6 +181,7 @@ bool Properties::load() { clipSurfaceViews = base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews()); + hdr10bitPlus = hwui_flags::hdr_10bit_plus(); return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw); } diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index b956facf6f90..ec53070f6cb8 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -336,6 +336,7 @@ public: static float maxHdrHeadroomOn8bit; static bool clipSurfaceViews; + static bool hdr10bitPlus; static StretchEffectBehavior getStretchEffectBehavior() { return stretchEffectBehavior; diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index e0f1f6ef44be..326b6ed77fe0 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -650,9 +650,14 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace(); break; case ColorMode::Hdr: - mSurfaceColorType = SkColorType::kN32_SkColorType; - mSurfaceColorSpace = SkColorSpace::MakeRGB( - GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3); + if (DeviceInfo::get()->isSupportFp16ForHdr()) { + mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType; + mSurfaceColorSpace = SkColorSpace::MakeSRGB(); + } else { + mSurfaceColorType = SkColorType::kN32_SkColorType; + mSurfaceColorSpace = SkColorSpace::MakeRGB( + GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3); + } break; case ColorMode::Hdr10: mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType; @@ -669,8 +674,13 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) { void SkiaPipeline::setTargetSdrHdrRatio(float ratio) { if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) { mTargetSdrHdrRatio = ratio; - mSurfaceColorSpace = SkColorSpace::MakeRGB(GetExtendedTransferFunction(mTargetSdrHdrRatio), - SkNamedGamut::kDisplayP3); + + if (mColorMode == ColorMode::Hdr && DeviceInfo::get()->isSupportFp16ForHdr()) { + mSurfaceColorSpace = SkColorSpace::MakeSRGB(); + } else { + mSurfaceColorSpace = SkColorSpace::MakeRGB( + GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3); + } } else { mTargetSdrHdrRatio = 1.f; } diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index eb4d4948dc53..ac2a9366a1f6 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -124,24 +124,19 @@ void CacheManager::trimMemory(TrimLevel mode) { // flush and submit all work to the gpu and wait for it to finish mGrContext->flushAndSubmit(GrSyncCpu::kYes); - switch (mode) { - case TrimLevel::BACKGROUND: - mGrContext->freeGpuResources(); - SkGraphics::PurgeAllCaches(); - mRenderThread.destroyRenderingContext(); - break; - case TrimLevel::UI_HIDDEN: - // Here we purge all the unlocked scratch resources and then toggle the resources cache - // limits between the background and max amounts. This causes the unlocked resources - // that have persistent data to be purged in LRU order. - mGrContext->setResourceCacheLimit(mBackgroundResourceBytes); - SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes); - mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly)); - mGrContext->setResourceCacheLimit(mMaxResourceBytes); - SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes); - break; - default: - break; + if (mode >= TrimLevel::BACKGROUND) { + mGrContext->freeGpuResources(); + SkGraphics::PurgeAllCaches(); + mRenderThread.destroyRenderingContext(); + } else if (mode == TrimLevel::UI_HIDDEN) { + // Here we purge all the unlocked scratch resources and then toggle the resources cache + // limits between the background and max amounts. This causes the unlocked resources + // that have persistent data to be purged in LRU order. + mGrContext->setResourceCacheLimit(mBackgroundResourceBytes); + SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes); + mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly)); + mGrContext->setResourceCacheLimit(mMaxResourceBytes); + SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes); } } diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index facf30b83b07..2904dfe76f40 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -441,22 +441,32 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, colorMode = ColorMode::Default; } - if (DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType) { + // TODO: maybe we want to get rid of the WCG check if overlay properties just works? + const bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() || + DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType; + + if (canUseFp16) { if (mEglConfigF16 == EGL_NO_CONFIG_KHR) { colorMode = ColorMode::Default; } else { config = mEglConfigF16; } } + if (EglExtensions.glColorSpace) { attribs[0] = EGL_GL_COLORSPACE_KHR; switch (colorMode) { case ColorMode::Default: attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; break; + case ColorMode::Hdr: + if (canUseFp16) { + attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT; + break; + // No fp16 support so fallthrough to HDR10 + } // We don't have an EGL colorspace for extended range P3 that's used for HDR // So override it after configuring the EGL context - case ColorMode::Hdr: case ColorMode::Hdr10: overrideWindowDataSpaceForHdr = true; attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index b002bbf20c08..ea136edf46be 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -1079,7 +1079,7 @@ public final class AudioFormat implements Parcelable { * @return one of the values that can be set in {@link Builder#setEncoding(int)} or * {@link AudioFormat#ENCODING_INVALID} if not set. */ - public int getEncoding() { + public @Encoding int getEncoding() { return mEncoding; } diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 46db77708521..587e35b4b1fc 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -16,6 +16,9 @@ package android.media; +import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE; + +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1635,6 +1638,34 @@ public final class MediaFormat { */ public static final String KEY_ALLOW_FRAME_DROP = "allow-frame-drop"; + /** + * A key describing the desired codec importance for the application. + * <p> + * The associated value is a positive integer including zero. + * Higher value means lesser importance. + * <p> + * The resource manager may use the codec importance, along with other factors + * when reclaiming codecs from an application. + * The specifics of reclaim policy is device dependent, but specifying the codec importance, + * will allow the resource manager to prioritize reclaiming less important codecs + * (assigned higher values) from the (reclaim) requesting application first. + * So, the codec importance is only relevant within the context of that application. + * <p> + * The codec importance can be set: + * <ul> + * <li>through {@link MediaCodec#configure}. </li> + * <li>through {@link MediaCodec#setParameters} if the codec has been configured already, + * which allows the users to change the codec importance multiple times. + * </ul> + * Any change/update in codec importance is guaranteed upon the completion of the function call + * that sets the codec importance. So, in case of concurrent codec operations, + * make sure to wait for the change in codec importance, before using another codec. + * Note that unless specified, by default the codecs will have highest importance (of value 0). + * + */ + @FlaggedApi(FLAG_CODEC_IMPORTANCE) + public static final String KEY_IMPORTANCE = "importance"; + /* package private */ MediaFormat(@NonNull Map<String, Object> map) { mMap = map; } diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index db0195056074..7f8f1a3f22ef 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -16,6 +16,7 @@ package android.media.tv; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -29,6 +30,7 @@ import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.Intent; +import android.media.tv.flags.Flags; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -2540,9 +2542,9 @@ public final class TvContract { * <p>This is used to indicate the broadcast visibility type defined in the underlying * broadcast standard or country/operator profile, if applicable. For example, * {@code visible_service_flag} and {@code numeric_selection_flag} of - * {@code service_attribute_descriptor} in D-Book, {@code visible_service_flag} and - * {@code selectable_service_flag} of {@code ciplus_service_descriptor} in CI Plus 1.3 - * specification. + * {@code service_attribute_descriptor} in D-Book, the specification for UK-based TV + * products, {@code visible_service_flag} and {@code selectable_service_flag} of + * {@code ciplus_service_descriptor} in the CI Plus 1.3 specification. * * <p>The value should match one of the following: * {@link #BROADCAST_VISIBILITY_TYPE_VISIBLE}, @@ -2553,8 +2555,8 @@ public final class TvContract { * by default. * * <p>Type: INTEGER - * @hide */ + @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES) public static final String COLUMN_BROADCAST_VISIBILITY_TYPE = "broadcast_visibility_type"; /** @hide */ @@ -2571,8 +2573,8 @@ public final class TvContract { * visible from users and selectable by users via normal service navigation mechanisms. * * @see #COLUMN_BROADCAST_VISIBILITY_TYPE - * @hide */ + @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES) public static final int BROADCAST_VISIBILITY_TYPE_VISIBLE = 0; /** @@ -2581,18 +2583,18 @@ public final class TvContract { * the logical channel number. * * @see #COLUMN_BROADCAST_VISIBILITY_TYPE - * @hide */ + @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES) public static final int BROADCAST_VISIBILITY_TYPE_NUMERIC_SELECTABLE_ONLY = 1; /** * The broadcast visibility type for invisible services. Use this type when the service - * is invisible from users and unselectable by users via any of normal service navigation - * mechanisms. + * is invisible from users and not able to be selected by users via any of the normal + * service navigation mechanisms. * * @see #COLUMN_BROADCAST_VISIBILITY_TYPE - * @hide */ + @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES) public static final int BROADCAST_VISIBILITY_TYPE_INVISIBLE = 2; private Channels() {} diff --git a/nfc/Android.bp b/nfc/Android.bp index 87da299464e4..74bec3ee8b3c 100644 --- a/nfc/Android.bp +++ b/nfc/Android.bp @@ -69,6 +69,7 @@ java_sdk_library { jarjar_rules: ":nfc-jarjar-rules", lint: { strict_updatability_linting: true, + baseline_filename: "lint-baseline.xml", }, apex_available: [ "//apex_available:platform", diff --git a/nfc/lint-baseline.xml b/nfc/lint-baseline.xml new file mode 100644 index 000000000000..1dfdd29e480a --- /dev/null +++ b/nfc/lint-baseline.xml @@ -0,0 +1,213 @@ +<?xml version="1.0" encoding="UTF-8"?> +<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01"> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `new android.nfc.cardemulation.AidGroup`" + errorLine1=" AidGroup aidGroup = new AidGroup(aids, category);" + errorLine2=" ~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="377" + column="29"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`" + errorLine1=" return (group != null ? group.getAids() : null);" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="537" + column="43"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`" + errorLine1=" return (group != null ? group.getAids() : null);" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="547" + column="47"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`" + errorLine1=" return (serviceInfo != null ? serviceInfo.getAids() : null);" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="714" + column="55"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`" + errorLine1=" return (serviceInfo != null ? serviceInfo.getAids() : null);" + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="724" + column="59"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`" + errorLine1=" if (!serviceInfo.isOnHost()) {" + errorLine2=" ~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="755" + column="34"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`" + errorLine1=" return serviceInfo.getOffHostSecureElement() == null ?" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="756" + column="40"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`" + errorLine1=' "OffHost" : serviceInfo.getOffHostSecureElement();' + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="757" + column="53"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`" + errorLine1=" if (!serviceInfo.isOnHost()) {" + errorLine2=" ~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="772" + column="38"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`" + errorLine1=" return serviceInfo.getOffHostSecureElement() == null ?" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="773" + column="44"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`" + errorLine1=' "Offhost" : serviceInfo.getOffHostSecureElement();' + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="774" + column="57"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`" + errorLine1=" return (serviceInfo != null ? serviceInfo.getDescription() : null);" + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="798" + column="55"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`" + errorLine1=" return (serviceInfo != null ? serviceInfo.getDescription() : null);" + errorLine2=" ~~~~~~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="808" + column="59"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`" + errorLine1=" if (!activity.isResumed()) {" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="1032" + column="23"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`" + errorLine1=" if (!activity.isResumed()) {" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java" + line="1066" + column="23"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`" + errorLine1=" resumed = activity.isResumed();" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/NfcActivityManager.java" + line="124" + column="32"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`" + errorLine1=" if (!activity.isResumed()) {" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java" + line="2457" + column="23"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`" + errorLine1=" if (!activity.isResumed()) {" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java" + line="315" + column="23"/> + </issue> + + <issue + id="NewApi" + message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`" + errorLine1=" if (!activity.isResumed()) {" + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java" + line="351" + column="23"/> + </issue> + +</issues>
\ No newline at end of file diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 5da4b9518a31..c2cb75709b45 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -9,6 +9,9 @@ package { android_library { name: "SettingsLib", + defaults: [ + "SettingsLintDefaults", + ], static_libs: [ "androidx.localbroadcastmanager_localbroadcastmanager", @@ -60,8 +63,15 @@ android_library { "src/**/*.java", "src/**/*.kt", ], +} + +// defaults for lint option +java_defaults { + name: "SettingsLintDefaults", lint: { - extra_check_modules: ["SettingsLibLintChecker"], + extra_check_modules: [ + "SettingsLibLintChecker", + ], }, } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index 1c830c1c5b06..74b556ea106e 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -162,6 +162,7 @@ internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionApp uid = checkNotNull(applicationInfo).uid, packageName = packageName) }) RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory) + InfoPageAdditionalContent(record, isAllowed) } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt index 916d83af3f8f..3f7a8526839f 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt @@ -77,6 +77,9 @@ interface TogglePermissionAppListModel<T : AppRecord> { * Sets whether the permission is allowed for the given app. */ fun setAllowed(record: T, newAllowed: Boolean) + + @Composable + fun InfoPageAdditionalContent(record: T, isAllowed: () -> Boolean?){} } interface TogglePermissionAppListProvider { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 071afaf18598..f7f06739d4b2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -117,6 +117,12 @@ public class BluetoothUtils { } } + if (cachedDevice.isHearingAidDevice()) { + return new Pair<>(getBluetoothDrawable(context, + com.android.internal.R.drawable.ic_bt_hearing_aid), + context.getString(R.string.bluetooth_talkback_hearing_aids)); + } + List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles(); int resId = 0; for (LocalBluetoothProfile profile : profiles) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 032a8381a7b5..560bc467ca8e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -412,6 +412,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> public void setHearingAidInfo(HearingAidInfo hearingAidInfo) { mHearingAidInfo = hearingAidInfo; + dispatchAttributesChanged(); } public HearingAidInfo getHearingAidInfo() { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index 7409eea2cd51..f7ec80b041e9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -16,6 +16,7 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -87,6 +88,14 @@ public class BluetoothUtilsTest { } @Test + public void getBtClassDrawableWithDescription_typeHearingAid_returnHearingAidDrawable() { + when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true); + BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedBluetoothDevice); + + verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_hearing_aid); + } + + @Test public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() { when(mBluetoothDevice.getMetadata( BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("false".getBytes()); diff --git a/packages/SettingsProvider/TEST_MAPPING b/packages/SettingsProvider/TEST_MAPPING index 890510ffebe3..0eed2b7490d4 100644 --- a/packages/SettingsProvider/TEST_MAPPING +++ b/packages/SettingsProvider/TEST_MAPPING @@ -11,5 +11,10 @@ } ] } + ], + "postsubmit": [ + { + "name": "CtsDeviceConfigTestCases" + } ] } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig index ecac5ee18582..edbc0b391b27 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig +++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig @@ -14,3 +14,11 @@ flag { bug: "311155098" is_fixed_read_only: true } + +flag { + name: "configurable_font_scale_default" + namespace: "large_screen_experiences_app_compat" + description: "Whether the font_scale is read from a device dependent configuration file" + bug: "319808237" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 507d9c467d68..3dfc4540d6e7 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -559,6 +559,9 @@ <!-- Permission required for CTS test - android.server.biometrics --> <uses-permission android:name="android.permission.TEST_BIOMETRIC" /> + <!-- Permission required for CTS test - android.server.biometrics --> + <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" /> + <!-- Permissions required for CTS test - NotificationManagerTest --> <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" /> @@ -891,6 +894,9 @@ <!-- Permission required for Cts test - CtsNotificationTestCases --> <uses-permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" /> + <!-- Permission required for BinaryTransparencyService shell API and host test --> + <uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 7443e4ccf79e..168e6e003dc6 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -355,6 +355,8 @@ <uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" /> + <uses-permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" /> + <!-- Listen to (dis-)connection of external displays and enable / disable them. --> <uses-permission android:name="android.permission.MANAGE_DISPLAYS" /> diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index f7b1a26c9df9..7ba889bc8fee 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -36,3 +36,10 @@ flag { description: "Animates the floating menu's transition between curved and jagged edges." bug: "281140482" } + +flag { + name: "create_windowless_window_magnifier" + namespace: "accessibility" + description: "Uses SurfaceControlViewHost to create the magnifier for window magnification." + bug: "280992417" +} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index c23a49c68363..b464498d10f6 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -343,3 +343,10 @@ flag { description: "Relocate Smartspace to bottom of the Lock Screen" bug: "316212788" } + +flag { + name: "pin_input_field_styled_focus_state" + namespace: "systemui" + description: "Enables styled focus states on pin input field if keyboard is connected" + bug: "316106516" +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index c8e18d7df63a..0a1a98ff9a91 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -610,7 +610,7 @@ private fun WidgetContent( model.appWidgetHost.setInteractionHandler(viewModel.getInteractionHandler()) val view = model.appWidgetHost - .createView(context, model.appWidgetId, model.providerInfo) + .createViewForCommunal(context, model.appWidgetId, model.providerInfo) .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) } model.appWidgetHost.setInteractionHandler(null) view diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index 900616f6af89..42fcd1363f11 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -16,23 +16,55 @@ package com.android.systemui.keyguard.ui.composable.section +import android.content.Context +import android.view.ViewGroup import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.android.compose.animation.scene.SceneScope +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.notifications.ui.composable.NotificationStack +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.statusbar.notification.stack.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject class NotificationSection @Inject constructor( + @Application context: Context, private val viewModel: NotificationsPlaceholderViewModel, + controller: NotificationStackScrollLayoutController, + sceneContainerFlags: SceneContainerFlags, + sharedNotificationContainer: SharedNotificationContainer, + stackScrollLayout: NotificationStackScrollLayout, + notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel, + ambientState: AmbientState, ) { + init { + if (sceneContainerFlags.flexiNotifsEnabled()) { + (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout) + sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout) + + NotificationStackAppearanceViewBinder.bind( + context, + sharedNotificationContainer, + notificationStackAppearanceViewModel, + ambientState, + controller, + ) + } + } + @Composable fun SceneScope.Notifications(modifier: Modifier = Modifier) { NotificationStack( viewModel = viewModel, - isScrimVisible = false, modifier = modifier, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 0b26ae96de54..e835d3e576d5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme @@ -43,8 +44,10 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.modifiers.height import com.android.systemui.notifications.ui.composable.Notifications.Form import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel +import kotlin.math.roundToInt object Notifications { object Elements { @@ -77,32 +80,52 @@ fun SceneScope.HeadsUpNotificationSpace( ) } -/** Adds the space where notification stack will appear in the scene. */ +/** Adds the space where notification stack should appear in the scene. */ @Composable fun SceneScope.NotificationStack( viewModel: NotificationsPlaceholderViewModel, - isScrimVisible: Boolean, + modifier: Modifier = Modifier, +) { + NotificationPlaceholder( + viewModel = viewModel, + form = Form.Stack, + modifier = modifier, + ) +} + +/** + * Adds the space where notification stack should appear in the scene, with a scrim and nested + * scrolling. + */ +@Composable +fun SceneScope.NotificationScrollingStack( + viewModel: NotificationsPlaceholderViewModel, modifier: Modifier = Modifier, ) { val cornerRadius by viewModel.cornerRadiusDp.collectAsState() - Box(modifier = modifier) { - if (isScrimVisible) { - Box( - modifier = - Modifier.element(Notifications.Elements.NotificationScrim) - .fillMaxSize() - .graphicsLayer { - shape = RoundedCornerShape(cornerRadius.dp) - clip = true - } - .background(MaterialTheme.colorScheme.surface) - ) - } + val contentHeight by viewModel.intrinsicContentHeight.collectAsState() + + val expansionFraction by viewModel.expandFraction.collectAsState(0f) + + Box( + modifier = + modifier + .verticalNestedScrollToScene() + .fillMaxWidth() + .element(Notifications.Elements.NotificationScrim) + .graphicsLayer { + shape = RoundedCornerShape(cornerRadius.dp) + clip = true + alpha = expansionFraction + } + .background(MaterialTheme.colorScheme.surface) + .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f)) + ) { NotificationPlaceholder( viewModel = viewModel, form = Form.Stack, - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxWidth().height { contentHeight.roundToInt() } ) } } @@ -159,6 +182,7 @@ private fun SceneScope.NotificationPlaceholder( debugLog(viewModel) { "STACK onSizeChanged: size=$size" } } .onPlaced { coordinates: LayoutCoordinates -> + viewModel.onContentTopChanged(coordinates.positionInWindow().y) debugLog(viewModel) { "STACK onPlaced:" + " size=${coordinates.size}" + diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index bded98d52481..747faabe514b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace +import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.Edge import com.android.systemui.scene.shared.model.SceneKey @@ -66,6 +67,7 @@ constructor( modifier: Modifier, ) { Box(modifier = modifier) { + Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim)) HeadsUpNotificationSpace( viewModel = notificationsViewModel, modifier = Modifier.padding(16.dp).fillMaxSize(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt index 6bb525aa00fb..0c2c5195becc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt @@ -3,12 +3,12 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder -import com.android.systemui.notifications.ui.composable.Notifications -import com.android.systemui.scene.ui.composable.Shade +import com.android.systemui.qs.ui.composable.QuickSettings +import com.android.systemui.shade.ui.composable.ShadeHeader fun TransitionBuilder.goneToShadeTransition() { spec = tween(durationMillis = 500) - translate(Shade.rootElementKey, Edge.Top, true) - fade(Notifications.Elements.NotificationScrim) + fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) } + translate(QuickSettings.Elements.Content, Edge.Top, true) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 9c0f1fe0ec68..085dcf44fa69 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -22,11 +22,9 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -47,7 +45,7 @@ import com.android.systemui.media.controls.ui.MediaCarouselController import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL -import com.android.systemui.notifications.ui.composable.NotificationStack +import com.android.systemui.notifications.ui.composable.NotificationScrollingStack import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Direction @@ -148,35 +146,27 @@ private fun SceneScope.ShadeScene( mediaHost: MediaHost, modifier: Modifier = Modifier, ) { + val localDensity = LocalDensity.current val layoutWidth = remember { mutableStateOf(0) } - Box(modifier.element(Shade.Elements.Scrim)) { - Spacer( - modifier = - Modifier.element(Shade.Elements.ScrimBackground) - .fillMaxSize() - .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim) - ) + Box( + modifier = + modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim), + ) + Box { Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = - Modifier.fillMaxSize() - .clickable(onClick = { viewModel.onContentClicked() }) - .padding( - start = Shade.Dimensions.HorizontalPadding, - end = Shade.Dimensions.HorizontalPadding, - bottom = 48.dp - ) + modifier = Modifier.fillMaxWidth().clickable(onClick = { viewModel.onContentClicked() }) ) { CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, + modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding) ) - Spacer(modifier = Modifier.height(16.dp)) QuickSettings( - modifier = Modifier.wrapContentHeight(), + modifier = Modifier.height(130.dp), viewModel.qsSceneAdapter, ) @@ -202,16 +192,15 @@ private fun SceneScope.ShadeScene( }, mediaHost = mediaHost, layoutWidth = layoutWidth.value, - layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(), + layoutHeight = with(localDensity) { mediaHeight.toPx() }.toInt(), carouselController = mediaCarouselController, ) } Spacer(modifier = Modifier.height(16.dp)) - NotificationStack( + NotificationScrollingStack( viewModel = viewModel.notifications, - isScrimVisible = true, - modifier = Modifier.weight(1f), + modifier = Modifier.fillMaxWidth().weight(1f), ) } } diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml index 8fe9656c1879..fc337fb19f43 100644 --- a/packages/SystemUI/compose/features/tests/AndroidManifest.xml +++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml @@ -30,11 +30,6 @@ android:enabled="false" tools:replace="android:authorities" tools:node="remove" /> - <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle" - android:authorities="com.android.systemui.test.keyguard.disabled" - android:enabled="false" - tools:replace="android:authorities" - tools:node="remove" /> <provider android:name="com.android.systemui.keyguard.CustomizationProvider" android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled" android:enabled="false" diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java index e89316997fb2..c4bcb536de78 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -35,6 +35,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -107,7 +108,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener, mEmergencyButtonController, mFalsingCollector, featureFlags, - mSelectedUserInteractor) { + mSelectedUserInteractor, new FakeKeyboardRepository()) { @Override public void onResume(int reason) { super.onResume(reason); diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 78b854e39d13..c2efc05132ba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository import com.android.systemui.res.R import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED @@ -141,7 +142,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { postureController, featureFlags, mSelectedUserInteractor, - uiEventLogger + uiEventLogger, + FakeKeyboardRepository() ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index bc3ca1bd6c56..2a793ea70292 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -47,16 +47,20 @@ import com.android.systemui.classifier.FalsingA11yDelegate import com.android.systemui.classifier.FalsingCollector import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope import com.android.systemui.log.SessionTracker import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -66,6 +70,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.UserSwitcherController +import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.util.mockito.any @@ -156,7 +161,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController private lateinit var keyguardPasswordView: KeyguardPasswordView private lateinit var testableResources: TestableResources - private lateinit var sceneTestUtils: SceneTestUtils + private lateinit var kosmos: Kosmos private lateinit var sceneInteractor: SceneInteractor private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var deviceEntryInteractor: DeviceEntryInteractor @@ -222,15 +227,15 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { mSelectedUserInteractor, ) - sceneTestUtils = SceneTestUtils(this) - sceneInteractor = sceneTestUtils.sceneInteractor() + kosmos = testKosmos() + sceneInteractor = kosmos.sceneInteractor keyguardTransitionInteractor = - KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create(kosmos.testScope.backgroundScope) .keyguardTransitionInteractor sceneTransitionStateFlow = MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen)) sceneInteractor.setTransitionState(sceneTransitionStateFlow) - deviceEntryInteractor = sceneTestUtils.deviceEntryInteractor() + deviceEntryInteractor = kosmos.deviceEntryInteractor mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) underTest = @@ -249,7 +254,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { falsingManager, userSwitcherController, featureFlags, - sceneTestUtils.fakeSceneContainerFlags, + kosmos.fakeSceneContainerFlags, globalSettings, sessionTracker, Optional.of(sideFpsController), @@ -259,7 +264,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { audioManager, faceAuthInteractor, mock(), - { JavaAdapter(sceneTestUtils.testScope.backgroundScope) }, + { JavaAdapter(kosmos.testScope.backgroundScope) }, mSelectedUserInteractor, deviceProvisionedController, faceAuthAccessibilityDelegate, @@ -786,8 +791,8 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { @Test fun dismissesKeyguard_whenSceneChangesToGone() = - sceneTestUtils.testScope.runTest { - sceneTestUtils.fakeSceneContainerFlags.enabled = true + kosmos.testScope.runTest { + kosmos.fakeSceneContainerFlags.enabled = true // Upon init, we have never dismisses the keyguard. underTest.onInit() runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index f7751753cc18..0959f1b2bcf6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -16,7 +16,6 @@ package com.android.keyguard -import android.telephony.PinResult import android.telephony.TelephonyManager import android.testing.TestableLooper import android.view.LayoutInflater @@ -28,9 +27,11 @@ import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository import com.android.systemui.res.R import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -39,7 +40,6 @@ import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.anyInt -import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -75,8 +75,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { `when`(messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java))) .thenReturn(keyguardMessageAreaController) `when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager) - `when`(telephonyManager.supplyIccLockPin(anyString())) - .thenReturn(mock(PinResult::class.java)) + `when`(telephonyManager.supplyIccLockPin(anyString())).thenReturn(mock()) simPinView = LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null) as KeyguardSimPinView @@ -97,7 +96,8 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { falsingCollector, emergencyButtonController, fakeFeatureFlags, - mSelectedUserInteractor + mSelectedUserInteractor, + FakeKeyboardRepository() ) underTest.init() underTest.onViewAttached() diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt index 45a60199984b..1281e4409a83 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository import com.android.systemui.res.R import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any @@ -91,6 +92,7 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() { emergencyButtonController, fakeFeatureFlags, mSelectedUserInteractor, + FakeKeyboardRepository() ) underTest.init() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt index 27d1eb741bb0..c86c7470909b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt @@ -17,14 +17,15 @@ package com.android.systemui.accessibility.data.repository import android.os.UserHandle +import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -51,6 +52,7 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { underTest = ColorCorrectionRepositoryImpl( testDispatcher, + scope.backgroundScope, settings, ) } @@ -58,83 +60,78 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { @Test fun isEnabled_initiallyGetsSettingsValue() = scope.runTest { + val actualValue by collectLastValue(underTest.isEnabled(testUser1)) + settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - 1, + SETTING_NAME, + ENABLED, testUser1.identifier ) - - underTest = - ColorCorrectionRepositoryImpl( - testDispatcher, - settings, - ) - - underTest.isEnabled(testUser1).launchIn(backgroundScope) runCurrent() - val actualValue: Boolean = underTest.isEnabled(testUser1).first() Truth.assertThat(actualValue).isTrue() } @Test fun isEnabled_settingUpdated_valueUpdated() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1)) settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse() settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.ENABLED, + SETTING_NAME, + ENABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue() settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse() + + Truth.assertThat(flowValues.size).isEqualTo(3) + Truth.assertThat(flowValues).containsExactly(false, true, false).inOrder() } @Test fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1)) + val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2)) + settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser1.identifier ) - underTest.isEnabled(testUser2).launchIn(backgroundScope) settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser2.identifier ) - runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse() - Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse() + + Truth.assertThat(lastValueUser1).isFalse() + Truth.assertThat(lastValueUser2).isFalse() settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.ENABLED, + SETTING_NAME, + ENABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue() - Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse() + + Truth.assertThat(lastValueUser1).isTrue() + Truth.assertThat(lastValueUser2).isFalse() } @Test @@ -146,10 +143,10 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { val actualValue = settings.getIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, + SETTING_NAME, testUser1.identifier ) - Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.ENABLED) + Truth.assertThat(actualValue).isEqualTo(ENABLED) } @Test @@ -161,9 +158,15 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { val actualValue = settings.getIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, + SETTING_NAME, testUser1.identifier ) - Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.DISABLED) + Truth.assertThat(actualValue).isEqualTo(DISABLED) } + + companion object { + private const val SETTING_NAME = ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED + private const val DISABLED = 0 + private const val ENABLED = 1 + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt index 423e124bbc84..4853529229fe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt @@ -21,11 +21,11 @@ import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -52,6 +52,7 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() { underTest = ColorInversionRepositoryImpl( testDispatcher, + scope.backgroundScope, settings, ) } @@ -59,55 +60,47 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() { @Test fun isEnabled_initiallyGetsSettingsValue() = scope.runTest { - settings.putIntForUser(SETTING_NAME, 1, testUser1.identifier) + val actualValue by collectLastValue(underTest.isEnabled(testUser1)) - underTest = - ColorInversionRepositoryImpl( - testDispatcher, - settings, - ) - - underTest.isEnabled(testUser1).launchIn(backgroundScope) + settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) runCurrent() - val actualValue: Boolean = underTest.isEnabled(testUser1).first() assertThat(actualValue).isTrue() } @Test fun isEnabled_settingUpdated_valueUpdated() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val flowValues: List<Boolean> by + collectValues(underTest.isEnabled(testUser1)) settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isFalse() - settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isTrue() - settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isFalse() + + assertThat(flowValues.size).isEqualTo(3) + assertThat(flowValues).containsExactly(false, true, false).inOrder() } @Test fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1)) + val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2)) + settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier) - underTest.isEnabled(testUser2).launchIn(backgroundScope) settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier) - runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isFalse() - assertThat(underTest.isEnabled(testUser2).first()).isFalse() + assertThat(lastValueUser1).isFalse() + assertThat(lastValueUser2).isFalse() settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isTrue() - assertThat(underTest.isEnabled(testUser2).first()).isFalse() + assertThat(lastValueUser1).isTrue() + assertThat(lastValueUser2).isFalse() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index b4d4e1f51251..caf92199737c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt @@ -29,10 +29,13 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.log.table.TableLogBuffer -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock @@ -59,8 +62,8 @@ class AuthenticationRepositoryTest : SysuiTestCase() { @Mock private lateinit var tableLogger: TableLogBuffer @Mock private lateinit var devicePolicyManager: DevicePolicyManager - private val testUtils = SceneTestUtils(this) - private val testScope = testUtils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private val clock = FakeSystemClock() private val userRepository = FakeUserRepository() private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository @@ -82,8 +85,8 @@ class AuthenticationRepositoryTest : SysuiTestCase() { underTest = AuthenticationRepositoryImpl( applicationScope = testScope.backgroundScope, - backgroundDispatcher = testUtils.testDispatcher, - flags = testUtils.fakeSceneContainerFlags, + backgroundDispatcher = kosmos.testDispatcher, + flags = kosmos.sceneContainerFlags, clock = clock, getSecurityMode = getSecurityMode, userRepository = userRepository, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 10c16bd2f3ed..cb8cebf80767 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern @@ -29,7 +30,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationWipeModel import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,9 +47,9 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class AuthenticationInteractorTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val underTest = utils.authenticationInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val underTest = kosmos.authenticationInteractor private val onAuthenticationResult by testScope.collectLastValue(underTest.onAuthenticationResult) @@ -62,7 +64,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertThat(authMethod).isEqualTo(Pin) assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin) - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertThat(authMethod).isEqualTo(Password) assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password) @@ -74,7 +76,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { val authMethod by collectLastValue(underTest.authenticationMethod) runCurrent() - utils.authenticationRepository.setAuthenticationMethod(None) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(None) assertThat(authMethod).isEqualTo(None) assertThat(underTest.getAuthenticationMethod()).isEqualTo(None) @@ -83,7 +85,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPin_succeeds() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) } @@ -91,7 +93,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withIncorrectPin_fails() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))) } @@ -99,7 +101,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test(expected = IllegalArgumentException::class) fun authenticate_withEmptyPin_throwsException() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) underTest.authenticate(listOf()) } @@ -107,7 +109,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withCorrectMaxLengthPin_succeeds() = testScope.runTest { val correctMaxLengthPin = List(16) { 9 } - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) overrideCredential(correctMaxLengthPin) } @@ -124,7 +126,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { // If the policy changes, there is work to do in SysUI. assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertFailed(underTest.authenticate(List(17) { 9 })) } @@ -132,7 +134,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPassword_succeeds() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertSucceeded(underTest.authenticate("password".toList())) } @@ -140,7 +142,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withIncorrectPassword_fails() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertFailed(underTest.authenticate("alohomora".toList())) } @@ -148,7 +150,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPattern_succeeds() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pattern) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern) assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN)) } @@ -156,7 +158,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withIncorrectPattern_fails() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pattern) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern) val wrongPattern = listOf( AuthenticationPatternCoordinate(x = 2, y = 0), @@ -172,7 +174,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } @@ -182,14 +184,14 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true)) assertThat(underTest.lockoutEndTimestamp).isNull() - assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) + assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0) } @Test fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalse() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } @@ -207,7 +209,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalse() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } @@ -225,7 +227,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrue() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } @@ -241,7 +243,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) reportLockoutStarted(42) @@ -258,7 +260,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() = testScope.runTest { - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(false) } @@ -271,7 +273,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun tryAutoConfirm_withoutCorrectPassword_returnsNull() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true)) } @@ -280,7 +282,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun isAutoConfirmEnabled_featureDisabled_returnsFalse() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(false) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false) assertThat(isAutoConfirmEnabled).isFalse() } @@ -289,7 +291,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun isAutoConfirmEnabled_featureEnabled_returnsTrue() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) assertThat(isAutoConfirmEnabled).isTrue() } @@ -298,7 +300,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) // The feature is enabled. assertThat(isAutoConfirmEnabled).isTrue() @@ -308,7 +310,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN } assertThat(underTest.lockoutEndTimestamp).isNotNull() - assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) + assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1) // Lockout disabled auto-confirm. assertThat(isAutoConfirmEnabled).isFalse() @@ -336,7 +338,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { val failedAuthenticationAttempts by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) val correctPin = FakeAuthenticationRepository.DEFAULT_PIN assertSucceeded(underTest.authenticate(correctPin)) @@ -366,7 +368,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun lockoutEndTimestamp() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) val correctPin = FakeAuthenticationRepository.DEFAULT_PIN underTest.authenticate(correctPin) @@ -384,7 +386,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { val expectedLockoutEndTimestamp = testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp) - assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) + assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1) // Correct PIN, but locked out, so doesn't attempt it: assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false) @@ -409,7 +411,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun upcomingWipe() = testScope.runTest { val upcomingWipe by collectLastValue(underTest.upcomingWipe) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) val correctPin = FakeAuthenticationRepository.DEFAULT_PIN val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 } @@ -418,7 +420,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { var expectedFailedAttempts = 0 var remainingFailedAttempts = - utils.authenticationRepository.getMaxFailedUnlockAttemptsForWipe() + kosmos.fakeAuthenticationRepository.getMaxFailedUnlockAttemptsForWipe() assertThat(remainingFailedAttempts) .isGreaterThan(LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) @@ -458,7 +460,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withoutAutoConfirm_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(false) } @@ -470,11 +472,13 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinTooShort_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) overrideCredential( buildList { - repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) } + repeat(kosmos.fakeAuthenticationRepository.hintedPinLength - 1) { + add(it + 1) + } } ) setAutoConfirmFeatureEnabled(true) @@ -487,28 +491,31 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) overrideCredential( buildList { - repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) } + repeat(kosmos.fakeAuthenticationRepository.hintedPinLength) { add(it + 1) } } ) } - assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength) + assertThat(hintedPinLength) + .isEqualTo(kosmos.fakeAuthenticationRepository.hintedPinLength) } @Test fun hintedPinLength_withAutoConfirmPinTooLong_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) overrideCredential( buildList { - repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) } + repeat(kosmos.fakeAuthenticationRepository.hintedPinLength + 1) { + add(it + 1) + } } ) setAutoConfirmFeatureEnabled(true) @@ -520,10 +527,10 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withTooShortPassword() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) val tooShortPassword = buildList { - repeat(utils.authenticationRepository.minPasswordLength - 1) { time -> + repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time -> add("$time") } } @@ -534,7 +541,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(onAuthenticationResult).isTrue() assertThat(underTest.lockoutEndTimestamp).isNull() - assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) + assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0) assertThat(failedAuthenticationAttempts).isEqualTo(0) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt index 4a39799fd64f..72e884e9e5d6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt @@ -16,6 +16,7 @@ package com.android.systemui.biometrics +import android.graphics.Bitmap import android.hardware.biometrics.BiometricManager.Authenticators import android.hardware.biometrics.ComponentInfoInternal import android.hardware.biometrics.PromptContentView @@ -117,6 +118,8 @@ internal fun Collection<SensorPropertiesInternal?>.extractAuthenticatorTypes(): } internal fun promptInfo( + logoRes: Int = -1, + logoBitmap: Bitmap? = null, title: String = "title", subtitle: String = "sub", description: String = "desc", @@ -127,6 +130,8 @@ internal fun promptInfo( negativeButton: String = "neg", ): PromptInfo { val info = PromptInfo() + info.logoRes = logoRes + info.logoBitmap = logoBitmap info.title = title info.subtitle = subtitle info.description = description diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt index c2117ae5bda4..a67b0931f171 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt @@ -20,8 +20,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -36,8 +39,8 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class EmergencyServicesRepositoryImplTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var underTest: EmergencyServicesRepository @@ -52,7 +55,7 @@ class EmergencyServicesRepositoryImplTest : SysuiTestCase() { EmergencyServicesRepository( resources = context.resources, applicationScope = testScope.backgroundScope, - configurationRepository = utils.configurationRepository, + configurationRepository = kosmos.configurationRepository, ) } @@ -71,7 +74,7 @@ class EmergencyServicesRepositoryImplTest : SysuiTestCase() { private fun TestScope.setEmergencyCallWhileSimLocked(isEnabled: Boolean) { overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, isEnabled) - utils.configurationRepository.onConfigurationChange() + kosmos.fakeConfigurationRepository.onConfigurationChange() runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt index 63581b3f7070..741cde82354a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt @@ -25,11 +25,18 @@ import com.android.internal.logging.fakeMetricsLogger import com.android.internal.logging.nano.MetricsProto import com.android.internal.util.emergencyAffordanceManager import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.telephony.data.repository.fakeTelephonyRepository +import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.whenever import com.android.telecom.telecomManager @@ -54,11 +61,11 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor @Mock private lateinit var telecomManager: TelecomManager - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val metricsLogger = utils.kosmos.fakeMetricsLogger - private val activityTaskManager = utils.kosmos.activityTaskManager - private val emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val metricsLogger = kosmos.fakeMetricsLogger + private val activityTaskManager = kosmos.activityTaskManager + private val emergencyAffordanceManager = kosmos.emergencyAffordanceManager private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository @@ -68,9 +75,9 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - utils.fakeSceneContainerFlags.enabled = true + kosmos.fakeSceneContainerFlags.enabled = true - mobileConnectionsRepository = utils.mobileConnectionsRepository + mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL) overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL) @@ -83,18 +90,18 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { .thenReturn(needsEmergencyAffordance) whenever(telecomManager.isInCall).thenReturn(false) - utils.fakeFeatureFlags.set(REFACTOR_GETCURRENTUSER, true) + kosmos.fakeFeatureFlagsClassic.set(REFACTOR_GETCURRENTUSER, true) - utils.telephonyRepository.setHasTelephonyRadio(true) + kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true) - utils.kosmos.telecomManager = telecomManager + kosmos.telecomManager = telecomManager } @Test fun noTelephonyRadio_noButton() = testScope.runTest { - utils.telephonyRepository.setHasTelephonyRadio(false) - val underTest = utils.bouncerActionButtonInteractor() + kosmos.fakeTelephonyRepository.setHasTelephonyRadio(false) + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) assertThat(actionButton).isNull() } @@ -102,8 +109,8 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun noTelecomManager_noButton() = testScope.runTest { - utils.kosmos.telecomManager = null - val underTest = utils.bouncerActionButtonInteractor() + kosmos.telecomManager = null + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) assertThat(actionButton).isNull() } @@ -111,9 +118,9 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun duringCall_returnToCallButton() = testScope.runTest { - val underTest = utils.bouncerActionButtonInteractor() + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) - utils.telephonyRepository.setIsInCall(true) + kosmos.fakeTelephonyRepository.setIsInCall(true) assertThat(actionButton).isNotNull() assertThat(actionButton?.label).isEqualTo(MESSAGE_RETURN_TO_CALL) @@ -133,11 +140,13 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun noCall_secureAuthMethod_emergencyCallButton() = testScope.runTest { - val underTest = utils.bouncerActionButtonInteractor() + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) mobileConnectionsRepository.isAnySimSecure.value = false - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.telephonyRepository.setIsInCall(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeTelephonyRepository.setIsInCall(false) assertThat(actionButton).isNotNull() assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL) @@ -163,11 +172,13 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() = testScope.runTest { - val underTest = utils.bouncerActionButtonInteractor() + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) mobileConnectionsRepository.isAnySimSecure.value = true - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.telephonyRepository.setIsInCall(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + kosmos.fakeTelephonyRepository.setIsInCall(false) runCurrent() assertThat(actionButton).isNotNull() @@ -179,11 +190,13 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun noCall_insecure_noButton() = testScope.runTest { - val underTest = utils.bouncerActionButtonInteractor() + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) mobileConnectionsRepository.isAnySimSecure.value = false - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.telephonyRepository.setIsInCall(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + kosmos.fakeTelephonyRepository.setIsInCall(false) assertThat(actionButton).isNull() } @@ -191,13 +204,15 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun noCall_simSecureButEmergencyNotSupported_noButton() = testScope.runTest { - val underTest = utils.bouncerActionButtonInteractor() + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) mobileConnectionsRepository.isAnySimSecure.value = true overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, false) - utils.configurationRepository.onConfigurationChange() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.telephonyRepository.setIsInCall(false) + kosmos.fakeConfigurationRepository.onConfigurationChange() + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + kosmos.fakeTelephonyRepository.setIsInCall(false) runCurrent() assertThat(actionButton).isNull() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 4b6199b55b58..707777b9f728 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -20,15 +20,20 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationResult +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,9 +50,9 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class BouncerInteractorTest : SysuiTestCase() { - private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true } - private val testScope = utils.testScope - private val authenticationInteractor = utils.authenticationInteractor() + private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } + private val testScope = kosmos.testScope + private val authenticationInteractor = kosmos.authenticationInteractor private lateinit var underTest: BouncerInteractor @@ -62,7 +67,7 @@ class BouncerInteractorTest : SysuiTestCase() { overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD) overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN) - underTest = utils.bouncerInteractor() + underTest = kosmos.bouncerInteractor } @Test @@ -70,7 +75,9 @@ class BouncerInteractorTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) runCurrent() underTest.clearMessage() assertThat(message).isNull() @@ -94,7 +101,9 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_sim_skipsAuthentication() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Sim + ) runCurrent() // We rely on TelephonyManager to authenticate the sim card. @@ -109,9 +118,11 @@ class BouncerInteractorTest : SysuiTestCase() { testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) runCurrent() - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) assertThat(isAutoConfirmEnabled).isTrue() // Incomplete input. @@ -137,7 +148,9 @@ class BouncerInteractorTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) runCurrent() // Incomplete input. @@ -160,7 +173,7 @@ class BouncerInteractorTest : SysuiTestCase() { fun passwordAuthMethod() = testScope.runTest { val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) runCurrent() @@ -180,7 +193,8 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat( underTest.authenticate( buildList { - repeat(utils.authenticationRepository.minPasswordLength - 1) { time -> + repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time + -> add("$time") } } @@ -198,7 +212,7 @@ class BouncerInteractorTest : SysuiTestCase() { fun patternAuthMethod() = testScope.runTest { val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) runCurrent() @@ -214,7 +228,8 @@ class BouncerInteractorTest : SysuiTestCase() { AuthenticationPatternCoordinate(0, 1), ) assertThat(wrongPattern).isNotEqualTo(FakeAuthenticationRepository.PATTERN) - assertThat(wrongPattern.size).isAtLeast(utils.authenticationRepository.minPatternLength) + assertThat(wrongPattern.size) + .isAtLeast(kosmos.fakeAuthenticationRepository.minPatternLength) assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED) assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN) @@ -225,7 +240,7 @@ class BouncerInteractorTest : SysuiTestCase() { val tooShortPattern = FakeAuthenticationRepository.PATTERN.subList( 0, - utils.authenticationRepository.minPatternLength - 1 + kosmos.fakeAuthenticationRepository.minPatternLength - 1 ) assertThat(underTest.authenticate(tooShortPattern)) .isEqualTo(AuthenticationResult.SKIPPED) @@ -245,7 +260,9 @@ class BouncerInteractorTest : SysuiTestCase() { val lockoutStartedEvents by collectValues(underTest.onLockoutStarted) val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) assertThat(lockoutStartedEvents).isEmpty() // Try the wrong PIN repeatedly, until lockout is triggered: @@ -291,17 +308,17 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun intentionalUserInputEvent_registersTouchEvent() = testScope.runTest { - assertThat(utils.powerRepository.userTouchRegistered).isFalse() + assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse() underTest.onIntentionalUserInput() - assertThat(utils.powerRepository.userTouchRegistered).isTrue() + assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue() } @Test fun intentionalUserInputEvent_notifiesFaceAuthInteractor() = testScope.runTest { val isFaceAuthRunning by - collectLastValue(utils.kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning) - utils.kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted() + collectLastValue(kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning) + kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted() runCurrent() assertThat(isFaceAuthRunning).isTrue() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt index 8c53c0e3f267..09fdd11a99dd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt @@ -28,8 +28,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor.Companion.INVALID_SUBSCRIPTION_ID import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -54,10 +57,10 @@ class SimBouncerInteractorTest : SysuiTestCase() { @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock lateinit var euiccManager: EuiccManager - private val utils = SceneTestUtils(this) + private val kosmos = testKosmos() private val bouncerSimRepository = FakeSimBouncerRepository() private val resources: Resources = context.resources - private val testScope = utils.testScope + private val testScope = kosmos.testScope private lateinit var underTest: SimBouncerInteractor @@ -68,13 +71,13 @@ class SimBouncerInteractorTest : SysuiTestCase() { SimBouncerInteractor( context, testScope.backgroundScope, - utils.testDispatcher, + kosmos.testDispatcher, bouncerSimRepository, telephonyManager, resources, keyguardUpdateMonitor, euiccManager, - utils.mobileConnectionsRepository, + kosmos.mobileConnectionsRepository, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 3043a710276b..27b84b2ffabc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -20,9 +20,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest @@ -33,16 +37,16 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class AuthMethodBouncerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val bouncerInteractor = utils.bouncerInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val bouncerInteractor = kosmos.bouncerInteractor private val underTest = PinBouncerViewModel( applicationContext = context, viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true), - simBouncerInteractor = utils.simBouncerInteractor, + simBouncerInteractor = kosmos.simBouncerInteractor, authenticationMethod = AuthenticationMethodModel.Pin, ) @@ -50,7 +54,9 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { fun animateFailure() = testScope.runTest { val animateFailure by collectLastValue(underTest.animateFailure) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) assertThat(animateFailure).isFalse() // Wrong PIN: diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 4b1f9fee48bb..cfe8c5d52c18 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -20,15 +20,21 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlin.time.Duration.Companion.seconds @@ -50,16 +56,16 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class BouncerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val authenticationInteractor = utils.authenticationInteractor() - private val bouncerInteractor = utils.bouncerInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val authenticationInteractor = kosmos.authenticationInteractor + private val bouncerInteractor = kosmos.bouncerInteractor private lateinit var underTest: BouncerViewModel @Before fun setUp() { - utils.fakeSceneContainerFlags.enabled = true - underTest = utils.bouncerViewModel() + kosmos.fakeSceneContainerFlags.enabled = true + underTest = kosmos.bouncerViewModel } @Test @@ -68,7 +74,7 @@ class BouncerViewModelTest : SysuiTestCase() { var authMethodViewModel: AuthMethodBouncerViewModel? = null authMethodsToTest().forEach { authMethod -> - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) val job = underTest.authMethodViewModel.onEach { authMethodViewModel = it }.launchIn(this) runCurrent() @@ -98,13 +104,13 @@ class BouncerViewModelTest : SysuiTestCase() { // First pass, populate our "seen" map: authMethodsToTest().forEach { authMethod -> - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) authMethodViewModel?.let { seen[authMethod] = it } } // Second pass, assert same instances are not reused: authMethodsToTest().forEach { authMethod -> - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) authMethodViewModel?.let { assertThat(it.authenticationMethod).isEqualTo(authMethod) assertThat(it).isNotSameInstanceAs(seen[authMethod]) @@ -116,11 +122,11 @@ class BouncerViewModelTest : SysuiTestCase() { fun authMethodUnchanged_reusesInstances() = testScope.runTest { authMethodsToTest().forEach { authMethod -> - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) val firstInstance: AuthMethodBouncerViewModel? = collectLastValue(underTest.authMethodViewModel).invoke() - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) val secondInstance: AuthMethodBouncerViewModel? = collectLastValue(underTest.authMethodViewModel).invoke() @@ -139,7 +145,7 @@ class BouncerViewModelTest : SysuiTestCase() { fun message() = testScope.runTest { val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(message?.isUpdateAnimated).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { @@ -157,8 +163,8 @@ class BouncerViewModelTest : SysuiTestCase() { testScope.runTest { val authMethodViewModel by collectLastValue(underTest.authMethodViewModel) val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod(Pin) - assertThat(utils.authenticationRepository.lockoutEndTimestamp).isNull() + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) + assertThat(kosmos.fakeAuthenticationRepository.lockoutEndTimestamp).isNull() assertThat(authMethodViewModel?.lockoutMessageId).isNotNull() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times -> @@ -192,7 +198,7 @@ class BouncerViewModelTest : SysuiTestCase() { authViewModel?.isInputEnabled ?: emptyFlow() } ) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(isInputEnabled).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { @@ -210,7 +216,7 @@ class BouncerViewModelTest : SysuiTestCase() { testScope.runTest { val authMethodViewModel by collectLastValue(underTest.authMethodViewModel) val dialogViewModel by collectLastValue(underTest.dialogViewModel) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(authMethodViewModel?.lockoutMessageId).isNotNull() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { @@ -228,17 +234,17 @@ class BouncerViewModelTest : SysuiTestCase() { fun isSideBySideSupported() = testScope.runTest { val isSideBySideSupported by collectLastValue(underTest.isSideBySideSupported) - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(isSideBySideSupported).isTrue() - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertThat(isSideBySideSupported).isTrue() - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(isSideBySideSupported).isTrue() - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertThat(isSideBySideSupported).isFalse() } @@ -246,12 +252,12 @@ class BouncerViewModelTest : SysuiTestCase() { fun isFoldSplitRequired() = testScope.runTest { val isFoldSplitRequired by collectLastValue(underTest.isFoldSplitRequired) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(isFoldSplitRequired).isTrue() - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertThat(isFoldSplitRequired).isFalse() - utils.authenticationRepository.setAuthenticationMethod(Pattern) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern) assertThat(isFoldSplitRequired).isTrue() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 5c5632f6aa7b..b3b6457b46e7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -19,13 +19,19 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -43,12 +49,12 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PasswordBouncerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val authenticationInteractor = utils.authenticationInteractor() - private val sceneInteractor = utils.sceneInteractor() - private val bouncerInteractor = utils.bouncerInteractor() - private val bouncerViewModel = utils.bouncerViewModel() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val authenticationInteractor = kosmos.authenticationInteractor + private val sceneInteractor = kosmos.sceneInteractor + private val bouncerInteractor = kosmos.bouncerInteractor + private val bouncerViewModel = kosmos.bouncerViewModel private val isInputEnabled = MutableStateFlow(true) private val underTest = @@ -140,10 +146,10 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(SceneKey.Bouncer) // No input entered. @@ -309,8 +315,10 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { } private fun TestScope.lockDeviceAndOpenPasswordBouncer() { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Password + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(SceneKey.Bouncer) } @@ -320,13 +328,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { ) { if (isLockedOut) { repeat(failedAttemptCount) { - utils.authenticationRepository.reportAuthenticationAttempt(false) + kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false) } - utils.authenticationRepository.reportLockoutStarted( + kosmos.fakeAuthenticationRepository.reportLockoutStarted( 30.seconds.inWholeMilliseconds.toInt() ) } else { - utils.authenticationRepository.reportAuthenticationAttempt(true) + kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(true) } isInputEnabled.value = !isLockedOut diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 9ee344a9527a..c2680bcc82a3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -20,13 +20,20 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.authenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -44,12 +51,12 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PatternBouncerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val authenticationInteractor = utils.authenticationInteractor() - private val sceneInteractor = utils.sceneInteractor() - private val bouncerInteractor = utils.bouncerInteractor() - private val bouncerViewModel = utils.bouncerViewModel() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val authenticationInteractor = kosmos.authenticationInteractor + private val sceneInteractor = kosmos.sceneInteractor + private val bouncerInteractor = kosmos.bouncerInteractor + private val bouncerViewModel = kosmos.bouncerViewModel private val underTest = PatternBouncerViewModel( applicationContext = context, @@ -305,7 +312,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragStart() CORRECT_PATTERN.subList( 0, - utils.authenticationRepository.minPatternLength - 1, + kosmos.authenticationRepository.minPatternLength - 1, ) .forEach { coordinate -> underTest.onDrag( @@ -374,8 +381,10 @@ class PatternBouncerViewModelTest : SysuiTestCase() { } private fun TestScope.lockDeviceAndOpenPatternBouncer() { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pattern + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(SceneKey.Bouncer) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 75e372f29590..1d660d63710d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -20,12 +20,20 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -43,19 +51,19 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PinBouncerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = utils.authenticationInteractor() - private val bouncerInteractor = utils.bouncerInteractor() - private val bouncerViewModel = utils.bouncerViewModel() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor + private val authenticationInteractor = kosmos.authenticationInteractor + private val bouncerInteractor = kosmos.bouncerInteractor + private val bouncerViewModel = kosmos.bouncerViewModel private val underTest = PinBouncerViewModel( applicationContext = context, viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true).asStateFlow(), - simBouncerInteractor = utils.simBouncerInteractor, + simBouncerInteractor = kosmos.simBouncerInteractor, authenticationMethod = AuthenticationMethodModel.Pin, ) @@ -86,7 +94,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true).asStateFlow(), - simBouncerInteractor = utils.simBouncerInteractor, + simBouncerInteractor = kosmos.simBouncerInteractor, authenticationMethod = AuthenticationMethodModel.Sim, ) @@ -97,7 +105,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun onErrorDialogDismissed_clearsDialogMessage() = testScope.runTest { val dialogMessage by collectLastValue(underTest.errorDialogMessage) - utils.simBouncerRepository.setSimVerificationErrorMessage("abc") + kosmos.fakeSimBouncerRepository.setSimVerificationErrorMessage("abc") assertThat(dialogMessage).isEqualTo("abc") underTest.onErrorDialogDismissed() @@ -114,10 +122,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true).asStateFlow(), - simBouncerInteractor = utils.simBouncerInteractor, + simBouncerInteractor = kosmos.simBouncerInteractor, authenticationMethod = AuthenticationMethodModel.Sim, ) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) val hintedPinLength by collectLastValue(underTest.hintedPinLength) assertThat(hintedPinLength).isNull() @@ -254,7 +262,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenCorrect() = testScope.runTest { - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult) lockDeviceAndOpenPinBouncer() @@ -269,7 +277,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) lockDeviceAndOpenPinBouncer() FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit -> @@ -309,7 +317,9 @@ class PinBouncerViewModelTest : SysuiTestCase() { testScope.runTest { val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown) } @@ -318,8 +328,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() = testScope.runTest { val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden) } @@ -328,8 +340,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() = testScope.runTest { val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) underTest.onPinButtonClicked(1) @@ -341,7 +355,9 @@ class PinBouncerViewModelTest : SysuiTestCase() { testScope.runTest { val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown) } @@ -350,8 +366,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun confirmButtonAppearance_withAutoConfirm_isHidden() = testScope.runTest { val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden) } @@ -361,10 +379,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { testScope.runTest { val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled) - utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true) + kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(true) assertThat(isAnimationEnabled).isFalse() - utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false) + kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(false) assertThat(isAnimationEnabled).isTrue() } @@ -382,8 +400,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { } private fun TestScope.lockDeviceAndOpenPinBouncer() { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(SceneKey.Bouncer) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt new file mode 100644 index 000000000000..820bfbfdf0a2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.data.repository + +import android.content.SharedPreferences +import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.settings.UserFileManager +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.FakeSharedPreferences +import com.google.common.truth.Truth.assertThat +import java.io.File +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalPrefsRepositoryImplTest : SysuiTestCase() { + private lateinit var underTest: CommunalPrefsRepositoryImpl + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private lateinit var userRepository: FakeUserRepository + private lateinit var userFileManager: UserFileManager + + @Before + fun setUp() { + userRepository = kosmos.fakeUserRepository + userRepository.setUserInfos(USER_INFOS) + + userFileManager = + FakeUserFileManager( + mapOf( + USER_INFOS[0].id to FakeSharedPreferences(), + USER_INFOS[1].id to FakeSharedPreferences() + ) + ) + underTest = + CommunalPrefsRepositoryImpl( + testScope.backgroundScope, + kosmos.testDispatcher, + userRepository, + userFileManager, + ) + } + + @Test + fun isCtaDismissedValue_byDefault_isFalse() = + testScope.runTest { + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + assertThat(isCtaDismissed).isFalse() + } + + @Test + fun isCtaDismissedValue_onSet_isTrue() = + testScope.runTest { + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + + underTest.setCtaDismissedForCurrentUser() + assertThat(isCtaDismissed).isTrue() + } + + @Test + fun isCtaDismissedValue_whenSwitchUser() = + testScope.runTest { + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + underTest.setCtaDismissedForCurrentUser() + + // dismissed true for primary user + assertThat(isCtaDismissed).isTrue() + + // switch to secondary user + userRepository.setSelectedUserInfo(USER_INFOS[1]) + + // dismissed is false for secondary user + assertThat(isCtaDismissed).isFalse() + + // switch back to primary user + userRepository.setSelectedUserInfo(USER_INFOS[0]) + + // dismissed is true for primary user + assertThat(isCtaDismissed).isTrue() + } + + private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) : + UserFileManager { + override fun getFile(fileName: String, userId: Int): File { + throw UnsupportedOperationException() + } + + override fun getSharedPreferences( + fileName: String, + mode: Int, + userId: Int + ): SharedPreferences { + if (fileName != FILE_NAME) { + throw IllegalArgumentException("Preference files must be $FILE_NAME") + } + return sharedPrefs.getValue(userId) + } + } + + companion object { + val USER_INFOS = + listOf( + UserInfo(/* id= */ 0, "zero", /* flags= */ 0), + UserInfo(/* id= */ 1, "secondary", /* flags= */ 0), + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt index 65176e1c5c0d..81d5344ed264 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt @@ -24,11 +24,12 @@ import com.android.systemui.communal.shared.model.ObservableCommunalTransitionSt import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.data.repository.SceneContainerRepository +import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.StandardTestDispatcher @@ -51,8 +52,8 @@ class CommunalRepositoryImplTest : SysuiTestCase() { @Before fun setUp() { - val sceneTestUtils = SceneTestUtils(this) - sceneContainerRepository = sceneTestUtils.fakeSceneContainerRepository() + val kosmos = testKosmos() + sceneContainerRepository = kosmos.sceneContainerRepository featureFlagsClassic = FakeFeatureFlagsClassic() featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt index 30a5497d0a14..30a5497d0a14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 4079f1241f31..1c6cecd6c838 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.communal.data.repository -import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo import android.content.ComponentName @@ -32,6 +31,7 @@ import com.android.systemui.communal.data.db.CommunalWidgetDao import com.android.systemui.communal.data.db.CommunalWidgetItem import com.android.systemui.communal.shared.CommunalWidgetHost import com.android.systemui.communal.shared.model.CommunalWidgetContentModel +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.FakeLogBuffer @@ -65,7 +65,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var appWidgetManager: AppWidgetManager - @Mock private lateinit var appWidgetHost: AppWidgetHost + @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost @Mock private lateinit var userManager: UserManager diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index cd83c07a3b38..178eb6a42e10 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository @@ -65,6 +66,7 @@ class CommunalInteractorTest : SysuiTestCase() { private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter private lateinit var underTest: CommunalInteractor @@ -84,6 +86,7 @@ class CommunalInteractorTest : SysuiTestCase() { smartspaceRepository = withDeps.smartspaceRepository keyguardRepository = withDeps.keyguardRepository editWidgetsActivityStarter = withDeps.editWidgetsActivityStarter + communalPrefsRepository = withDeps.communalPrefsRepository underTest = withDeps.communalInteractor } @@ -331,10 +334,9 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun cta_visibilityTrue_shows() = + fun ctaTile_showsByDefault() = testScope.runTest { tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(true) val ctaTileContent by collectLastValue(underTest.ctaTileContent) @@ -346,10 +348,10 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun ctaTile_visibilityFalse_doesNotShow() = + fun ctaTile_afterDismiss_doesNotShow() = testScope.runTest { tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(false) + communalPrefsRepository.setCtaDismissedForCurrentUser() val ctaTileContent by collectLastValue(underTest.ctaTileContent) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt new file mode 100644 index 000000000000..e904236f8c3e --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.widgets + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.widgets.CommunalAppWidgetHost +import com.android.systemui.communal.widgets.CommunalAppWidgetHostView +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalAppWidgetHostTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private lateinit var underTest: CommunalAppWidgetHost + + @Before + fun setUp() { + underTest = CommunalAppWidgetHost(context = context, hostId = 116) + } + + @Test + fun createViewForCommunal_returnCommunalAppWidgetView() = + testScope.runTest { + val appWidgetId = 789 + val view = + underTest.createViewForCommunal( + context = context, + appWidgetId = appWidgetId, + appWidget = null + ) + assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java) + assertThat(view).isNotNull() + assertThat(view.appWidgetId).isEqualTo(appWidgetId) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 54510a82201a..09243e5282da 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.communal.view.viewmodel import android.app.Activity.RESULT_CANCELED import android.app.Activity.RESULT_OK import android.app.smartspace.SmartspaceTarget -import android.appwidget.AppWidgetHost import android.content.ComponentName import android.provider.Settings import android.widget.RemoteViews @@ -36,6 +35,7 @@ import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.kosmos.testScope @@ -61,7 +61,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class CommunalEditModeViewModelTest : SysuiTestCase() { @Mock private lateinit var mediaHost: MediaHost - @Mock private lateinit var appWidgetHost: AppWidgetHost + @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost @Mock private lateinit var uiEventLogger: UiEventLogger private val kosmos = testKosmos() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 804c0528c46c..f9cfc3732a01 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository @@ -66,6 +67,7 @@ class CommunalViewModelTest : SysuiTestCase() { private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository private lateinit var mediaRepository: FakeCommunalMediaRepository + private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository private lateinit var underTest: CommunalViewModel @@ -82,6 +84,7 @@ class CommunalViewModelTest : SysuiTestCase() { widgetRepository = withDeps.widgetRepository smartspaceRepository = withDeps.smartspaceRepository mediaRepository = withDeps.mediaRepository + communalPrefsRepository = withDeps.communalPrefsRepository underTest = CommunalViewModel( @@ -149,9 +152,6 @@ class CommunalViewModelTest : SysuiTestCase() { // Media playing. mediaRepository.mediaActive() - // CTA Tile not dismissed. - communalRepository.setCtaTileInViewModeVisibility(true) - val communalContent by collectLastValue(underTest.communalContent) // Order is smart space, then UMO, widget content and cta tile. @@ -171,7 +171,6 @@ class CommunalViewModelTest : SysuiTestCase() { fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() = testScope.runTest { tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(true) val communalContent by collectLastValue(underTest.communalContent) val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing) @@ -195,7 +194,6 @@ class CommunalViewModelTest : SysuiTestCase() { fun popup_onDismiss_hidesImmediately() = testScope.runTest { tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(true) val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt index 565049baf6f4..b54c5bdae004 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt @@ -7,9 +7,11 @@ import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever @@ -36,8 +38,8 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { @Mock private lateinit var keyguardBypassController: KeyguardBypassController @Mock private lateinit var keyguardStateController: KeyguardStateController - private val testUtils = SceneTestUtils(this) - private val testScope = testUtils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private val userRepository = FakeUserRepository() private val keyguardRepository = FakeKeyguardRepository() @@ -52,7 +54,7 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { underTest = DeviceEntryRepositoryImpl( applicationScope = testScope.backgroundScope, - backgroundDispatcher = testUtils.testDispatcher, + backgroundDispatcher = kosmos.testDispatcher, userRepository = userRepository, lockPatternUtils = lockPatternUtils, keyguardBypassController = keyguardBypassController, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index 929e879a5406..62d23152b77a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -20,14 +20,20 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeTrustRepository -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -41,18 +47,18 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class DeviceEntryInteractorTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository - private val trustRepository = utils.kosmos.fakeTrustRepository - private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = utils.authenticationInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository + private val trustRepository = kosmos.fakeTrustRepository + private val sceneInteractor = kosmos.sceneInteractor + private val authenticationInteractor = kosmos.authenticationInteractor private lateinit var underTest: DeviceEntryInteractor @Before fun setUp() { - utils.fakeSceneContainerFlags.enabled = true - underTest = utils.deviceEntryInteractor() + kosmos.fakeSceneContainerFlags.enabled = true + underTest = kosmos.deviceEntryInteractor } @Test @@ -65,8 +71,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.apply { + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + kosmos.fakeDeviceEntryRepository.apply { setLockscreenEnabled(false) // Toggle isUnlocked, twice. @@ -99,8 +107,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isUnlocked_whenAuthMethodIsSimAndUnlocked_isFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Sim + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) val isUnlocked by collectLastValue(underTest.isUnlocked) assertThat(isUnlocked).isFalse() @@ -157,10 +167,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isDeviceEntered_onBouncer_isFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) - utils.deviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Bouncer) @@ -182,8 +192,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun canSwipeToEnter_onLockscreenWithPin_isFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) switchToScene(SceneKey.Lockscreen) val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) @@ -203,15 +215,15 @@ class DeviceEntryInteractorTest : SysuiTestCase() { } private fun setupSwipeDeviceEntryMethod() { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) } @Test fun canSwipeToEnter_whenTrustedByTrustManager_isTrue() = testScope.runTest { val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) switchToScene(SceneKey.Lockscreen) @@ -228,7 +240,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun canSwipeToEnter_whenAuthenticatedByFace_isTrue() = testScope.runTest { val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) switchToScene(SceneKey.Lockscreen) @@ -244,9 +256,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_lockedAndSecured_true() = testScope.runTest { - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) @@ -256,9 +268,11 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_lockedAndNotSecured_false() = testScope.runTest { - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) assertThat(underTest.isAuthenticationRequired()).isFalse() } @@ -266,9 +280,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_unlockedAndSecured_false() = testScope.runTest { - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) @@ -278,9 +292,11 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_unlockedAndNotSecured_false() = testScope.runTest { - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) assertThat(underTest.isAuthenticationRequired()).isFalse() } @@ -288,7 +304,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isBypassEnabled_enabledInRepository_true() = testScope.runTest { - utils.deviceEntryRepository.setBypassEnabled(true) + kosmos.fakeDeviceEntryRepository.setBypassEnabled(true) assertThat(underTest.isBypassEnabled.value).isTrue() } @@ -299,8 +315,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { switchToScene(SceneKey.Lockscreen) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() underTest.attemptDeviceEntry() @@ -315,7 +333,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { switchToScene(SceneKey.Lockscreen) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) underTest.attemptDeviceEntry() @@ -329,8 +349,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { switchToScene(SceneKey.Lockscreen) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - utils.deviceEntryRepository.setLockscreenEnabled(true) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) underTest.attemptDeviceEntry() @@ -340,7 +362,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isBypassEnabled_disabledInRepository_false() = testScope.runTest { - utils.deviceEntryRepository.setBypassEnabled(false) + kosmos.fakeDeviceEntryRepository.setBypassEnabled(false) assertThat(underTest.isBypassEnabled.value).isFalse() } @@ -348,8 +370,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun successfulAuthenticationChallengeAttempt_updatesIsUnlockedState() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) assertThat(isUnlocked).isFalse() authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 8933d2c93f5a..2c3afb1b40a9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -26,12 +26,16 @@ import com.android.systemui.common.ui.data.repository.FakeConfigurationRepositor import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel +import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractorFactory -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -48,10 +52,10 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class KeyguardInteractorTest : SysuiTestCase() { - private val testUtils = SceneTestUtils(this) - private val testScope = testUtils.testScope - private val repository = testUtils.keyguardRepository - private val sceneInteractor = testUtils.sceneInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val repository = kosmos.fakeKeyguardRepository + private val sceneInteractor = kosmos.sceneInteractor private val commandQueue = FakeCommandQueue() private val bouncerRepository = FakeKeyguardBouncerRepository() private val shadeRepository = FakeShadeRepository() @@ -63,7 +67,7 @@ class KeyguardInteractorTest : SysuiTestCase() { repository = repository, commandQueue = commandQueue, powerInteractor = PowerInteractorFactory.create().powerInteractor, - sceneContainerFlags = testUtils.fakeSceneContainerFlags, + sceneContainerFlags = kosmos.fakeSceneContainerFlags, bouncerRepository = bouncerRepository, configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), shadeRepository = shadeRepository, @@ -183,7 +187,7 @@ class KeyguardInteractorTest : SysuiTestCase() { @Test fun animationDozingTransitions() = testScope.runTest { - testUtils.fakeSceneContainerFlags.enabled = true + kosmos.fakeSceneContainerFlags.enabled = true val isAnimate by collectLastValue(underTest.animateDozingTransitions) underTest.setAnimateDozingTransitions(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 04e90c8deebf..aa15d0befa80 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -21,11 +21,19 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.communal.data.repository.fakeCommunalRepository +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -37,9 +45,9 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class LockscreenSceneViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor private val underTest = createLockscreenSceneViewModel() @@ -47,9 +55,11 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_canSwipeToUnlock_gone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setLockscreenEnabled(true) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) @@ -59,8 +69,10 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer) @@ -69,7 +81,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun leftTransitionSceneKey_communalIsEnabled_communal() = testScope.runTest { - utils.communalRepository.setIsCommunalEnabled(true) + kosmos.fakeCommunalRepository.setIsCommunalEnabled(true) val underTest = createLockscreenSceneViewModel() assertThat(underTest.leftDestinationSceneKey).isEqualTo(SceneKey.Communal) @@ -78,7 +90,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun leftTransitionSceneKey_communalIsDisabled_null() = testScope.runTest { - utils.communalRepository.setIsCommunalEnabled(false) + kosmos.fakeCommunalRepository.setIsCommunalEnabled(false) val underTest = createLockscreenSceneViewModel() assertThat(underTest.leftDestinationSceneKey).isNull() @@ -87,13 +99,13 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel { return LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, - deviceEntryInteractor = utils.deviceEntryInteractor(), - communalInteractor = utils.communalInteractor(), + deviceEntryInteractor = kosmos.deviceEntryInteractor, + communalInteractor = kosmos.communalInteractor, longPress = KeyguardLongPressViewModel( interactor = mock(), ), - notifications = utils.notificationsPlaceholderViewModel(), + notifications = kosmos.notificationsPlaceholderViewModel, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 6403ed19a9b2..be523b813494 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -22,13 +22,15 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.kosmos.testScope import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository @@ -36,6 +38,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -47,9 +50,9 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuickSettingsSceneViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() } @@ -90,7 +93,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { QuickSettingsSceneViewModel( shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, - notifications = utils.notificationsPlaceholderViewModel(), + notifications = kosmos.notificationsPlaceholderViewModel, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index ecc2ef15dec8..a08283de702f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -27,22 +27,39 @@ import com.android.internal.util.EmergencyAffordanceManager import com.android.internal.util.emergencyAffordanceManager import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor +import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel +import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel +import com.android.systemui.classifier.domain.interactor.falsingInteractor +import com.android.systemui.classifier.falsingCollector +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel +import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.model.SysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -50,13 +67,17 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.telephony.data.repository.fakeTelephonyRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -101,13 +122,13 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class SceneFrameworkIntegrationTest : SysuiTestCase() { - private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true } - private val testScope = utils.testScope - private val sceneContainerConfig = utils.fakeSceneContainerConfig() - private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = utils.authenticationInteractor() - private val deviceEntryInteractor = utils.deviceEntryInteractor() - private val communalInteractor = utils.communalInteractor() + private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } + private val testScope = kosmos.testScope + private val sceneContainerConfig = kosmos.sceneContainerConfig + private val sceneInteractor = kosmos.sceneInteractor + private val authenticationInteractor = kosmos.authenticationInteractor + private val deviceEntryInteractor = kosmos.deviceEntryInteractor + private val communalInteractor = kosmos.communalInteractor private val transitionState = MutableStateFlow<ObservableTransitionState>( @@ -116,11 +137,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val sceneContainerViewModel = SceneContainerViewModel( sceneInteractor = sceneInteractor, - falsingInteractor = utils.falsingInteractor(), + falsingInteractor = kosmos.falsingInteractor, ) .apply { setTransitionState(transitionState) } - private val bouncerInteractor = utils.bouncerInteractor() + private val bouncerInteractor = kosmos.bouncerInteractor private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor @@ -135,7 +156,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { KeyguardLongPressViewModel( interactor = mock(), ), - notifications = utils.notificationsPlaceholderViewModel(), + notifications = kosmos.notificationsPlaceholderViewModel, ) private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) @@ -152,21 +173,22 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { FakeMobileConnectionsRepository(), ), constants = mock(), - utils.fakeFeatureFlags, + flags = kosmos.fakeFeatureFlagsClassic, scope = testScope.backgroundScope, ) private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel private lateinit var shadeSceneViewModel: ShadeSceneViewModel - private val keyguardInteractor = utils.keyguardInteractor() - private val powerInteractor = utils.powerInteractor() + private val keyguardInteractor = kosmos.keyguardInteractor + private val powerInteractor = kosmos.powerInteractor private var bouncerSceneJob: Job? = null private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { mock() }) @Mock private lateinit var mediaDataManager: MediaDataManager + @Mock private lateinit var mediaHost: MediaHost private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager @@ -177,27 +199,27 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, true) - telecomManager = checkNotNull(utils.kosmos.telecomManager) + telecomManager = checkNotNull(kosmos.telecomManager) whenever(telecomManager.isInCall).thenReturn(false) - emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager + emergencyAffordanceManager = kosmos.emergencyAffordanceManager whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true) - utils.fakeFeatureFlags.apply { + kosmos.fakeFeatureFlagsClassic.apply { set(Flags.NEW_NETWORK_SLICE_UI, false) set(Flags.REFACTOR_GETCURRENTUSER, true) } - mobileConnectionsRepository = utils.mobileConnectionsRepository + mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository mobileConnectionsRepository.isAnySimSecure.value = false - utils.telephonyRepository.apply { + kosmos.fakeTelephonyRepository.apply { setHasTelephonyRadio(true) setCallState(TelephonyManager.CALL_STATE_IDLE) setIsInCall(false) } - bouncerActionButtonInteractor = utils.bouncerActionButtonInteractor() - bouncerViewModel = utils.bouncerViewModel() + bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor + bouncerViewModel = kosmos.bouncerViewModel shadeHeaderViewModel = ShadeHeaderViewModel( @@ -215,12 +237,12 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { deviceEntryInteractor = deviceEntryInteractor, shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, - notifications = utils.notificationsPlaceholderViewModel(), + notifications = kosmos.notificationsPlaceholderViewModel, mediaDataManager = mediaDataManager, mediaHost = mediaHost, ) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) val displayTracker = FakeDisplayTracker(context) val sysUiState = SysUiState(displayTracker) @@ -230,15 +252,15 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneInteractor = sceneInteractor, deviceEntryInteractor = deviceEntryInteractor, keyguardInteractor = keyguardInteractor, - flags = utils.fakeSceneContainerFlags, + flags = kosmos.fakeSceneContainerFlags, sysUiState = sysUiState, displayId = displayTracker.defaultDisplayId, sceneLogger = mock(), - falsingCollector = utils.falsingCollector(), + falsingCollector = kosmos.falsingCollector, powerInteractor = powerInteractor, bouncerInteractor = bouncerInteractor, - simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor }, - authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() }, + simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor }, + authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor }, windowController = mock(), ) startable.start() @@ -534,15 +556,15 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit // is not an observable that can trigger a new evaluation. - utils.deviceEntryRepository.setLockscreenEnabled(enableLockscreen) - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) runCurrent() } /** Emulates a phone call in progress. */ private fun TestScope.startPhoneCall() { whenever(telecomManager.isInCall).thenReturn(true) - utils.telephonyRepository.apply { + kosmos.fakeTelephonyRepository.apply { setHasTelephonyRadio(true) setIsInCall(true) setCallState(TelephonyManager.CALL_STATE_OFFHOOK) @@ -651,7 +673,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(authMethod.isSecure) .isTrue() - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() } @@ -665,7 +687,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { enterPin() // This repository state is not changed by the AuthInteractor, it relies on // KeyguardStateController. - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) emulateUiSceneTransition( expectedVisible = false, ) @@ -721,7 +743,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } pinBouncerViewModel.onAuthenticateButtonClicked() setAuthMethod(authMethodAfterSimUnlock) - utils.mobileConnectionsRepository.isAnySimSecure.value = false + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false runCurrent() } @@ -768,7 +790,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private fun TestScope.introduceLockedSim() { setAuthMethod(AuthenticationMethodModel.Sim) - utils.mobileConnectionsRepository.isAnySimSecure.value = true + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true runCurrent() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 339d0263a089..b267720971cd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -22,11 +22,14 @@ 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.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -39,12 +42,12 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SceneContainerRepositoryTest : SysuiTestCase() { - private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true } - private val testScope = utils.testScope + private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } + private val testScope = kosmos.testScope @Test fun allSceneKeys() { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository assertThat(underTest.allSceneKeys()) .isEqualTo( listOf( @@ -61,7 +64,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { @Test fun desiredScene() = testScope.runTest { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository val currentScene by collectLastValue(underTest.desiredScene) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) @@ -71,15 +74,15 @@ class SceneContainerRepositoryTest : SysuiTestCase() { @Test(expected = IllegalStateException::class) fun setDesiredScene_noSuchSceneInContainer_throws() { - utils.kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) - val underTest = utils.fakeSceneContainerRepository() + kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) + val underTest = kosmos.sceneContainerRepository underTest.setDesiredScene(SceneModel(SceneKey.Shade)) } @Test fun isVisible() = testScope.runTest { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository val isVisible by collectLastValue(underTest.isVisible) assertThat(isVisible).isTrue() @@ -93,19 +96,19 @@ class SceneContainerRepositoryTest : SysuiTestCase() { @Test fun transitionState_defaultsToIdle() = testScope.runTest { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository val transitionState by collectLastValue(underTest.transitionState) assertThat(transitionState) .isEqualTo( - ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey) ) } @Test fun transitionState_reflectsUpdates() = testScope.runTest { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(SceneKey.Lockscreen) @@ -134,7 +137,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { underTest.setTransitionState(null) assertThat(reflectedTransitionState) .isEqualTo( - ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey) ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 486f7ab6501d..d159986015a0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -20,10 +20,16 @@ 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.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.sceneContainerConfig +import com.android.systemui.scene.sceneKeys +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf @@ -36,20 +42,20 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SceneInteractorTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var underTest: SceneInteractor @Before fun setUp() { - utils.fakeSceneContainerFlags.enabled = true - underTest = utils.sceneInteractor() + kosmos.fakeSceneContainerFlags.enabled = true + underTest = kosmos.sceneInteractor } @Test fun allSceneKeys() { - assertThat(underTest.allSceneKeys()).isEqualTo(utils.fakeSceneKeys()) + assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys) } @Test @@ -75,7 +81,7 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun transitionState() = testScope.runTest { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(SceneKey.Lockscreen) @@ -104,7 +110,7 @@ class SceneInteractorTest : SysuiTestCase() { underTest.setTransitionState(null) assertThat(reflectedTransitionState) .isEqualTo( - ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey) ) } @@ -348,8 +354,8 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun userInput() = testScope.runTest { - assertThat(utils.powerRepository.userTouchRegistered).isFalse() + assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse() underTest.onUserInput() - assertThat(utils.powerRepository.userTouchRegistered).isTrue() + assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt index 8be4eeb7be7a..f23716ccca54 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.scene.domain.interactor +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.statusbar.IStatusBarService @@ -28,7 +30,11 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.init.NotificationsController +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any @@ -37,6 +43,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -50,6 +57,7 @@ import org.mockito.Mockito.verify class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { private val testScope = TestScope() + private val testDispatcher = StandardTestDispatcher() private val iStatusBarService = mock<IStatusBarService>() private val executor = FakeExecutor(FakeSystemClock()) private val windowRootViewVisibilityRepository = @@ -59,6 +67,9 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { private val notificationPresenter = mock<NotificationPresenter>() private val notificationsController = mock<NotificationsController>() private val powerInteractor = PowerInteractorFactory.create().powerInteractor + private val activeNotificationsRepository = ActiveNotificationListRepository() + private val activeNotificationsInteractor = + ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher) private val underTest = WindowRootViewVisibilityInteractor( @@ -67,6 +78,7 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { keyguardRepository, headsUpManager, powerInteractor, + activeNotificationsInteractor, ) .apply { setUp(notificationPresenter, notificationsController) } @@ -257,7 +269,8 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test - fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() = + @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOff_notifCountOne() = testScope.runTest { underTest.start() @@ -273,6 +286,23 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOn_notifCountOne() = + testScope.runTest { + underTest.start() + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + activeNotificationsRepository.setActiveNotifs(4) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(1) + } + + @Test fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() = testScope.runTest { underTest.start() @@ -288,7 +318,8 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test - fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() = + @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_noHeadsUp_flagOff_notifCountMatchesNotifController() = testScope.runTest { underTest.start() whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) @@ -304,7 +335,25 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test - fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() = + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_noHeadsUp_flagOn_notifCountMatchesNotifController() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + activeNotificationsRepository.setActiveNotifs(9) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(9) + } + + @Test + @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOff_notifCountMatchesNotifController() = testScope.runTest { underTest.start() whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) @@ -320,6 +369,23 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOn_notifCountMatchesNotifController() = + testScope.runTest { + underTest.start() + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false) + activeNotificationsRepository.setActiveNotifs(8) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(8) + } + + @Test fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() = testScope.runTest { underTest.start() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 5fe4ca1fbfe3..4afa5f2a44b9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -25,19 +25,31 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags as AconfigFlags import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.kosmos.testScope import com.android.systemui.model.SysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -65,15 +77,15 @@ class SceneContainerStartableTest : SysuiTestCase() { @Mock private lateinit var windowController: NotificationShadeWindowController - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() - private val sceneContainerFlags = utils.fakeSceneContainerFlags - private val authenticationInteractor = utils.authenticationInteractor() - private val bouncerInteractor = utils.bouncerInteractor() - private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository - private val deviceEntryInteractor = utils.deviceEntryInteractor() - private val keyguardInteractor = utils.keyguardInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor + private val sceneContainerFlags = kosmos.fakeSceneContainerFlags + private val authenticationInteractor = kosmos.authenticationInteractor + private val bouncerInteractor = kosmos.bouncerInteractor + private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository + private val deviceEntryInteractor = kosmos.deviceEntryInteractor + private val keyguardInteractor = kosmos.keyguardInteractor private val sysUiState: SysUiState = mock() private val falsingCollector: FalsingCollector = mock() private val powerInteractor = PowerInteractorFactory.create().powerInteractor @@ -97,7 +109,7 @@ class SceneContainerStartableTest : SysuiTestCase() { falsingCollector = falsingCollector, powerInteractor = powerInteractor, bouncerInteractor = bouncerInteractor, - simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor }, + simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor }, authenticationInteractor = dagger.Lazy { authenticationInteractor }, windowController = windowController, ) @@ -172,7 +184,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) underTest.start() - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -188,7 +200,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) underTest.start() - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -204,7 +216,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -222,7 +234,7 @@ class SceneContainerStartableTest : SysuiTestCase() { // Authenticate using a passive auth method like face auth while bypass is disabled. faceAuthRepository.isAuthenticated.value = true - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -244,7 +256,7 @@ class SceneContainerStartableTest : SysuiTestCase() { transitionStateFlowValue.value = ObservableTransitionState.Idle(SceneKey.Shade) assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) @@ -263,7 +275,7 @@ class SceneContainerStartableTest : SysuiTestCase() { // Authenticate using a passive auth method like face auth while bypass is disabled. faceAuthRepository.isAuthenticated.value = true - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -373,7 +385,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() powerInteractor.setAwakeForTest() runCurrent() @@ -463,11 +475,11 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() verify(falsingCollector).setShowingAod(false) - utils.keyguardRepository.setIsDozing(true) + kosmos.fakeKeyguardRepository.setIsDozing(true) runCurrent() verify(falsingCollector).setShowingAod(true) - utils.keyguardRepository.setIsDozing(false) + kosmos.fakeKeyguardRepository.setIsDozing(false) runCurrent() verify(falsingCollector, times(2)).setShowingAod(false) } @@ -493,7 +505,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun collectFalsingSignals_screenOnAndOff_aodUnavailable() = testScope.runTest { - utils.keyguardRepository.setAodAvailable(false) + kosmos.fakeKeyguardRepository.setAodAvailable(false) runCurrent() prepareState( initialSceneKey = SceneKey.Lockscreen, @@ -541,7 +553,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun collectFalsingSignals_screenOnAndOff_aodAvailable() = testScope.runTest { - utils.keyguardRepository.setAodAvailable(true) + kosmos.fakeKeyguardRepository.setAodAvailable(true) runCurrent() prepareState( initialSceneKey = SceneKey.Lockscreen, @@ -619,7 +631,7 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() runCurrent() - utils.mobileConnectionsRepository.isAnySimSecure.value = true + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true runCurrent() assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) @@ -628,7 +640,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchesToLockscreen_whenSimBecomesUnlocked() = testScope.runTest { - utils.mobileConnectionsRepository.isAnySimSecure.value = true + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( @@ -638,7 +650,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ) underTest.start() runCurrent() - utils.mobileConnectionsRepository.isAnySimSecure.value = false + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false runCurrent() assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) @@ -647,7 +659,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchesToGone_whenSimBecomesUnlocked_ifDeviceUnlockedAndLockscreenDisabled() = testScope.runTest { - utils.mobileConnectionsRepository.isAnySimSecure.value = true + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( @@ -658,7 +670,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ) underTest.start() runCurrent() - utils.mobileConnectionsRepository.isAnySimSecure.value = false + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false runCurrent() assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) @@ -730,8 +742,8 @@ class SceneContainerStartableTest : SysuiTestCase() { } } sceneContainerFlags.enabled = true - utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked) - utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled) + kosmos.fakeDeviceEntryRepository.setUnlocked(isDeviceUnlocked) + kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled) val transitionStateFlow = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(SceneKey.Lockscreen) @@ -743,8 +755,8 @@ class SceneContainerStartableTest : SysuiTestCase() { sceneInteractor.onSceneChanged(SceneModel(it), "reason") } authenticationMethod?.let { - utils.authenticationRepository.setAuthenticationMethod(authenticationMethod) - utils.deviceEntryRepository.setLockscreenEnabled( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled( isLockscreenEnabled = isLockscreenEnabled ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index 3a4ee642685f..ede453d85ee1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -21,10 +21,14 @@ package com.android.systemui.scene.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.domain.interactor.falsingInteractor import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.sceneKeys +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -36,17 +40,17 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SceneContainerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val interactor = utils.sceneInteractor() + private val kosmos = testKosmos() + private val interactor = kosmos.sceneInteractor private lateinit var underTest: SceneContainerViewModel @Before fun setUp() { - utils.fakeSceneContainerFlags.enabled = true + kosmos.fakeSceneContainerFlags.enabled = true underTest = SceneContainerViewModel( sceneInteractor = interactor, - falsingInteractor = utils.falsingInteractor(), + falsingInteractor = kosmos.falsingInteractor, ) } @@ -64,7 +68,7 @@ class SceneContainerViewModelTest : SysuiTestCase() { @Test fun allSceneKeys() { - assertThat(underTest.allSceneKeys).isEqualTo(utils.fakeSceneKeys()) + assertThat(underTest.allSceneKeys).isEqualTo(kosmos.sceneKeys) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index 77ddf15c41ad..51745023c5be 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -7,7 +7,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository @@ -18,6 +19,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow @@ -31,9 +33,9 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) class ShadeHeaderViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index a8133a3a4de3..bf873c2dfb52 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -19,16 +19,21 @@ package com.android.systemui.shade.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository @@ -36,6 +41,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -53,10 +59,10 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class ShadeSceneViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() - private val deviceEntryInteractor = utils.deviceEntryInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor + private val deviceEntryInteractor = kosmos.deviceEntryInteractor private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } @@ -105,7 +111,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { deviceEntryInteractor = deviceEntryInteractor, shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, - notifications = utils.notificationsPlaceholderViewModel(), + notifications = kosmos.notificationsPlaceholderViewModel, mediaDataManager = mediaDataManager, mediaHost = mediaHost, ) @@ -115,8 +121,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_deviceLocked_lockScreen() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -125,8 +133,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_deviceUnlocked_gone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) } @@ -135,8 +145,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.deviceEntryRepository.setLockscreenEnabled(true) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason") @@ -147,8 +159,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.deviceEntryRepository.setLockscreenEnabled(true) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason") @@ -159,8 +173,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() underTest.onContentClicked() @@ -172,8 +188,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() underTest.onContentClicked() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt index 0a10b2c85ebe..0c7ce970cf3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt @@ -16,11 +16,10 @@ package com.android.systemui.statusbar.notification.collection.render +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -34,18 +33,19 @@ import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @SmallTest +@RunWith(AndroidJUnit4::class) class GroupExpansionManagerTest : SysuiTestCase() { - private lateinit var gem: GroupExpansionManagerImpl + private lateinit var underTest: GroupExpansionManagerImpl private val dumpManager: DumpManager = mock() private val groupMembershipManager: GroupMembershipManager = mock() - private val featureFlags = FakeFeatureFlagsClassic() private val pipeline: NotifPipeline = mock() private lateinit var beforeRenderListListener: OnBeforeRenderListListener @@ -85,79 +85,57 @@ class GroupExpansionManagerTest : SysuiTestCase() { whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1) whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2) - gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags) + underTest = GroupExpansionManagerImpl(dumpManager, groupMembershipManager) } @Test - fun testNotifyOnlyOnChange_enabled() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun notifyOnlyOnChange() { var listenerCalledCount = 0 - gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } + underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } - gem.setGroupExpanded(summary1, false) + underTest.setGroupExpanded(summary1, false) assertThat(listenerCalledCount).isEqualTo(0) - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary1, true) assertThat(listenerCalledCount).isEqualTo(1) - gem.setGroupExpanded(summary2, true) - assertThat(listenerCalledCount).isEqualTo(2) - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary2, true) assertThat(listenerCalledCount).isEqualTo(2) - gem.setGroupExpanded(summary2, false) - assertThat(listenerCalledCount).isEqualTo(3) - } - - @Test - fun testNotifyOnlyOnChange_disabled() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - - var listenerCalledCount = 0 - gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ } - - gem.setGroupExpanded(summary1, false) - assertThat(listenerCalledCount).isEqualTo(1) - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary1, true) assertThat(listenerCalledCount).isEqualTo(2) - gem.setGroupExpanded(summary2, true) + underTest.setGroupExpanded(summary2, false) assertThat(listenerCalledCount).isEqualTo(3) - gem.setGroupExpanded(summary1, true) - assertThat(listenerCalledCount).isEqualTo(4) - gem.setGroupExpanded(summary2, false) - assertThat(listenerCalledCount).isEqualTo(5) } @Test - fun testExpandUnattachedEntry() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun expandUnattachedEntry() { // First, expand the entry when it is attached. - gem.setGroupExpanded(summary1, true) - assertThat(gem.isGroupExpanded(summary1)).isTrue() + underTest.setGroupExpanded(summary1, true) + assertThat(underTest.isGroupExpanded(summary1)).isTrue() // Un-attach it, and un-expand it. NotificationEntryBuilder.setNewParent(summary1, null) - gem.setGroupExpanded(summary1, false) + underTest.setGroupExpanded(summary1, false) // Expanding again should throw. - assertThrows(IllegalArgumentException::class.java) { gem.setGroupExpanded(summary1, true) } + assertThrows(IllegalArgumentException::class.java) { + underTest.setGroupExpanded(summary1, true) + } } @Test - fun testSyncWithPipeline() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - gem.attach(pipeline) + fun syncWithPipeline() { + underTest.attach(pipeline) beforeRenderListListener = withArgCaptor { verify(pipeline).addOnBeforeRenderListListener(capture()) } val listener: OnGroupExpansionChangeListener = mock() - gem.registerGroupExpansionChangeListener(listener) + underTest.registerGroupExpansionChangeListener(listener) beforeRenderListListener.onBeforeRenderList(entries) verify(listener, never()).onGroupExpansionChange(any(), any()) // Expand one of the groups. - gem.setGroupExpanded(summary1, true) + underTest.setGroupExpanded(summary1, true) verify(listener).onGroupExpansionChange(summary1.row, true) // Empty the pipeline list and verify that the group is no longer expanded. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt index c1ffa641c6a4..2cbcc5a8d925 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt @@ -16,67 +16,35 @@ package com.android.systemui.statusbar.notification.collection.render +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.google.common.truth.Truth.assertThat -import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith @SmallTest +@RunWith(AndroidJUnit4::class) class GroupMembershipManagerTest : SysuiTestCase() { - private lateinit var gmm: GroupMembershipManagerImpl - - private val featureFlags = FakeFeatureFlagsClassic() - - @Before - fun setUp() { - gmm = GroupMembershipManagerImpl(featureFlags) - } + private var underTest = GroupMembershipManagerImpl() @Test - fun testIsChildInGroup_topLevel() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) + fun isChildInGroup_topLevel() { val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() - assertThat(gmm.isChildInGroup(topLevelEntry)).isFalse() - } - - @Test - fun testIsChildInGroup_noParent_old() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - val noParentEntry = NotificationEntryBuilder().setParent(null).build() - assertThat(gmm.isChildInGroup(noParentEntry)).isTrue() + assertThat(underTest.isChildInGroup(topLevelEntry)).isFalse() } @Test - fun testIsChildInGroup_noParent_new() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + fun isChildInGroup_noParent() { val noParentEntry = NotificationEntryBuilder().setParent(null).build() - assertThat(gmm.isChildInGroup(noParentEntry)).isFalse() - } - @Test - fun testIsChildInGroup_summary_old() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - - val groupKey = "group" - val summary = - NotificationEntryBuilder() - .setGroup(mContext, groupKey) - .setGroupSummary(mContext, true) - .build() - GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - - assertThat(gmm.isChildInGroup(summary)).isTrue() + assertThat(underTest.isChildInGroup(noParentEntry)).isFalse() } @Test - fun testIsChildInGroup_summary_new() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun isChildInGroup_summary() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -85,27 +53,17 @@ class GroupMembershipManagerTest : SysuiTestCase() { .build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(gmm.isChildInGroup(summary)).isFalse() + assertThat(underTest.isChildInGroup(summary)).isFalse() } @Test - fun testIsChildInGroup_child() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false) - val childEntry = NotificationEntryBuilder().build() - assertThat(gmm.isChildInGroup(childEntry)).isTrue() - } - - @Test - fun testIsGroupSummary_topLevelEntry() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + fun isGroupSummary_topLevelEntry() { val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() - assertThat(gmm.isGroupSummary(entry)).isFalse() + assertThat(underTest.isGroupSummary(entry)).isFalse() } @Test - fun testIsGroupSummary_summary() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun isGroupSummary_summary() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -114,13 +72,11 @@ class GroupMembershipManagerTest : SysuiTestCase() { .build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(gmm.isGroupSummary(summary)).isTrue() + assertThat(underTest.isGroupSummary(summary)).isTrue() } @Test - fun testIsGroupSummary_child() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun isGroupSummary_child() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -130,20 +86,17 @@ class GroupMembershipManagerTest : SysuiTestCase() { val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build() - assertThat(gmm.isGroupSummary(entry)).isFalse() + assertThat(underTest.isGroupSummary(entry)).isFalse() } @Test - fun testGetGroupSummary_topLevelEntry() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) + fun getGroupSummary_topLevelEntry() { val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build() - assertThat(gmm.getGroupSummary(entry)).isNull() + assertThat(underTest.getGroupSummary(entry)).isNull() } @Test - fun testGetGroupSummary_summary() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun getGroupSummary_summary() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -152,13 +105,11 @@ class GroupMembershipManagerTest : SysuiTestCase() { .build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).build() - assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary) + assertThat(underTest.getGroupSummary(summary)).isEqualTo(summary) } @Test - fun testGetGroupSummary_child() { - featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true) - + fun getGroupSummary_child() { val groupKey = "group" val summary = NotificationEntryBuilder() @@ -168,6 +119,6 @@ class GroupMembershipManagerTest : SysuiTestCase() { val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build() GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build() - assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary) + assertThat(underTest.getGroupSummary(entry)).isEqualTo(summary) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt index 262795fe0eb6..8e8e5106e93c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt @@ -24,12 +24,13 @@ 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.scene.SceneTestUtils +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.telephony.TelephonyListenerManager +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.kotlinArgumentCaptor import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -39,7 +40,6 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class TelephonyRepositoryImplTest : SysuiTestCase() { @@ -47,8 +47,8 @@ class TelephonyRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var manager: TelephonyListenerManager @Mock private lateinit var telecomManager: TelecomManager - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var underTest: TelephonyRepositoryImpl @@ -61,7 +61,7 @@ class TelephonyRepositoryImplTest : SysuiTestCase() { TelephonyRepositoryImpl( applicationScope = testScope.backgroundScope, applicationContext = context, - backgroundDispatcher = utils.testDispatcher, + backgroundDispatcher = kosmos.testDispatcher, manager = manager, telecomManager = telecomManager, ) diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml new file mode 100644 index 000000000000..84b89ca68e65 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_focused="true"> + <shape android:shape="rectangle"> + <corners android:radius="16dp" /> + <!--By default this outline will not show hence 0 width. + width is set programmatically when needed and is gated by the flag: + com.android.systemui.Flags.pinInputFieldStyledFocusState--> + <stroke android:width="0dp" + android:color="@color/bouncer_password_focus_color" /> + </shape> + </item> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml index 66c54f2a668e..0b35559148af 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml @@ -23,7 +23,7 @@ android:layout_marginTop="@dimen/keyguard_lock_padding" android:importantForAccessibility="no" android:ellipsize="marquee" - android:focusable="true" + android:focusable="false" android:gravity="center" android:singleLine="true" /> </merge> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 0628c3e957b1..ddad1e3f8940 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -25,6 +25,12 @@ <!-- Maximum width of the sliding KeyguardSecurityContainer --> <dimen name="keyguard_security_width">420dp</dimen> + <!-- Width for the keyguard pin input field --> + <dimen name="keyguard_pin_field_width">292dp</dimen> + + <!-- Width for the keyguard pin input field --> + <dimen name="keyguard_pin_field_height">48dp</dimen> + <!-- Height of the sliding KeyguardSecurityContainer (includes 2x keyguard_security_view_top_margin) --> <dimen name="keyguard_security_height">420dp</dimen> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index 565ed1085fb9..f51e1098f333 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -62,10 +62,10 @@ <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string> <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. --> - <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging optimized to protect battery</string> + <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging on hold to protect battery</string> <!-- When the lock screen is showing and the phone plugged in with incompatible charger. --> - <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Issue with charging accessory</string> + <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Check charging accessory</string> <!-- SIM messages --><skip /> <!-- When the user inserts a sim card from an unsupported network, it becomes network locked --> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 2cca9510417a..4789a229c4d0 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -76,6 +76,7 @@ </style> <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item> <item name="android:gravity">center</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml index 23fbb12f3036..10f71134c4cc 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml @@ -20,6 +20,13 @@ android:layout_height="wrap_content" android:orientation="vertical"> + <ImageView + android:id="@+id/logo" + android:layout_width="@dimen/biometric_auth_icon_size" + android:layout_height="@dimen/biometric_auth_icon_size" + android:layout_gravity="center" + android:scaleType="fitXY"/> + <TextView android:id="@+id/title" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index 85b6e8dc12b3..5db9eee6a908 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -50,7 +50,10 @@ app:layout_constraintStart_toStartOf="@id/album_art" app:layout_constraintEnd_toEndOf="@id/album_art" app:layout_constraintTop_toTopOf="@id/album_art" - app:layout_constraintBottom_toBottomOf="@id/album_art" /> + app:layout_constraintBottom_toBottomOf="@id/album_art" + android:clipToOutline="true" + android:background="@drawable/qs_media_outline_layout_bg" + /> <com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView android:id="@+id/turbulence_noise_view" @@ -59,7 +62,10 @@ app:layout_constraintStart_toStartOf="@id/album_art" app:layout_constraintEnd_toEndOf="@id/album_art" app:layout_constraintTop_toTopOf="@id/album_art" - app:layout_constraintBottom_toBottomOf="@id/album_art" /> + app:layout_constraintBottom_toBottomOf="@id/album_art" + android:clipToOutline="true" + android:background="@drawable/qs_media_outline_layout_bg" + /> <!-- Guideline for output switcher --> <androidx.constraintlayout.widget.Guideline diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index bcc3c83b4560..61a323d44dfc 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -93,6 +93,8 @@ <color name="qs_user_switcher_selected_avatar_icon_color">#202124</color> <!-- Color of background circle of user avatars in quick settings user switcher --> <color name="qs_user_switcher_avatar_background">#3C4043</color> + <!-- Color of border for keyguard password input when focused --> + <color name="bouncer_password_focus_color">@*android:color/system_secondary_dark</color> <!-- Accessibility floating menu --> <color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% --> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 8be1cc7282e3..3839dd98cdd3 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -56,6 +56,8 @@ <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color> <!-- Color of background circle of user avatars in keyguard user switcher --> <color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color> + <!-- Color of border for keyguard password input when focused --> + <color name="bouncer_password_focus_color">@*android:color/system_secondary_light</color> <!-- Icon color for user avatars in user switcher quick settings --> <color name="qs_user_switcher_avatar_icon_color">#3C4043</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 798fc06b44f7..ee2a1ceab2b7 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -897,6 +897,10 @@ <dimen name="communal_tutorial_indicator_padding">24dp</dimen> <dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen> + <!-- Size of the maximum radius for the enforced rounded rectangles on communal hub. + Keep it the same as in Launcher--> + <dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen> + <!-- The width/height of the unlock icon view on keyguard. --> <dimen name="keyguard_lock_height">42dp</dimen> <dimen name="keyguard_lock_padding">20dp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 6e035e8c8c36..ec4c7d5bf67e 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -249,6 +249,9 @@ --> <item type="id" name="tag_smartspace_view" /> + <!-- ID of the Scene Container root Composable view --> + <item type='id' name="scene_container_root_composable" /> + <!-- Tag set on the Compose implementation of the QS footer actions. --> <item type="id" name="tag_compose_qs_footer_actions" /> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 66f965aea76f..efd8f7f97ca3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -37,6 +37,7 @@ import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyboard.data.repository.KeyboardRepository; import com.android.systemui.log.BouncerLogger; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; @@ -210,6 +211,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final FeatureFlags mFeatureFlags; private final SelectedUserInteractor mSelectedUserInteractor; private final UiEventLogger mUiEventLogger; + private final KeyboardRepository mKeyboardRepository; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -223,7 +225,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> DevicePostureController devicePostureController, KeyguardViewController keyguardViewController, FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + KeyboardRepository keyboardRepository) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -240,6 +243,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mFeatureFlags = featureFlags; mSelectedUserInteractor = selectedUserInteractor; mUiEventLogger = uiEventLogger; + mKeyboardRepository = keyboardRepository; } /** Create a new {@link KeyguardInputViewController}. */ @@ -268,19 +272,22 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, emergencyButtonController, mFalsingCollector, mDevicePostureController, mFeatureFlags, mSelectedUserInteractor, - mUiEventLogger); + mUiEventLogger, mKeyboardRepository + ); } else if (keyguardInputView instanceof KeyguardSimPinView) { return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, - emergencyButtonController, mFeatureFlags, mSelectedUserInteractor); + emergencyButtonController, mFeatureFlags, mSelectedUserInteractor, + mKeyboardRepository); } else if (keyguardInputView instanceof KeyguardSimPukView) { return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, mTelephonyManager, mFalsingCollector, - emergencyButtonController, mFeatureFlags, mSelectedUserInteractor); + emergencyButtonController, mFeatureFlags, mSelectedUserInteractor, + mKeyboardRepository); } throw new RuntimeException("Unable to find controller for " + keyguardInputView); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index 36fe75f69a45..fcff0dbc0878 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -25,6 +25,7 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FO import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; +import static com.android.systemui.Flags.pinInputFieldStyledFocusState; import android.animation.Animator; import android.animation.AnimatorSet; @@ -168,7 +169,9 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView // Set selected property on so the view can send accessibility events. mPasswordEntry.setSelected(true); - mPasswordEntry.setDefaultFocusHighlightEnabled(false); + if (!pinInputFieldStyledFocusState()) { + mPasswordEntry.setDefaultFocusHighlightEnabled(false); + } mOkButton = findViewById(R.id.key_enter); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java index 376933dc5519..60dd5686c315 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -16,17 +16,25 @@ package com.android.keyguard; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import static com.android.systemui.Flags.pinInputFieldStyledFocusState; + +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.StateListDrawable; +import android.util.TypedValue; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.View.OnKeyListener; import android.view.View.OnTouchListener; +import android.view.ViewGroup; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyboard.data.repository.KeyboardRepository; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -35,6 +43,7 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB private final LiftToActivateListener mLiftToActivateListener; private final FalsingCollector mFalsingCollector; + private final KeyboardRepository mKeyboardRepository; protected PasswordTextView mPasswordEntry; private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> { @@ -65,12 +74,14 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector, FeatureFlags featureFlags, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, + KeyboardRepository keyboardRepository) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, falsingCollector, emergencyButtonController, featureFlags, selectedUserInteractor); mLiftToActivateListener = liftToActivateListener; mFalsingCollector = falsingCollector; + mKeyboardRepository = keyboardRepository; mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); } @@ -120,6 +131,35 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB }); okButton.setOnHoverListener(mLiftToActivateListener); } + if (pinInputFieldStyledFocusState()) { + collectFlow(mPasswordEntry, mKeyboardRepository.isAnyKeyboardConnected(), + this::setKeyboardBasedFocusOutline); + + /** + * new UI Specs for PIN Input field have new dimensions go/pin-focus-states. + * However we want these changes behind a flag, and resource files cannot be flagged + * hence the dimension change in code. When the flags are removed these dimensions + * should be set in resources permanently and the code below removed. + */ + ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams(); + layoutParams.width = (int) getResources().getDimension( + R.dimen.keyguard_pin_field_width); + layoutParams.height = (int) getResources().getDimension( + R.dimen.keyguard_pin_field_height); + } + } + + private void setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected) { + StateListDrawable background = (StateListDrawable) mPasswordEntry.getBackground(); + GradientDrawable stateDrawable = (GradientDrawable) background.getStateDrawable(0); + int color = getResources().getColor(R.color.bouncer_password_focus_color); + if (!isAnyKeyboardConnected) { + stateDrawable.setStroke(0, color); + } else { + int strokeWidthInDP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, + getResources().getDisplayMetrics()); + stateDrawable.setStroke(strokeWidthInDP, color); + } } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 2aab1f189263..b958f55bdf79 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.keyboard.data.repository.KeyboardRepository; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -58,12 +59,13 @@ public class KeyguardPinViewController LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector, - DevicePostureController postureController, - FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor, - UiEventLogger uiEventLogger) { + DevicePostureController postureController, FeatureFlags featureFlags, + SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger, + KeyboardRepository keyboardRepository) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor); + emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, + keyboardRepository); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mPostureController = postureController; mLockPatternUtils = lockPatternUtils; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 5729119a582a..1cdcbd06815f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -44,6 +44,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyboard.data.repository.KeyboardRepository; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -93,10 +94,11 @@ public class KeyguardSimPinViewController LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor); + emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, + keyboardRepository); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 05fb5fa75e9e..f019d61a3ccc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -39,6 +39,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.keyboard.data.repository.KeyboardRepository; import com.android.systemui.res.R; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -90,10 +91,11 @@ public class KeyguardSimPukViewController LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, - emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor); + emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor, + keyboardRepository); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mTelephonyManager = telephonyManager; mSimImageView = mView.findViewById(R.id.keyguard_sim); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt index b15aaaf9a00e..e88aaf015f87 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt +++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt @@ -91,7 +91,7 @@ abstract class SystemUIAppComponentFactoryBase : AppComponentFactory() { return app } - @UsesReflection(KeepTarget(extendsClassConstant = SysUIComponent::class, methodName = "inject")) + @UsesReflection(KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject")) override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider { val contentProvider = super.instantiateProviderCompat(cl, className) if (contentProvider is ContextInitializer) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt index 6483ae44d5ec..c7e5b645db5f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt @@ -19,16 +19,20 @@ package com.android.systemui.accessibility.data.repository import android.os.UserHandle import android.provider.Settings.Secure import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext /** Provides data related to color correction. */ @@ -45,22 +49,24 @@ class ColorCorrectionRepositoryImpl @Inject constructor( @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, private val secureSettings: SecureSettings, ) : ColorCorrectionRepository { - companion object { - const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED - const val DISABLED = 0 - const val ENABLED = 1 - } + private val userMap = mutableMapOf<Int, Flow<Boolean>>() override fun isEnabled(userHandle: UserHandle): Flow<Boolean> = - secureSettings - .observerFlow(userHandle.identifier, SETTING_NAME) - .onStart { emit(Unit) } - .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED } - .distinctUntilChanged() - .flowOn(bgCoroutineContext) + userMap.getOrPut(userHandle.identifier) { + secureSettings + .observerFlow(userHandle.identifier, SETTING_NAME) + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED + } + .distinctUntilChanged() + .flowOn(bgCoroutineContext) + .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1) + } override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean = withContext(bgCoroutineContext) { @@ -70,4 +76,10 @@ constructor( userHandle.identifier ) } + + companion object { + private const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED + private const val DISABLED = 0 + private const val ENABLED = 1 + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt index bbf10c509e50..419eada91f87 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt @@ -19,16 +19,20 @@ package com.android.systemui.accessibility.data.repository import android.os.UserHandle import android.provider.Settings.Secure import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext /** Provides data related to color inversion. */ @@ -45,16 +49,24 @@ class ColorInversionRepositoryImpl @Inject constructor( @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, private val secureSettings: SecureSettings, ) : ColorInversionRepository { + private val userMap = mutableMapOf<Int, Flow<Boolean>>() + override fun isEnabled(userHandle: UserHandle): Flow<Boolean> = - secureSettings - .observerFlow(userHandle.identifier, SETTING_NAME) - .onStart { emit(Unit) } - .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED } - .distinctUntilChanged() - .flowOn(bgCoroutineContext) + userMap.getOrPut(userHandle.identifier) { + secureSettings + .observerFlow(userHandle.identifier, SETTING_NAME) + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED + } + .distinctUntilChanged() + .flowOn(bgCoroutineContext) + .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1) + } override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean = withContext(bgCoroutineContext) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index ab23564a1df4..57e308ff16e8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -399,7 +399,8 @@ public class AuthContainerView extends LinearLayout config.mPromptInfo, config.mUserId, config.mOperationId, - new BiometricModalities(fpProps, faceProps)); + new BiometricModalities(fpProps, faceProps), + config.mOpPackageName); final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate( R.layout.biometric_prompt_layout, null, false); @@ -470,7 +471,8 @@ public class AuthContainerView extends LinearLayout mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication( - mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId); + mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId, + mConfig.mOpPackageName); final CredentialViewModel vm = mCredentialViewModelProvider.get(); vm.setAnimateContents(animateContents); ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java index 86802a5b58b0..02eae9cedf74 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java @@ -148,6 +148,12 @@ public class UdfpsDialogMeasureAdapter { || child.getId() == R.id.customized_view_container) { //skip description view and compute later continue; + } else if (child.getId() == R.id.logo) { + child.measure( + MeasureSpec.makeMeasureSpec(child.getLayoutParams().width, + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, + MeasureSpec.EXACTLY)); } else { child.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt index b35fbbc7bb32..ad7bb0e61178 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt @@ -55,6 +55,9 @@ interface PromptRepository { /** The kind of credential to use (biometric, pin, pattern, etc.). */ val kind: StateFlow<PromptKind> + /** The package name that the prompt is called from. */ + val opPackageName: StateFlow<String?> + /** * If explicit confirmation is required. * @@ -68,6 +71,7 @@ interface PromptRepository { userId: Int, gatekeeperChallenge: Long?, kind: PromptKind, + opPackageName: String, ) /** Unset the prompt info. */ @@ -108,6 +112,9 @@ constructor( private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric()) override val kind = _kind.asStateFlow() + private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null) + override val opPackageName = _opPackageName.asStateFlow() + private val _faceSettings = _userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged() private val _faceSettingAlwaysRequireConfirmation = @@ -127,11 +134,13 @@ constructor( userId: Int, gatekeeperChallenge: Long?, kind: PromptKind, + opPackageName: String, ) { _kind.value = kind _userId.value = userId _challenge.value = gatekeeperChallenge _promptInfo.value = promptInfo + _opPackageName.value = opPackageName } override fun unsetPrompt() { @@ -139,6 +148,7 @@ constructor( _userId.value = null _challenge.value = null _kind.value = PromptKind.Biometric() + _opPackageName.value = null } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt index ac4b717a23ec..359e2e703ee6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt @@ -115,12 +115,14 @@ constructor( @Utils.CredentialType kind: Int, userId: Int, challenge: Long, + opPackageName: String, ) { biometricPromptRepository.setPrompt( promptInfo, userId, challenge, - kind.asBiometricPromptCredential() + kind.asBiometricPromptCredential(), + opPackageName, ) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt index 65a2c0a2490f..b3f95748ccdc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt @@ -76,6 +76,7 @@ interface PromptSelectorInteractor { userId: Int, challenge: Long, modalities: BiometricModalities, + opPackageName: String, ) /** Use credential-based authentication instead of biometrics. */ @@ -84,6 +85,7 @@ interface PromptSelectorInteractor { @Utils.CredentialType kind: Int, userId: Int, challenge: Long, + opPackageName: String, ) /** Unset the current authentication request. */ @@ -104,9 +106,12 @@ constructor( promptRepository.promptInfo, promptRepository.challenge, promptRepository.userId, - promptRepository.kind - ) { promptInfo, challenge, userId, kind -> - if (promptInfo == null || userId == null || challenge == null) { + promptRepository.kind, + promptRepository.opPackageName, + ) { promptInfo, challenge, userId, kind, opPackageName -> + if ( + promptInfo == null || userId == null || challenge == null || opPackageName == null + ) { return@combine null } @@ -117,6 +122,7 @@ constructor( userInfo = BiometricUserInfo(userId = userId), operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge), modalities = kind.activeModalities, + opPackageName = opPackageName, ) else -> null } @@ -152,13 +158,15 @@ constructor( promptInfo: PromptInfo, userId: Int, challenge: Long, - modalities: BiometricModalities + modalities: BiometricModalities, + opPackageName: String, ) { promptRepository.setPrompt( promptInfo = promptInfo, userId = userId, gatekeeperChallenge = challenge, kind = PromptKind.Biometric(modalities), + opPackageName = opPackageName, ) } @@ -167,12 +175,14 @@ constructor( @Utils.CredentialType kind: Int, userId: Int, challenge: Long, + opPackageName: String, ) { promptRepository.setPrompt( promptInfo = promptInfo, userId = userId, gatekeeperChallenge = challenge, kind = kind.asBiometricPromptCredential(), + opPackageName = opPackageName, ) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt index 437793798567..c17c8dced668 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt @@ -1,5 +1,6 @@ package com.android.systemui.biometrics.domain.model +import android.graphics.Bitmap import android.hardware.biometrics.PromptContentView import android.hardware.biometrics.PromptInfo import com.android.systemui.biometrics.shared.model.BiometricModalities @@ -26,6 +27,7 @@ sealed class BiometricPromptRequest( userInfo: BiometricUserInfo, operationInfo: BiometricOperationInfo, val modalities: BiometricModalities, + val opPackageName: String, ) : BiometricPromptRequest( title = info.title?.toString() ?: "", @@ -36,6 +38,8 @@ sealed class BiometricPromptRequest( showEmergencyCallButton = info.isShowEmergencyCallButton ) { val contentView: PromptContentView? = info.contentView + val logoRes: Int = info.logoRes + val logoBitmap: Bitmap? = info.logoBitmap val negativeButtonText: String = info.negativeButtonText?.toString() ?: "" } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java index 60b454e9670e..b450896729b7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java @@ -115,6 +115,12 @@ public class BiometricPromptLayout extends LinearLayout { MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height, MeasureSpec.EXACTLY)); + } else if (child.getId() == R.id.logo) { + child.measure( + MeasureSpec.makeMeasureSpec(child.getLayoutParams().width, + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, + MeasureSpec.EXACTLY)); } else if (child.getId() == R.id.biometric_icon) { child.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt index 22b02da5a7d5..16e7f05fae37 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt @@ -20,9 +20,9 @@ import android.content.Context import android.content.res.Resources import android.content.res.Resources.Theme import android.graphics.Paint -import android.hardware.biometrics.PromptContentListItem -import android.hardware.biometrics.PromptContentListItemBulletedText -import android.hardware.biometrics.PromptContentListItemPlainText +import android.hardware.biometrics.PromptContentItem +import android.hardware.biometrics.PromptContentItemBulletedText +import android.hardware.biometrics.PromptContentItemPlainText import android.hardware.biometrics.PromptContentView import android.hardware.biometrics.PromptVerticalListContentView import android.text.SpannableString @@ -111,21 +111,21 @@ private fun createNewRowLayout(inflater: LayoutInflater): LinearLayout { return inflater.inflate(R.layout.biometric_prompt_content_row_layout, null) as LinearLayout } -private fun PromptContentListItem.doesExceedMaxLinesIfTwoColumn( +private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn( resources: Resources, ): Boolean { val passedInText: CharSequence = when (this) { - is PromptContentListItemPlainText -> text - is PromptContentListItemBulletedText -> text + is PromptContentItemPlainText -> text + is PromptContentItemBulletedText -> text else -> { - throw IllegalStateException("No such ListItem: $this") + throw IllegalStateException("No such PromptContentItem: $this") } } when (this) { - is PromptContentListItemPlainText, - is PromptContentListItemBulletedText -> { + is PromptContentItemPlainText, + is PromptContentItemBulletedText -> { val dialogMargin = resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding) val halfDialogWidth = @@ -155,12 +155,12 @@ private fun PromptContentListItem.doesExceedMaxLinesIfTwoColumn( return numLines > maxLines } else -> { - throw IllegalStateException("No such ListItem: $this") + throw IllegalStateException("No such PromptContentItem: $this") } } } -private fun PromptContentListItem.toView( +private fun PromptContentItem.toView( resources: Resources, inflater: LayoutInflater, theme: Theme, @@ -171,10 +171,10 @@ private fun PromptContentListItem.toView( textView.layoutParams = lp when (this) { - is PromptContentListItemPlainText -> { + is PromptContentItemPlainText -> { textView.text = text } - is PromptContentListItemBulletedText -> { + is PromptContentItemBulletedText -> { val bulletedText = SpannableString(text) val span = BulletSpan( @@ -186,25 +186,25 @@ private fun PromptContentListItem.toView( textView.text = bulletedText } else -> { - throw IllegalStateException("No such ListItem: $this") + throw IllegalStateException("No such PromptContentItem: $this") } } return textView } -private fun PromptContentListItem.getListItemPadding(resources: Resources): Int { +private fun PromptContentItem.getListItemPadding(resources: Resources): Int { var listItemPadding = resources.getDimensionPixelSize( R.dimen.biometric_prompt_content_list_item_padding_horizontal ) * 2 when (this) { - is PromptContentListItemPlainText -> {} - is PromptContentListItemBulletedText -> { + is PromptContentItemPlainText -> {} + is PromptContentItemBulletedText -> { listItemPadding += getListItemBulletRadius(resources) * 2 + getListItemBulletGapWidth(resources) } else -> { - throw IllegalStateException("No such ListItem: $this") + throw IllegalStateException("No such PromptContentItem: $this") } } return listItemPadding diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 04dc7a8da9f3..285ab4a800b6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -31,6 +31,7 @@ import android.view.View import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO import android.view.accessibility.AccessibilityManager import android.widget.Button +import android.widget.ImageView import android.widget.ScrollView import android.widget.TextView import androidx.lifecycle.DefaultLifecycleObserver @@ -92,6 +93,7 @@ object BiometricViewBinder { val textColorHint = view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme) + val logoView = view.requireViewById<ImageView>(R.id.logo) val titleView = view.requireViewById<TextView>(R.id.title) val subtitleView = view.requireViewById<TextView>(R.id.subtitle) val descriptionView = view.requireViewById<TextView>(R.id.description) @@ -99,6 +101,8 @@ object BiometricViewBinder { view.requireViewById<ScrollView>(R.id.customized_view_container) // set selected to enable marquee unless a screen reader is enabled + logoView.isSelected = + !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled titleView.isSelected = !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled subtitleView.isSelected = @@ -152,6 +156,7 @@ object BiometricViewBinder { } } + logoView.setImageDrawable(viewModel.logo.first()) titleView.text = viewModel.title.first() subtitleView.text = viewModel.subtitle.first() descriptionView.text = viewModel.description.first() @@ -183,6 +188,7 @@ object BiometricViewBinder { viewModel = viewModel, viewsToHideWhenSmall = listOf( + logoView, titleView, subtitleView, descriptionView, @@ -190,6 +196,7 @@ object BiometricViewBinder { ), viewsToFadeInOnSizeChange = listOf( + logoView, titleView, subtitleView, descriptionView, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index c3bbaedb2670..d5695f31f121 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -25,6 +25,7 @@ import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowManager import android.view.accessibility.AccessibilityManager +import android.widget.ImageView import android.widget.TextView import androidx.core.animation.addListener import androidx.core.view.doOnLayout @@ -234,7 +235,13 @@ private fun View.isLandscape(): Boolean { private fun View.showContentOrHide(forceHide: Boolean = false) { val isTextViewWithBlankText = this is TextView && this.text.isBlank() - visibility = if (forceHide || isTextViewWithBlankText) View.GONE else View.VISIBLE + val isImageViewWithoutImage = this is ImageView && this.drawable == null + visibility = + if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) { + View.GONE + } else { + View.VISIBLE + } } private fun View.asVerticalAnimator( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 1c789283ec70..dca0338dc8e7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -18,6 +18,8 @@ package com.android.systemui.biometrics.ui.viewmodel import android.content.Context import android.graphics.Rect +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable import android.hardware.biometrics.BiometricPrompt import android.hardware.biometrics.PromptContentView import android.util.Log @@ -233,6 +235,19 @@ constructor( } } + /** Logo for the prompt. */ + val logo: Flow<Drawable?> = + promptSelectorInteractor.prompt + .map { + when { + it == null -> null + it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme) + it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap) + else -> context.packageManager.getApplicationIcon(it.opPackageName) + } + } + .distinctUntilChanged() + /** Title for the prompt. */ val title: Flow<String> = promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index 10768ea6122a..dc07c1b25678 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -18,6 +18,7 @@ package com.android.systemui.communal.dagger import com.android.systemui.communal.data.db.CommunalDatabaseModule import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule +import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule import com.android.systemui.communal.data.repository.CommunalRepositoryModule import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule @@ -34,6 +35,7 @@ import dagger.Module CommunalTutorialRepositoryModule::class, CommunalWidgetRepositoryModule::class, CommunalDatabaseModule::class, + CommunalPrefsRepositoryModule::class, ] ) interface CommunalModule { diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt new file mode 100644 index 000000000000..c2ea2e93ce58 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.data.repository + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.UserInfo +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences +import com.android.systemui.user.data.repository.UserRepository +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +/** + * Stores simple preferences for the current user in communal hub. For use cases like "has the CTA + * tile been dismissed?" + */ +interface CommunalPrefsRepository { + + /** Whether the CTA tile has been dismissed. */ + val isCtaDismissed: Flow<Boolean> + + /** Save the CTA tile dismissed state for the current user. */ + suspend fun setCtaDismissedForCurrentUser() +} + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class CommunalPrefsRepositoryImpl +@Inject +constructor( + @Background private val backgroundScope: CoroutineScope, + @Background private val bgDispatcher: CoroutineDispatcher, + private val userRepository: UserRepository, + private val userFileManager: UserFileManager, +) : CommunalPrefsRepository { + + override val isCtaDismissed: Flow<Boolean> = + userRepository.selectedUserInfo + .flatMapLatest(::observeCtaDismissState) + .stateIn( + scope = backgroundScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + + override suspend fun setCtaDismissedForCurrentUser() = + withContext(bgDispatcher) { + getSharedPrefsForUser(userRepository.getSelectedUserInfo()) + .edit() + .putBoolean(CTA_DISMISSED_STATE, true) + .apply() + } + + private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> = + userFileManager + .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id) + // Emit at the start of collection to ensure we get an initial value + .onStart { emit(Unit) } + .map { getCtaDismissedState() } + .flowOn(bgDispatcher) + + private suspend fun getCtaDismissedState(): Boolean = + withContext(bgDispatcher) { + getSharedPrefsForUser(userRepository.getSelectedUserInfo()) + .getBoolean(CTA_DISMISSED_STATE, false) + } + + private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences { + return userFileManager.getSharedPreferences( + FILE_NAME, + Context.MODE_PRIVATE, + user.id, + ) + } + + companion object { + const val TAG = "CommunalRepository" + const val FILE_NAME = "communal_hub_prefs" + const val CTA_DISMISSED_STATE = "cta_dismissed" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt new file mode 100644 index 000000000000..a4ff6d3dfbef --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.communal.data.repository + +import dagger.Binds +import dagger.Module + +@Module +interface CommunalPrefsRepositoryModule { + @Binds fun communalPrefsRepository(impl: CommunalPrefsRepositoryImpl): CommunalPrefsRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt index 553b3ebc0813..1f4be4060223 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt @@ -56,9 +56,6 @@ interface CommunalRepository { /** Exposes the transition state of the communal [SceneTransitionLayout]. */ val transitionState: StateFlow<ObservableCommunalTransitionState> - /** Whether the CTA tile is visible in the hub under view mode. */ - val isCtaTileInViewModeVisible: Flow<Boolean> - /** Updates the requested scene. */ fun setDesiredScene(desiredScene: CommunalSceneKey) @@ -68,9 +65,6 @@ interface CommunalRepository { * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) - - /** Updates whether to display the CTA tile in the hub under view mode. */ - fun setCtaTileInViewModeVisibility(isVisible: Boolean) } @OptIn(ExperimentalCoroutinesApi::class) @@ -102,16 +96,6 @@ constructor( initialValue = defaultTransitionState, ) - // TODO(b/313462210) - persist the value in local storage, so the tile won't show up again - // once dismissed. - private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true) - override val isCtaTileInViewModeVisible: Flow<Boolean> = - _isCtaTileInViewModeVisible.asStateFlow() - - override fun setCtaTileInViewModeVisibility(isVisible: Boolean) { - _isCtaTileInViewModeVisible.value = isVisible - } - override fun setDesiredScene(desiredScene: CommunalSceneKey) { _desiredScene.value = desiredScene } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index e6816e954b5d..bfc5019801d0 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -16,7 +16,6 @@ package com.android.systemui.communal.data.repository -import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.Intent @@ -29,6 +28,7 @@ import com.android.systemui.communal.data.db.CommunalWidgetDao import com.android.systemui.communal.data.db.CommunalWidgetItem import com.android.systemui.communal.shared.CommunalWidgetHost import com.android.systemui.communal.shared.model.CommunalWidgetContentModel +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -83,7 +83,7 @@ class CommunalWidgetRepositoryImpl @Inject constructor( private val appWidgetManager: Optional<AppWidgetManager>, - private val appWidgetHost: AppWidgetHost, + private val appWidgetHost: CommunalAppWidgetHost, @Application private val applicationScope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher, broadcastDispatcher: BroadcastDispatcher, diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt index d0d9e3fabc7b..52f42c1aba4f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt @@ -17,11 +17,11 @@ package com.android.systemui.communal.data.repository -import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.content.Context import android.content.res.Resources import com.android.systemui.communal.shared.CommunalWidgetHost +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -48,15 +48,15 @@ interface CommunalWidgetRepositoryModule { @SysUISingleton @Provides - fun provideAppWidgetHost(@Application context: Context): AppWidgetHost { - return AppWidgetHost(context, APP_WIDGET_HOST_ID) + fun provideCommunalAppWidgetHost(@Application context: Context): CommunalAppWidgetHost { + return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID) } @SysUISingleton @Provides fun provideCommunalWidgetHost( appWidgetManager: Optional<AppWidgetManager>, - appWidgetHost: AppWidgetHost, + appWidgetHost: CommunalAppWidgetHost, @CommunalLog logBuffer: LogBuffer, ): CommunalWidgetHost { return CommunalWidgetHost(appWidgetManager, appWidgetHost, logBuffer) diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 9fa4cd6c7985..aa4a9d0d9569 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 @@ -17,9 +17,9 @@ package com.android.systemui.communal.domain.interactor import android.app.smartspace.SmartspaceTarget -import android.appwidget.AppWidgetHost import android.content.ComponentName import com.android.systemui.communal.data.repository.CommunalMediaRepository +import com.android.systemui.communal.data.repository.CommunalPrefsRepository import com.android.systemui.communal.data.repository.CommunalRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel @@ -29,6 +29,7 @@ import com.android.systemui.communal.shared.model.CommunalContentSize.HALF import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -49,10 +50,11 @@ class CommunalInteractor constructor( private val communalRepository: CommunalRepository, private val widgetRepository: CommunalWidgetRepository, + private val communalPrefsRepository: CommunalPrefsRepository, mediaRepository: CommunalMediaRepository, smartspaceRepository: SmartspaceRepository, keyguardInteractor: KeyguardInteractor, - private val appWidgetHost: AppWidgetHost, + private val appWidgetHost: CommunalAppWidgetHost, private val editWidgetsActivityStarter: EditWidgetsActivityStarter ) { @@ -122,7 +124,7 @@ constructor( } /** Dismiss the CTA tile from the hub in view mode. */ - fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false) + suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser() /** * Add a widget at the specified position. @@ -174,8 +176,8 @@ constructor( /** CTA tile to be displayed in the glanceable hub (view mode). */ val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> = - communalRepository.isCtaTileInViewModeVisible.map { visible -> - if (visible) listOf(CommunalContentModel.CtaTileInViewMode()) else emptyList() + communalPrefsRepository.isCtaDismissed.map { isDismissed -> + if (isDismissed) emptyList() else listOf(CommunalContentModel.CtaTileInViewMode()) } /** A list of tutorial content to be displayed in the communal hub in tutorial mode. */ diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt index 46f957f3aaf2..0d52afd4fff5 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt @@ -16,10 +16,10 @@ package com.android.systemui.communal.domain.model -import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetProviderInfo import android.widget.RemoteViews import com.android.systemui.communal.shared.model.CommunalContentSize +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import java.util.UUID /** Encapsulates data for a communal content. */ @@ -44,7 +44,7 @@ sealed interface CommunalContentModel { class Widget( val appWidgetId: Int, val providerInfo: AppWidgetProviderInfo, - val appWidgetHost: AppWidgetHost, + val appWidgetHost: CommunalAppWidgetHost, ) : CommunalContentModel { override val key = KEY.widget(appWidgetId) // Widget size is always half. diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt index 41f9cb4c98ed..7fe37ccf0dc8 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt @@ -16,11 +16,11 @@ package com.android.systemui.communal.shared -import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE import android.content.ComponentName +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog @@ -35,7 +35,7 @@ class CommunalWidgetHost @Inject constructor( private val appWidgetManager: Optional<AppWidgetManager>, - private val appWidgetHost: AppWidgetHost, + private val appWidgetHost: CommunalAppWidgetHost, @CommunalLog logBuffer: LogBuffer, ) { companion object { diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index fcad45f950dc..317dd4040e7c 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -20,7 +20,6 @@ import android.app.Activity import android.app.Activity.RESULT_CANCELED import android.app.Activity.RESULT_OK import android.app.ActivityOptions -import android.appwidget.AppWidgetHost import android.content.ActivityNotFoundException import android.content.ComponentName import android.widget.RemoteViews @@ -28,6 +27,7 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.log.CommunalUiEvent +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.media.dagger.MediaModule @@ -48,7 +48,7 @@ class CommunalEditModeViewModel @Inject constructor( private val communalInteractor: CommunalInteractor, - private val appWidgetHost: AppWidgetHost, + private val appWidgetHost: CommunalAppWidgetHost, @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, private val uiEventLogger: UiEventLogger, ) : BaseCommunalViewModel(communalInteractor, mediaHost) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 09c18ed0c8de..d619362b0311 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -86,9 +86,11 @@ constructor( override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor() override fun onDismissCtaTile() { - communalInteractor.dismissCtaTile() - setPopupOnDismissCtaVisibility(true) - schedulePopupHiding() + scope.launch { + communalInteractor.dismissCtaTile() + setPopupOnDismissCtaVisibility(true) + schedulePopupHiding() + } } override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt new file mode 100644 index 000000000000..003c9d50e789 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt @@ -0,0 +1,48 @@ +/* + * 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.widgets + +import android.appwidget.AppWidgetHost +import android.appwidget.AppWidgetHostView +import android.appwidget.AppWidgetProviderInfo +import android.content.Context + +/** Communal app widget host that creates a [CommunalAppWidgetHostView]. */ +class CommunalAppWidgetHost(context: Context, hostId: Int) : AppWidgetHost(context, hostId) { + override fun onCreateView( + context: Context, + appWidgetId: Int, + appWidget: AppWidgetProviderInfo? + ): AppWidgetHostView { + return CommunalAppWidgetHostView(context) + } + + /** + * Creates and returns a [CommunalAppWidgetHostView]. This method does the same thing as + * `createView`. The only difference is that the returned value will be casted to + * [CommunalAppWidgetHostView]. + */ + fun createViewForCommunal( + context: Context?, + appWidgetId: Int, + appWidget: AppWidgetProviderInfo? + ): CommunalAppWidgetHostView { + // `createView` internally calls `onCreateView` to create the view. We cannot override + // `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView` + return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt new file mode 100644 index 000000000000..2b7d82395b89 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt @@ -0,0 +1,76 @@ +/* + * 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.widgets + +import android.appwidget.AppWidgetHostView +import android.content.Context +import android.graphics.Outline +import android.graphics.Rect +import android.view.View +import android.view.ViewOutlineProvider + +/** AppWidgetHostView that displays in communal hub with support for rounded corners. */ +class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context) { + // Mutable corner radius. + var enforcedCornerRadius: Float + + // Mutable `Rect`. The size will be mutated when the widget is reapplied. + var enforcedRectangle: Rect + + init { + enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context) + enforcedRectangle = Rect() + } + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + super.onLayout(changed, l, t, r, b) + + enforceRoundedCorners() + } + + private val cornerRadiusEnforcementOutline: ViewOutlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View?, outline: Outline) { + if (enforcedRectangle.isEmpty || enforcedCornerRadius <= 0) { + outline.setEmpty() + } else { + outline.setRoundRect(enforcedRectangle, enforcedCornerRadius) + } + } + } + + private fun enforceRoundedCorners() { + if (enforcedCornerRadius <= 0) { + resetRoundedCorners() + return + } + val background: View? = RoundedCornerEnforcement.findBackground(this) + if (background == null || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) { + resetRoundedCorners() + return + } + RoundedCornerEnforcement.computeRoundedRectangle(this, background, enforcedRectangle) + outlineProvider = cornerRadiusEnforcementOutline + clipToOutline = true + invalidateOutline() + } + + private fun resetRoundedCorners() { + outlineProvider = ViewOutlineProvider.BACKGROUND + clipToOutline = false + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt new file mode 100644 index 000000000000..abda44be09fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt @@ -0,0 +1,154 @@ +/* + * 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.widgets + +import android.annotation.IdRes +import android.annotation.Nullable +import android.content.Context +import android.content.res.Resources +import android.graphics.Rect +import android.view.View +import android.view.ViewGroup +import androidx.core.os.BuildCompat.isAtLeastS +import com.android.systemui.res.R +import kotlin.math.min + +/** + * Utilities to compute the enforced use of rounded corners on App Widgets. This is a fork of the + * Launcher3 source code to enforce the same visual treatment on communal hub. + */ +internal object RoundedCornerEnforcement { + /** + * Find the background view for a widget. + * + * @param appWidget the view containing the App Widget (typically the instance of + * [CommunalAppWidgetHostView]). + */ + fun findBackground(appWidget: View): View? { + val backgrounds = findViewsWithId(appWidget, R.id.background) + if (backgrounds.size == 1) { + return backgrounds[0] + } + // Really, the argument should contain the widget, so it cannot be the background. + if (appWidget is ViewGroup) { + val vg = appWidget + if (vg.childCount > 0) { + return findUndefinedBackground(vg.getChildAt(0)) + } + } + return appWidget + } + + /** Check whether the app widget has opted out of the enforcement. */ + fun hasAppWidgetOptedOut(appWidget: View?, background: View): Boolean { + return background.id == R.id.background && background.clipToOutline + } + + /** + * Computes the rounded rectangle needed for this app widget. + * + * @param appWidget View onto which the rounded rectangle will be applied. + * @param background Background view. This must be either `appWidget` or a descendant of + * `appWidget`. + * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame of + * `appWidget`. + */ + fun computeRoundedRectangle(appWidget: View, background: View, outRect: Rect) { + var background = background + outRect.left = 0 + outRect.right = background.width + outRect.top = 0 + outRect.bottom = background.height + while (background !== appWidget) { + outRect.offset(background.left, background.top) + background = background.parent as View + } + } + + /** Get the radius of the rounded rectangle defined in the host's resource. */ + private fun getOwnedEnforcedRadius(context: Context): Float { + val res: Resources = context.resources + return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius) + } + + /** + * Computes the radius of the rounded rectangle that should be applied to a widget expanded in + * the given context. + */ + fun computeEnforcedRadius(context: Context): Float { + if (!isAtLeastS()) { + return 0f + } + val res: Resources = context.resources + val systemRadius: Float = + res.getDimension(android.R.dimen.system_app_widget_background_radius) + val defaultRadius = getOwnedEnforcedRadius(context) + return min(defaultRadius, systemRadius) + } + + private fun findViewsWithId(view: View, @IdRes viewId: Int): List<View> { + val output: MutableList<View> = ArrayList() + accumulateViewsWithId(view, viewId, output) + return output + } + + // Traverse views. If the predicate returns true, continue on the children, otherwise, don't. + private fun accumulateViewsWithId(view: View, @IdRes viewId: Int, output: MutableList<View>) { + if (view.id == viewId) { + output.add(view) + return + } + if (view is ViewGroup) { + val vg = view + for (i in 0 until vg.childCount) { + accumulateViewsWithId(vg.getChildAt(i), viewId, output) + } + } + } + + private fun isViewVisible(view: View): Boolean { + return if (view.visibility != View.VISIBLE) { + false + } else !view.willNotDraw() || view.foreground != null || view.background != null + } + + @Nullable + private fun findUndefinedBackground(current: View): View? { + if (current.visibility != View.VISIBLE) { + return null + } + if (isViewVisible(current)) { + return current + } + var lastVisibleView: View? = null + // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw + // something, or a ViewGroup that contains more than one view. + if (current is ViewGroup) { + val vg = current + for (i in 0 until vg.childCount) { + val visibleView = findUndefinedBackground(vg.getChildAt(i)) + if (visibleView != null) { + if (lastVisibleView != null) { + return current // At least two visible children + } + lastVisibleView = visibleView + } + } + } + return lastVisibleView + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index b2d70523c282..6d9994fb2205 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -556,7 +556,7 @@ public class FrameworkServicesModule { @Provides @Singleton static SubscriptionManager provideSubscriptionManager(Context context) { - return context.getSystemService(SubscriptionManager.class); + return context.getSystemService(SubscriptionManager.class).createForAllUserProfiles(); } @Provides diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 846736c04d98..ea8ba1029782 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -102,12 +102,6 @@ object Flags { default = true ) - /** Only notify group expansion listeners when a change happens. */ - // TODO(b/292213543): Tracking Bug - @JvmField - val NOTIFICATION_GROUP_EXPANSION_CHANGE = - releasedFlag("notification_group_expansion_change") - // TODO(b/301955929) @JvmField val NOTIF_LS_BACKGROUND_THREAD = diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt index 496c64e1120e..c6fb4f9d6956 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt @@ -19,6 +19,8 @@ package com.android.systemui.keyboard import com.android.systemui.keyboard.data.repository.KeyboardRepository import com.android.systemui.keyboard.data.repository.KeyboardRepositoryImpl +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl import dagger.Binds import dagger.Module @@ -27,4 +29,9 @@ abstract class KeyboardModule { @Binds abstract fun bindKeyboardRepository(repository: KeyboardRepositoryImpl): KeyboardRepository + + @Binds + abstract fun bindStickyKeysRepository( + repository: StickyKeysRepositoryImpl + ): StickyKeysRepository } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt new file mode 100644 index 000000000000..37034f63aca7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys + +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.KeyboardLog +import javax.inject.Inject + +private const val TAG = "stickyKeys" + +class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogBuffer) { + fun logNewStickyKeysReceived(linkedHashMap: Map<ModifierKey, Locked>) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { str1 = linkedHashMap.toString() }, + { "new sticky keys state received: $str1" } + ) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt new file mode 100644 index 000000000000..34d288815570 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys.data.repository + +import android.hardware.input.InputManager +import android.hardware.input.InputManager.StickyModifierStateListener +import android.hardware.input.StickyModifierState +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.stickykeys.StickyKeysLogger +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +interface StickyKeysRepository { + val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> + val settingEnabled: Flow<Boolean> +} + +class StickyKeysRepositoryImpl +@Inject +constructor( + private val inputManager: InputManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val stickyKeysLogger: StickyKeysLogger, +) : StickyKeysRepository { + + override val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> = + conflatedCallbackFlow { + val listener = StickyModifierStateListener { stickyModifierState -> + trySendWithFailureLogging(stickyModifierState, TAG) + } + // after registering, InputManager calls listener with the current value + inputManager.registerStickyModifierStateListener(Runnable::run, listener) + awaitClose { inputManager.unregisterStickyModifierStateListener(listener) } + } + .map { toStickyKeysMap(it) } + .onEach { stickyKeysLogger.logNewStickyKeysReceived(it) } + .flowOn(backgroundDispatcher) + + // TODO(b/319837892): Implement reading actual setting + override val settingEnabled: StateFlow<Boolean> = MutableStateFlow(true) + + private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> { + val keys = linkedMapOf<ModifierKey, Locked>() + state.apply { + if (isAltGrModifierOn) keys[ALT_GR] = Locked(false) + if (isAltGrModifierLocked) keys[ALT_GR] = Locked(true) + if (isAltModifierOn) keys[ALT] = Locked(false) + if (isAltModifierLocked) keys[ALT] = Locked(true) + if (isCtrlModifierOn) keys[CTRL] = Locked(false) + if (isCtrlModifierLocked) keys[CTRL] = Locked(true) + if (isMetaModifierOn) keys[META] = Locked(false) + if (isMetaModifierLocked) keys[META] = Locked(true) + if (isShiftModifierOn) keys[SHIFT] = Locked(false) + if (isShiftModifierLocked) keys[SHIFT] = Locked(true) + } + return keys + } + + companion object { + const val TAG = "StickyKeysRepositoryImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt new file mode 100644 index 000000000000..d5f082a2566f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys.shared.model + +@JvmInline +value class Locked(val locked: Boolean) + +enum class ModifierKey(val text: String) { + ALT("ALT LEFT"), + ALT_GR("ALT RIGHT"), + CTRL("CTRL"), + META("META"), + SHIFT("SHIFT"), +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt new file mode 100644 index 000000000000..26eb706da200 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys.ui.viewmodel + +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyboard.data.repository.KeyboardRepository +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +class StickyKeysIndicatorViewModel +@Inject +constructor( + stickyKeysRepository: StickyKeysRepository, + keyboardRepository: KeyboardRepository, + @Application applicationScope: CoroutineScope, +) { + + @OptIn(ExperimentalCoroutinesApi::class) + val indicatorContent: Flow<Map<ModifierKey, Locked>> = + keyboardRepository.isAnyKeyboardConnected + .flatMapLatest { keyboardPresent -> + if (keyboardPresent) stickyKeysRepository.settingEnabled else flowOf(false) + } + .flatMapLatest { enabled -> + if (enabled) stickyKeysRepository.stickyKeys else flowOf(emptyMap()) + } + .stateIn(applicationScope, SharingStarted.Lazily, emptyMap()) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index fedd63be1454..8fa33ee7d0ca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -54,12 +54,33 @@ constructor( ) { override fun start() { - listenForAodToLockscreenOrOccluded() + listenForAodToLockscreen() + listenForAodToPrimaryBouncer() listenForAodToGone() + listenForAodToOccluded() listenForTransitionToCamera(scope, keyguardInteractor) } - private fun listenForAodToLockscreenOrOccluded() { + /** + * There are cases where the transition to AOD begins but never completes, such as tapping power + * during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to + * run AOD->OCCLUDED. + */ + private fun listenForAodToOccluded() { + scope.launch { + keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect { + (isOccluded, startedKeyguardState) -> + if (isOccluded && startedKeyguardState == KeyguardState.AOD) { + startTransitionTo( + toState = KeyguardState.OCCLUDED, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + } + } + } + } + + private fun listenForAodToLockscreen() { scope.launch { keyguardInteractor .dozeTransitionTo(DozeStateModel.FINISH) @@ -72,20 +93,15 @@ constructor( ::toTriple ) .collect { (_, lastStartedStep, occluded) -> - if (lastStartedStep.to == KeyguardState.AOD) { - val toState = - if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN + if (lastStartedStep.to == KeyguardState.AOD && !occluded) { val modeOnCanceled = - if ( - toState == KeyguardState.LOCKSCREEN && - lastStartedStep.from == KeyguardState.LOCKSCREEN - ) { + if (lastStartedStep.from == KeyguardState.LOCKSCREEN) { TransitionModeOnCanceled.REVERSE } else { TransitionModeOnCanceled.LAST_VALUE } startTransitionTo( - toState = toState, + toState = KeyguardState.LOCKSCREEN, modeOnCanceled = modeOnCanceled, ) } @@ -93,11 +109,26 @@ constructor( } } + /** + * If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the + * PRIMARY_BOUNCER. + */ + private fun listenForAodToPrimaryBouncer() { + scope.launch { + keyguardInteractor.primaryBouncerShowing + .sample(startedKeyguardTransitionStep, ::Pair) + .collect { (isBouncerShowing, lastStartedTransitionStep) -> + if (isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.AOD) { + startTransitionTo(KeyguardState.PRIMARY_BOUNCER) + } + } + } + } + private fun listenForAodToGone() { scope.launch { keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect { - pair -> - val (biometricUnlockState, keyguardState) = pair + (biometricUnlockState, keyguardState) -> if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) { startTransitionTo(KeyguardState.GONE) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt index 2a68f26d3ae7..0c0eb8a673a4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt @@ -24,6 +24,7 @@ import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.LEFT import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.RIGHT +import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder @@ -96,6 +97,11 @@ constructor( constrainHeight(R.id.end_button, height) connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin) connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin) + + // The constraint set visibility for start and end button are default visible, set to + // ignore so the view's own initial visibility (invisible) is used + setVisibilityMode(R.id.start_button, VISIBILITY_MODE_IGNORE) + setVisibilityMode(R.id.end_button, VISIBILITY_MODE_IGNORE) } } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt new file mode 100644 index 000000000000..5910701d9f2a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger + +import javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for keyboard-related functionality. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class KeyboardLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 24cb8fff9b67..3e0094081638 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -603,6 +603,14 @@ public class LogModule { return factory.create("BluetoothTileDialogLog", 50); } + /** Provides a {@link LogBuffer} for the keyboard functionalities. */ + @Provides + @SysUISingleton + @KeyboardLog + public static LogBuffer provideKeyboardLogBuffer(LogBufferFactory factory) { + return factory.create("KeyboardLog", 50); + } + /** Provides a {@link LogBuffer} for {@link PackageChangeRepository} */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt new file mode 100644 index 000000000000..ca790e830f7f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs + +import android.view.KeyEvent +import android.view.View +import androidx.core.util.Consumer + +/** + * Listens for left and right arrow keys pressed while focus is on the view. + * + * Key press is treated as correct when its full lifecycle happened on the view: first + * [KeyEvent.ACTION_DOWN] was performed, view didn't lose focus in the meantime and then + * [KeyEvent.ACTION_UP] was performed with the same [KeyEvent.getKeyCode] + */ +class LeftRightArrowPressedListener private constructor() : + View.OnKeyListener, View.OnFocusChangeListener { + + private var lastKeyCode: Int? = 0 + private var listener: Consumer<Int>? = null + + fun setArrowKeyPressedListener(arrowPressedListener: Consumer<Int>) { + listener = arrowPressedListener + } + + override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean { + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { + // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need + // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we + // have a chance to intercept ACTION_UP. + if (keyEvent.action == KeyEvent.ACTION_UP && keyCode == lastKeyCode) { + listener?.accept(keyCode) + lastKeyCode = null + } else if (keyEvent.repeatCount == 0) { + // we only read key events that are NOT coming from long pressing because that also + // causes reading ACTION_DOWN event (with repeated count > 0) when moving focus with + // arrow from another sibling view + lastKeyCode = keyCode + } + return true + } + return false + } + + override fun onFocusChange(view: View, hasFocus: Boolean) { + // resetting lastKeyCode so we get fresh cleared state on focus + if (hasFocus) { + lastKeyCode = null + } + } + + companion object { + @JvmStatic + fun createAndRegisterListenerForView(view: View): LeftRightArrowPressedListener { + val listener = LeftRightArrowPressedListener() + view.setOnKeyListener(listener) + view.onFocusChangeListener = listener + return listener + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java index 4770d5272508..1c9f5fdde6f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java @@ -1,5 +1,8 @@ package com.android.systemui.qs; +import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT; +import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT; + import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -9,10 +12,12 @@ import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; +import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; import com.android.settingslib.Utils; @@ -43,6 +48,7 @@ public class PageIndicator extends ViewGroup { private int mPosition = -1; private boolean mAnimating; + private PageScrollActionListener mPageScrollActionListener; private final Animatable2.AnimationCallback mAnimationCallback = new Animatable2.AnimationCallback() { @@ -77,6 +83,14 @@ public class PageIndicator extends ViewGroup { mPageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width); mPageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height); mPageDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width); + LeftRightArrowPressedListener arrowListener = + LeftRightArrowPressedListener.createAndRegisterListenerForView(this); + arrowListener.setArrowKeyPressedListener(keyCode -> { + if (mPageScrollActionListener != null) { + int swipeDirection = keyCode == KeyEvent.KEYCODE_DPAD_LEFT ? LEFT : RIGHT; + mPageScrollActionListener.onScrollActionTriggered(swipeDirection); + } + }); } public void setNumPages(int numPages) { @@ -280,4 +294,19 @@ public class PageIndicator extends ViewGroup { getChildAt(i).layout(left, 0, mPageIndicatorWidth + left, mPageIndicatorHeight); } } + + void setPageScrollActionListener(PageScrollActionListener listener) { + mPageScrollActionListener = listener; + } + + interface PageScrollActionListener { + + @IntDef({LEFT, RIGHT}) + @interface Direction { } + + int LEFT = 0; + int RIGHT = 1; + + void onScrollActionTriggered(@Direction int swipeDirection); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 052c0daf0b56..43f3a2242da4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -1,6 +1,8 @@ package com.android.systemui.qs; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE; +import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT; +import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -12,7 +14,6 @@ import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.util.AttributeSet; -import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -30,6 +31,7 @@ import androidx.viewpager.widget.ViewPager; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.systemui.plugins.qs.QSTile; +import com.android.systemui.qs.PageIndicator.PageScrollActionListener.Direction; import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.QSPanelControllerBase.TileRecord; import com.android.systemui.qs.logging.QSLogger; @@ -310,26 +312,18 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { mPageIndicator = indicator; mPageIndicator.setNumPages(mPages.size()); mPageIndicator.setLocation(mPageIndicatorPosition); - mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> { - if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { - // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need - // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we - // have a chance to intercept ACTION_UP. - if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) { - scrollByX(getDeltaXForKeyboardScrolling(keyCode), - SINGLE_PAGE_SCROLL_DURATION_MILLIS); - } - return true; + mPageIndicator.setPageScrollActionListener(swipeDirection -> { + if (mScroller.isFinished()) { + scrollByX(getDeltaXForPageScrolling(swipeDirection), + SINGLE_PAGE_SCROLL_DURATION_MILLIS); } - return false; }); } - private int getDeltaXForKeyboardScrolling(int keyCode) { - if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) { + private int getDeltaXForPageScrolling(@Direction int swipeDirection) { + if (swipeDirection == LEFT && getCurrentItem() != 0) { return -getWidth(); - } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT - && getCurrentItem() != mPages.size() - 1) { + } else if (swipeDirection == RIGHT && getCurrentItem() != mPages.size() - 1) { return getWidth(); } return 0; diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt index a6cccf1cb41b..e2959fe834d4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt @@ -24,7 +24,9 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.init.NotificationsController +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.policy.HeadsUpManager import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -44,6 +46,7 @@ constructor( private val keyguardRepository: KeyguardRepository, private val headsUpManager: HeadsUpManager, private val powerInteractor: PowerInteractor, + private val activeNotificationsInteractor: ActiveNotificationsInteractor, ) : CoreStartable { private var notificationPresenter: NotificationPresenter? = null @@ -117,6 +120,14 @@ constructor( return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) { 1 } else { + getActiveNotificationsCount() + } + } + + private fun getActiveNotificationsCount(): Int { + return if (NotificationsLiveDataStoreRefactor.isEnabled) { + activeNotificationsInteractor.allNotificationsCountValue + } else { notificationsController?.getActiveNotificationsCount() ?: 0 } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt index ab08f660789c..a755805d1872 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt @@ -37,6 +37,7 @@ object SceneContainerFlag { /** The flag description -- not an aconfig flag name */ const val DESCRIPTION = "SceneContainerFlag" + @JvmStatic inline val isEnabled get() = SCENE_CONTAINER_ENABLED && // mainStaticFlag diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 4a839b8df9ba..93cfc5dbcbe3 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -85,12 +85,13 @@ object SceneWindowRootViewBinder { view.addView( ComposeFacade.createSceneContainerView( - scope = this, - context = view.context, - viewModel = viewModel, - windowInsets = windowInsets, - sceneByKey = sortedSceneByKey, - ) + scope = this, + context = view.context, + viewModel = viewModel, + windowInsets = windowInsets, + sceneByKey = sortedSceneByKey, + ) + .also { it.id = R.id.scene_container_root_composable } ) val legacyView = view.requireViewById<View>(R.id.legacy_window_root) diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt new file mode 100644 index 000000000000..b09bfe21e014 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.settings + +import android.annotation.UserIdInt +import android.content.Context +import android.content.SharedPreferences +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +/** Extension functions for [UserFileManager]. */ +object UserFileManagerExt { + + /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */ + fun UserFileManager.observeSharedPreferences( + fileName: String, + @Context.PreferencesMode mode: Int, + @UserIdInt userId: Int + ): Flow<Unit> = conflatedCallbackFlow { + val sharedPrefs = getSharedPreferences(fileName, mode, userId) + + val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) } + + sharedPrefs.registerOnSharedPreferenceChangeListener(listener) + awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java index 2d5afd56da72..3cdb2cd9b5c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java @@ -21,8 +21,6 @@ import androidx.annotation.NonNull; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -53,14 +51,11 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl */ private final Set<NotificationEntry> mExpandedGroups = new HashSet<>(); - private final FeatureFlags mFeatureFlags; - @Inject public GroupExpansionManagerImpl(DumpManager dumpManager, - GroupMembershipManager groupMembershipManager, FeatureFlags featureFlags) { + GroupMembershipManager groupMembershipManager) { mDumpManager = dumpManager; mGroupMembershipManager = groupMembershipManager; - mFeatureFlags = featureFlags; } /** @@ -86,10 +81,8 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl }; public void attach(NotifPipeline pipeline) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - mDumpManager.registerDumpable(this); - pipeline.addOnBeforeRenderListListener(mNotifTracker); - } + mDumpManager.registerDumpable(this); + pipeline.addOnBeforeRenderListListener(mNotifTracker); } @Override @@ -105,8 +98,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl @Override public void setGroupExpanded(NotificationEntry entry, boolean expanded) { NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry); - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) - && entry.getParent() == null) { + if (entry.getParent() == null) { if (expanded) { throw new IllegalArgumentException("Cannot expand group that is not attached"); } else { @@ -124,7 +116,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl } // Only notify listeners if something changed. - if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) || changed) { + if (changed) { sendOnGroupExpandedChange(entry, expanded); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java index cb7935369564..da1247953c4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java @@ -22,8 +22,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.flags.FeatureFlagsClassic; -import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -38,25 +36,17 @@ import javax.inject.Inject; */ @SysUISingleton public class GroupMembershipManagerImpl implements GroupMembershipManager { - FeatureFlagsClassic mFeatureFlags; - @Inject - public GroupMembershipManagerImpl(FeatureFlagsClassic featureFlags) { - mFeatureFlags = featureFlags; - } + public GroupMembershipManagerImpl() {} @Override public boolean isGroupSummary(@NonNull NotificationEntry entry) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - if (entry.getParent() == null) { - // The entry is not attached, so it doesn't count. - return false; - } - // If entry is a summary, its parent is a GroupEntry with summary = entry. - return entry.getParent().getSummary() == entry; - } else { - return getGroupSummary(entry) == entry; + if (entry.getParent() == null) { + // The entry is not attached, so it doesn't count. + return false; } + // If entry is a summary, its parent is a GroupEntry with summary = entry. + return entry.getParent().getSummary() == entry; } @Nullable @@ -70,12 +60,8 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager { @Override public boolean isChildInGroup(@NonNull NotificationEntry entry) { - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) { - // An entry is a child if it's not a summary or top level entry, but it is attached. - return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null; - } else { - return !isTopLevelEntry(entry); - } + // An entry is a child if it's not a summary or top level entry, but it is attached. + return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 5180bab60fdf..b22e9fd2fb17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -68,7 +68,8 @@ constructor( /** * The same as [allNotificationsCount], but without flows, for easy access in synchronous code. */ - val allNotificationsCountValue: Int = repository.activeNotifications.value.individuals.size + val allNotificationsCountValue: Int + get() = repository.activeNotifications.value.individuals.size /** Are any notifications being actively presented in the notification stack? */ val areAnyNotificationsPresent: Flow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index ac499601b962..1677418c5c30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.init import android.service.notification.StatusBarNotification import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags import com.android.systemui.people.widget.PeopleSpaceWidgetManager import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption import com.android.systemui.statusbar.NotificationListener @@ -72,7 +71,6 @@ constructor( private val animatedImageNotificationManager: AnimatedImageNotificationManager, private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager, private val bubblesOptional: Optional<Bubbles>, - private val featureFlags: FeatureFlags ) : NotificationsController { override fun initialize( @@ -136,5 +134,8 @@ constructor( } } - override fun getActiveNotificationsCount(): Int = notifLiveDataStore.activeNotifCount.value + override fun getActiveNotificationsCount(): Int { + NotificationsLiveDataStoreRefactor.assertInLegacyMode() + return notifLiveDataStore.activeNotifCount.value + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index d9c51089d5f8..c527bb537f19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -26,9 +26,9 @@ import android.util.MathUtils; import androidx.annotation.VisibleForTesting; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; +import com.android.systemui.res.R; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index e791a6490e15..04db653282ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -94,6 +94,7 @@ import com.android.systemui.flags.RefactorFlag; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.TouchLogger; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; @@ -187,6 +188,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private int mOverflingDistance; private float mMaxOverScroll; private boolean mIsBeingDragged; + private boolean mSendingTouchesToSceneFramework; private int mLastMotionY; private int mDownX; private int mActivePointerId = INVALID_POINTER; @@ -1509,7 +1511,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ public void setExpandedHeight(float height) { final boolean skipHeightUpdate = shouldSkipHeightUpdate(); - updateStackPosition(); + + // when scene framework is enabled, updateStackPosition is already called by + // updateTopPadding every time the stack moves, so skip it here to avoid flickering. + if (!SceneContainerFlag.isEnabled()) { + updateStackPosition(); + } if (!skipHeightUpdate) { mExpandedHeight = height; @@ -2450,6 +2457,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications, shelfIntrinsicHeight); mIntrinsicContentHeight = height; + mController.setIntrinsicContentHeight(mIntrinsicContentHeight); // The topPadding can be bigger than the regular padding when qs is expanded, in that // state the maxPanelHeight and the contentHeight should be bigger @@ -3558,8 +3566,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public boolean onTouchEvent(MotionEvent ev) { - if (mTouchHandler != null && mTouchHandler.onTouchEvent(ev)) { - return true; + if (mTouchHandler != null) { + boolean touchHandled = mTouchHandler.onTouchEvent(ev); + if (SceneContainerFlag.isEnabled() || touchHandled) { + return touchHandled; + } } return super.onTouchEvent(ev); @@ -3567,6 +3578,27 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public boolean dispatchTouchEvent(MotionEvent ev) { + if (SceneContainerFlag.isEnabled() && mIsBeingDragged) { + if (!mSendingTouchesToSceneFramework) { + // if this is the first touch being sent to the scene framework, + // convert it into a synthetic DOWN event. + mSendingTouchesToSceneFramework = true; + MotionEvent downEvent = MotionEvent.obtain(ev); + downEvent.setAction(MotionEvent.ACTION_DOWN); + mController.sendTouchToSceneFramework(downEvent); + downEvent.recycle(); + } else { + mController.sendTouchToSceneFramework(ev); + } + + if ( + ev.getActionMasked() == MotionEvent.ACTION_UP + || ev.getActionMasked() == MotionEvent.ACTION_CANCEL + ) { + setIsBeingDragged(false); + } + return false; + } return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev)); } @@ -3633,6 +3665,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return true; } + // If the scene framework is enabled, ignore all non-move gestures if we are currently + // dragging - they should be dispatched to the scene framework. Move gestures should be let + // through to determine if we are still dragging or not. + if ( + SceneContainerFlag.isEnabled() + && mIsBeingDragged + && action != MotionEvent.ACTION_MOVE + ) { + setIsBeingDragged(false); + return false; + } + switch (action) { case MotionEvent.ACTION_DOWN: { if (getChildCount() == 0 || !isInContentBounds(ev)) { @@ -3676,6 +3720,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } if (mIsBeingDragged) { + // Defer actual scrolling to the scene framework if enabled + if (SceneContainerFlag.isEnabled()) { + setIsBeingDragged(false); + return false; + } // Scroll to follow the motion event mLastMotionY = y; float scrollAmount; @@ -3770,9 +3819,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } protected boolean isInsideQsHeader(MotionEvent ev) { - if (mQsHeader == null) { - Log.wtf(TAG, "qsHeader is null while NSSL is handling a touch"); - return false; + if (SceneContainerFlag.isEnabled()) { + return ev.getY() < mController.getPlaceholderTop(); } mQsHeader.getBoundsOnScreen(mQsHeaderBound); @@ -4026,9 +4074,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestDisallowInterceptTouchEvent(true); cancelLongPress(); resetExposedMenuView(true /* animate */, true /* force */); + } else { + mSendingTouchesToSceneFramework = false; } } + @VisibleForTesting + boolean getIsBeingDragged() { + return mIsBeingDragged; + } + public void requestDisallowLongPress() { cancelLongPress(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index c0e0b73c750d..6a66bb74f16d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -80,6 +80,9 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEv import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.CommandQueue; @@ -120,6 +123,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationSnooze; +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; @@ -145,6 +149,7 @@ import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Named; +import javax.inject.Provider; /** * Controller for {@link NotificationStackScrollLayout}. @@ -181,6 +186,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final NotificationRemoteInputManager mRemoteInputManager; private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; private final ShadeController mShadeController; + private final Provider<WindowRootView> mWindowRootView; + private final NotificationStackAppearanceInteractor mStackAppearanceInteractor; private final KeyguardMediaController mKeyguardMediaController; private final SysuiStatusBarStateController mStatusBarStateController; private final KeyguardBypassController mKeyguardBypassController; @@ -689,6 +696,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { SeenNotificationsInteractor seenNotificationsInteractor, NotificationListViewBinder viewBinder, ShadeController shadeController, + SceneContainerFlags sceneContainerFlags, + Provider<WindowRootView> windowRootView, + NotificationStackAppearanceInteractor stackAppearanceInteractor, InteractionJankMonitor jankMonitor, StackStateLogger stackLogger, NotificationStackScrollLogger logger, @@ -739,6 +749,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator; mSeenNotificationsInteractor = seenNotificationsInteractor; mShadeController = shadeController; + mWindowRootView = windowRootView; + mStackAppearanceInteractor = stackAppearanceInteractor; mFeatureFlags = featureFlags; mNotificationTargetsHelper = notificationTargetsHelper; mSecureSettings = secureSettings; @@ -1076,6 +1088,28 @@ public class NotificationStackScrollLayoutController implements Dumpable { return mView.getIntrinsicContentHeight(); } + /** + * Dispatch a touch to the scene container framework. + * TODO(b/316965302): Replace findViewById to avoid DFS + */ + public void sendTouchToSceneFramework(MotionEvent ev) { + View sceneContainer = mWindowRootView.get() + .findViewById(R.id.scene_container_root_composable); + if (sceneContainer != null) { + sceneContainer.dispatchTouchEvent(ev); + } + } + + /** Get the y-coordinate of the top bound of the stack. */ + public float getPlaceholderTop() { + return mStackAppearanceInteractor.getStackBounds().getValue().getTop(); + } + + /** Set the intrinsic height of the stack content without additional padding. */ + public void setIntrinsicContentHeight(float intrinsicContentHeight) { + mStackAppearanceInteractor.setIntrinsicContentHeight(intrinsicContentHeight); + } + public void setIntrinsicPadding(int intrinsicPadding) { mView.setIntrinsicPadding(intrinsicPadding); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt index e78a694735e0..aac3c28a3426 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt @@ -30,4 +30,18 @@ class NotificationStackAppearanceRepository @Inject constructor() { /** The corner radius of the notification stack, in dp. */ val cornerRadiusDp = MutableStateFlow(32f) + + /** + * The height in px of the contents of notification stack. Depending on the number of + * notifications, this can exceed the space available on screen to show notifications, at which + * point the notification stack should become scrollable. + */ + val intrinsicContentHeight = MutableStateFlow(0f) + + /** + * The y-coordinate in px of top of the contents of the notification stack. This value can be + * negative, if the stack is scrolled such that its top extends beyond the top edge of the + * screen. + */ + val contentTop = MutableStateFlow(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 61a4dfcbd201..1dfde09f3a85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -34,12 +34,32 @@ constructor( /** The bounds of the notification stack in the current scene. */ val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow() + /** The corner radius of the notification stack, in dp. */ + val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow() + + /** + * The height in px of the contents of notification stack. Depending on the number of + * notifications, this can exceed the space available on screen to show notifications, at which + * point the notification stack should become scrollable. + */ + val intrinsicContentHeight = repository.intrinsicContentHeight.asStateFlow() + + /** The y-coordinate in px of top of the contents of the notification stack. */ + val contentTop = repository.contentTop.asStateFlow() + /** Sets the position of the notification stack in the current scene. */ fun setStackBounds(bounds: NotificationContainerBounds) { check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } repository.stackBounds.value = bounds } - /** The corner radius of the notification stack, in dp. */ - val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow() + /** Sets the height of the contents of the notification stack. */ + fun setIntrinsicContentHeight(height: Float) { + repository.intrinsicContentHeight.value = height + } + + /** Sets the y-coord in px of the top of the contents of the notification stack. */ + fun setContentTop(startY: Float) { + repository.contentTop.value = startY + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt index a9b542dcce2d..ed15f557fb39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt @@ -25,6 +25,7 @@ import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel +import kotlin.math.pow import kotlin.math.roundToInt import kotlinx.coroutines.launch @@ -43,24 +44,28 @@ object NotificationStackAppearanceViewBinder { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { viewModel.stackBounds.collect { bounds -> - controller.updateTopPadding( - bounds.top, - controller.isAddOrRemoveAnimationPending - ) controller.setRoundedClippingBounds( - it.left, - it.top, - it.right, - it.bottom, + bounds.left.roundToInt(), + bounds.top.roundToInt(), + bounds.right.roundToInt(), + bounds.bottom.roundToInt(), viewModel.cornerRadiusDp.value.dpToPx(context), viewModel.cornerRadiusDp.value.dpToPx(context), ) } } + + launch { + viewModel.contentTop.collect { + controller.updateTopPadding(it, controller.isAddOrRemoveAnimationPending) + } + } + launch { viewModel.expandFraction.collect { expandFraction -> ambientState.expansionFraction = expandFraction controller.expandedHeight = expandFraction * controller.view.height + controller.setMaxAlphaForExpansion(expandFraction.pow(0.75f)) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt index 834d3ffe63c9..74db5831f7f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt @@ -41,4 +41,7 @@ constructor( /** The corner radius of the notification stack, in dp. */ val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp + + /** The y-coordinate in px of top of the contents of the notification stack. */ + val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 9f22118e3332..385f0619288d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -21,9 +21,11 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled import javax.inject.Inject +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow /** @@ -35,6 +37,7 @@ class NotificationsPlaceholderViewModel @Inject constructor( private val interactor: NotificationStackAppearanceInteractor, + shadeInteractor: ShadeInteractor, flags: SceneContainerFlags, featureFlags: FeatureFlagsClassic, ) { @@ -66,4 +69,22 @@ constructor( /** The corner radius of the placeholder, in dp. */ val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp + + /** + * The height in px of the contents of notification stack. Depending on the number of + * notifications, this can exceed the space available on screen to show notifications, at which + * point the notification stack should become scrollable. + */ + val intrinsicContentHeight = interactor.intrinsicContentHeight + + /** + * The amount [0-1] that the shade has been opened. At 0, the shade is closed; at 1, the shade + * is open. + */ + val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion + + /** Sets the y-coord in px of the top of the contents of the notification stack. */ + fun onContentTopChanged(padding: Float) { + interactor.setContentTop(padding) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index d8ef981b6a56..da6bfe84eee7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -48,10 +48,10 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -76,7 +76,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { protected MockitoSession mStaticMockSession; - protected final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this); + protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); protected @Mock DeviceEntryInteractor mDeviceEntryInteractor; protected @Mock LockIconView mLockIconView; protected @Mock AnimatedStateListDrawable mIconDrawable; @@ -175,7 +175,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mPrimaryBouncerInteractor, mContext, () -> mDeviceEntryInteractor, - mSceneTestUtils.getFakeSceneContainerFlags() + mKosmos.getFakeSceneContainerFlags() ); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java index 132bdb55180a..b0887efed4d7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java @@ -373,7 +373,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { @Test public void longPress_showBouncer_sceneContainerNotEnabled() { init(/* useMigrationFlag= */ false); - mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(false); + mKosmos.getFakeSceneContainerFlags().setEnabled(false); when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false); // WHEN longPress @@ -387,7 +387,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { @Test public void longPress_showBouncer() { init(/* useMigrationFlag= */ false); - mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true); + mKosmos.getFakeSceneContainerFlags().setEnabled(true); when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false); // WHEN longPress @@ -401,7 +401,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { @Test public void longPress_falsingTriggered_doesNotShowBouncer() { init(/* useMigrationFlag= */ false); - mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true); + mKosmos.getFakeSceneContainerFlags().setEnabled(true); when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true); // WHEN longPress diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index 2afb3a15b4be..d86d12303140 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -640,11 +640,10 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { @Test public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback() throws RemoteException { - enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration, mAnimationCallback); - Mockito.reset(mSpyController); + resetMockObjects(); getInstrumentation().runOnMainSync(() -> { mWindowMagnificationAnimationController.deleteWindowMagnification( mAnimationCallback2); @@ -658,6 +657,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mValueAnimator.end(); }); + // wait for animation returns + waitForIdleSync(); + + // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} will only + // be triggered once in {@link ValueAnimator#end()} verify(mSpyController).enableWindowMagnificationInternal( mScaleCaptor.capture(), mCenterXCaptor.capture(), mCenterYCaptor.capture(), @@ -717,7 +721,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration, mAnimationCallback); - deleteWindowMagnificationAndWaitAnimating(0, null); + // Verifying that WindowMagnificationController#deleteWindowMagnification is never called + // in previous steps + verify(mSpyController, never()).deleteWindowMagnification(); + + deleteWindowMagnificationWithoutAnimation(); verify(mSpyController).deleteWindowMagnification(); verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN); @@ -810,6 +818,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mWindowMagnificationAnimationController.enableWindowMagnification( targetScale, targetCenterX, targetCenterY, null); }); + // wait for animation returns + waitForIdleSync(); } private void enableWindowMagnificationAndWaitAnimating(long duration, @@ -829,12 +839,16 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { targetScale, targetCenterX, targetCenterY, callback); advanceTimeBy(duration); }); + // wait for animation returns + waitForIdleSync(); } private void deleteWindowMagnificationWithoutAnimation() { getInstrumentation().runOnMainSync(() -> { mWindowMagnificationAnimationController.deleteWindowMagnification(null); }); + // wait for animation returns + waitForIdleSync(); } private void deleteWindowMagnificationAndWaitAnimating(long duration, @@ -843,6 +857,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { mWindowMagnificationAnimationController.deleteWindowMagnification(callback); advanceTimeBy(duration); }); + // wait for animation returns + waitForIdleSync(); } private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt index 7c626a141a4a..e0c6bbad5635 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt @@ -44,6 +44,8 @@ import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeViewController import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.util.concurrency.FakeExecutor @@ -56,6 +58,7 @@ import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import org.junit.Before @@ -89,6 +92,9 @@ class BackActionInteractorTest : SysuiTestCase() { @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher @Mock private lateinit var iStatusBarService: IStatusBarService @Mock private lateinit var headsUpManager: HeadsUpManager + private val activeNotificationsRepository = ActiveNotificationListRepository() + private val activeNotificationsInteractor = + ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher()) private val keyguardRepository = FakeKeyguardRepository() private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy { @@ -98,6 +104,7 @@ class BackActionInteractorTest : SysuiTestCase() { keyguardRepository, headsUpManager, powerInteractor, + activeNotificationsInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 0ee09390d03a..43f7c60721ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.biometrics import android.app.admin.DevicePolicyManager +import android.content.pm.PackageManager import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricManager @@ -79,6 +80,8 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever +private const val OP_PACKAGE_NAME = "biometric.testapp" + @RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest @@ -109,6 +112,8 @@ open class AuthContainerViewTest : SysuiTestCase() { lateinit var authController: AuthController @Mock lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock + private lateinit var packageManager: PackageManager private val testScope = TestScope(StandardTestDispatcher()) private val fakeExecutor = FakeExecutor(FakeSystemClock()) @@ -134,6 +139,7 @@ open class AuthContainerViewTest : SysuiTestCase() { private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) + private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) private var authContainer: TestAuthContainerView? = null @@ -156,6 +162,9 @@ open class AuthContainerViewTest : SysuiTestCase() { selectedUserInteractor, testScope.backgroundScope, ) + // Set up default logo icon + whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon) + context.setMockPackageManager(packageManager) } @After @@ -533,6 +542,7 @@ open class AuthContainerViewTest : SysuiTestCase() { mPromptInfo = PromptInfo().apply { this.authenticators = authenticators } + mOpPackageName = OP_PACKAGE_NAME }, testScope.backgroundScope, fingerprintProps, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt index ec7ce634fd78..b39e09df9d2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt @@ -43,6 +43,7 @@ import org.mockito.junit.MockitoJUnit private const val USER_ID = 9 private const val CHALLENGE = 90L +private const val OP_PACKAGE_NAME = "biometric.testapp" @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -102,7 +103,8 @@ class PromptRepositoryImplTest : SysuiTestCase() { PromptInfo().apply { isConfirmationRequested = case }, USER_ID, CHALLENGE, - PromptKind.Biometric() + PromptKind.Biometric(), + OP_PACKAGE_NAME ) assertThat(isConfirmationRequired).isEqualTo(case) @@ -120,7 +122,8 @@ class PromptRepositoryImplTest : SysuiTestCase() { PromptInfo().apply { isConfirmationRequested = case }, USER_ID, CHALLENGE, - PromptKind.Biometric() + PromptKind.Biometric(), + OP_PACKAGE_NAME ) assertThat(isConfirmationRequired).isTrue() @@ -133,17 +136,19 @@ class PromptRepositoryImplTest : SysuiTestCase() { val kind = PromptKind.Pin val promptInfo = PromptInfo() - repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind) + repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME) assertThat(repository.kind.value).isEqualTo(kind) assertThat(repository.userId.value).isEqualTo(USER_ID) assertThat(repository.challenge.value).isEqualTo(CHALLENGE) assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo) + assertThat(repository.opPackageName.value).isEqualTo(OP_PACKAGE_NAME) repository.unsetPrompt() assertThat(repository.promptInfo.value).isNull() assertThat(repository.userId.value).isNull() assertThat(repository.challenge.value).isNull() + assertThat(repository.opPackageName.value).isNull() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt index dcefea28d4c8..8a46c0c6da9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt @@ -30,6 +30,7 @@ import org.mockito.junit.MockitoJUnit private const val USER_ID = 22 private const val OPERATION_ID = 100L +private const val OP_PACKAGE_NAME = "biometric.testapp" @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -114,7 +115,8 @@ class PromptCredentialInteractorTest : SysuiTestCase() { }, kind = kind, userId = USER_ID, - challenge = OPERATION_ID + challenge = OPERATION_ID, + opPackageName = OP_PACKAGE_NAME ) assertThat(prompt?.title).isEqualTo(title) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt index f15b738f3e95..52b42750847a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt @@ -51,6 +51,7 @@ private const val NEGATIVE_TEXT = "escape" private const val USER_ID = 8 private const val CHALLENGE = 999L +private const val OP_PACKAGE_NAME = "biometric.testapp" @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -113,13 +114,20 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { assertThat(currentPrompt).isNull() - interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities) + interactor.useBiometricsForAuthentication( + info, + USER_ID, + CHALLENGE, + modalities, + OP_PACKAGE_NAME + ) assertThat(currentPrompt).isNotNull() assertThat(currentPrompt?.title).isEqualTo(TITLE) assertThat(currentPrompt?.description).isEqualTo(DESCRIPTION) assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE) assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT) + assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME) if (allowCredentialFallback) { assertThat(credentialKind).isSameInstanceAs(PromptKind.Password) @@ -167,7 +175,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { assertThat(currentPrompt).isNull() - interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE) + interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME) // not using biometrics, should be null with no fallback option assertThat(currentPrompt).isNull() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt index bd4973d65006..a46167a423f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt @@ -1,6 +1,7 @@ package com.android.systemui.biometrics.domain.model -import android.hardware.biometrics.PromptContentListItemBulletedText +import android.graphics.Bitmap +import android.hardware.biometrics.PromptContentItemBulletedText import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -8,6 +9,7 @@ import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal import com.android.systemui.biometrics.promptInfo import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricUserInfo +import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -15,6 +17,7 @@ import org.junit.runners.JUnit4 private const val USER_ID = 2 private const val OPERATION_ID = 8L +private const val OP_PACKAGE_NAME = "biometric.testapp" @SmallTest @RunWith(JUnit4::class) @@ -22,19 +25,22 @@ class BiometricPromptRequestTest : SysuiTestCase() { @Test fun biometricRequestFromPromptInfo() { + val logoRes = R.drawable.ic_cake val title = "what" val subtitle = "a" val description = "request" val contentView = PromptVerticalListContentView.Builder() .setDescription("content description") - .addListItem(PromptContentListItemBulletedText("content text")) + .addListItem(PromptContentItemBulletedText("content item 1")) + .addListItem(PromptContentItemBulletedText("content item 2"), 1) .build() val fpPros = fingerprintSensorPropertiesInternal().first() val request = BiometricPromptRequest.Biometric( promptInfo( + logoRes = logoRes, title = title, subtitle = subtitle, description = description, @@ -43,8 +49,10 @@ class BiometricPromptRequestTest : SysuiTestCase() { BiometricUserInfo(USER_ID), BiometricOperationInfo(OPERATION_ID), BiometricModalities(fingerprintProperties = fpPros), + OP_PACKAGE_NAME, ) + assertThat(request.logoRes).isEqualTo(logoRes) assertThat(request.title).isEqualTo(title) assertThat(request.subtitle).isEqualTo(subtitle) assertThat(request.description).isEqualTo(description) @@ -56,6 +64,23 @@ class BiometricPromptRequestTest : SysuiTestCase() { } @Test + fun biometricRequestLogoBitmapFromPromptInfo() { + val logoBitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888) + val fpPros = fingerprintSensorPropertiesInternal().first() + val request = + BiometricPromptRequest.Biometric( + promptInfo( + logoBitmap = logoBitmap, + ), + BiometricUserInfo(USER_ID), + BiometricOperationInfo(OPERATION_ID), + BiometricModalities(fingerprintProperties = fpPros), + OP_PACKAGE_NAME, + ) + assertThat(request.logoBitmap).isEqualTo(logoBitmap) + } + + @Test fun credentialRequestFromPromptInfo() { val title = "what" val subtitle = "a" diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index bf61c2e6d32c..3888f2b940b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -16,9 +16,12 @@ package com.android.systemui.biometrics.ui.viewmodel +import android.content.pm.PackageManager import android.content.res.Configuration +import android.graphics.Bitmap import android.graphics.Point -import android.hardware.biometrics.PromptContentListItemBulletedText +import android.graphics.drawable.BitmapDrawable +import android.hardware.biometrics.PromptContentItemBulletedText import android.hardware.biometrics.PromptContentView import android.hardware.biometrics.PromptInfo import android.hardware.biometrics.PromptVerticalListContentView @@ -76,6 +79,7 @@ import org.mockito.junit.MockitoJUnit private const val USER_ID = 4 private const val CHALLENGE = 2L private const val DELAY = 1000L +private const val OP_PACKAGE_NAME = "biometric.testapp" @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -88,9 +92,14 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Mock private lateinit var authController: AuthController @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor @Mock private lateinit var udfpsUtils: UdfpsUtils + @Mock private lateinit var packageManager: PackageManager private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val testScope = TestScope() + private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) + private val logoResFromApp = R.drawable.ic_cake + private val logoFromApp = context.getDrawable(logoResFromApp) + private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565) private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository private lateinit var promptRepository: FakePromptRepository @@ -140,7 +149,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa selector.resetPrompt() promptContentView = PromptVerticalListContentView.Builder() - .addListItem(PromptContentListItemBulletedText("test")) + .addListItem(PromptContentItemBulletedText("content item 1")) + .addListItem(PromptContentItemBulletedText("content item 2"), 1) .build() viewModel = @@ -152,6 +162,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa udfpsUtils ) iconViewModel = viewModel.iconViewModel + + // Set up default logo icon and app customized icon + whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon) + context.setMockPackageManager(packageManager) + val resources = context.getOrCreateTestableResources() + resources.addOverride(logoResFromApp, logoFromApp) } @Test @@ -1226,6 +1242,26 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(contentView).isNull() } + @Test + fun defaultLogoIfNoLogoSet() = runGenericTest { + val logo by collectLastValue(viewModel.logo) + assertThat(logo).isEqualTo(defaultLogoIcon) + } + + @Test + fun logoResSetByApp() = + runGenericTest(logoRes = logoResFromApp) { + val logo by collectLastValue(viewModel.logo) + assertThat(logo).isEqualTo(logoFromApp) + } + + @Test + fun logoBitmapSetByApp() = + runGenericTest(logoBitmap = logoBitmapFromApp) { + val logo by collectLastValue(viewModel.logo) + assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp) + } + /** Asserts that the selected buttons are visible now. */ private suspend fun TestScope.assertButtonsVisible( tryAgain: Boolean = false, @@ -1247,6 +1283,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa allowCredentialFallback: Boolean = false, description: String? = null, contentView: PromptContentView? = null, + logoRes: Int = -1, + logoBitmap: Bitmap? = null, block: suspend TestScope.() -> Unit ) { selector.initializePrompt( @@ -1256,6 +1294,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa face = testCase.face, descriptionFromApp = description, contentViewFromApp = contentView, + logoResFromApp = logoRes, + logoBitmapFromApp = logoBitmap, ) // put the view model in the initial authenticating state, unless explicitly skipped @@ -1433,9 +1473,13 @@ private fun PromptSelectorInteractor.initializePrompt( allowCredentialFallback: Boolean = false, descriptionFromApp: String? = null, contentViewFromApp: PromptContentView? = null, + logoResFromApp: Int = -1, + logoBitmapFromApp: Bitmap? = null, ) { val info = PromptInfo().apply { + logoRes = logoResFromApp + logoBitmap = logoBitmapFromApp title = "t" subtitle = "s" description = descriptionFromApp @@ -1444,11 +1488,13 @@ private fun PromptSelectorInteractor.initializePrompt( isDeviceCredentialAllowed = allowCredentialFallback isConfirmationRequested = requireConfirmation } + useBiometricsForAuthentication( info, USER_ID, CHALLENGE, BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face), + OP_PACKAGE_NAME, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt index 44c57f34fa1b..134c40da1033 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt @@ -3,8 +3,9 @@ package com.android.systemui.bouncer.data.repository import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.testScope import com.android.systemui.log.table.TableLogBuffer -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.SystemClock @@ -23,8 +24,8 @@ class KeyguardBouncerRepositoryTest : SysuiTestCase() { @Mock private lateinit var systemClock: SystemClock @Mock private lateinit var bouncerLogger: TableLogBuffer - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope lateinit var underTest: KeyguardBouncerRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt new file mode 100644 index 000000000000..d397fc202637 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.stickykeys.ui.viewmodel + +import android.hardware.input.InputManager +import android.hardware.input.StickyModifierState +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository +import com.android.systemui.keyboard.stickykeys.StickyKeysLogger +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions + +@SmallTest +@RunWith(JUnit4::class) +class StickyKeysIndicatorViewModelTest : SysuiTestCase() { + + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) + private lateinit var viewModel: StickyKeysIndicatorViewModel + private val inputManager = mock<InputManager>() + private val keyboardRepository = FakeKeyboardRepository() + private val captor = + ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java) + + @Before + fun setup() { + val stickyKeysRepository = StickyKeysRepositoryImpl( + inputManager, + dispatcher, + mock<StickyKeysLogger>() + ) + viewModel = + StickyKeysIndicatorViewModel( + stickyKeysRepository = stickyKeysRepository, + keyboardRepository = keyboardRepository, + applicationScope = testScope.backgroundScope, + ) + } + + @Test + fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnected() { + testScope.runTest { + collectLastValue(viewModel.indicatorContent) + runCurrent() + verifyZeroInteractions(inputManager) + + keyboardRepository.setIsAnyKeyboardConnected(true) + runCurrent() + + verify(inputManager) + .registerStickyModifierStateListener( + any(), + any(InputManager.StickyModifierStateListener::class.java) + ) + } + } + + @Test + fun stopsListeningToStickyKeysWhenKeyboardDisconnects() { + testScope.runTest { + collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + runCurrent() + + keyboardRepository.setIsAnyKeyboardConnected(false) + runCurrent() + + verify(inputManager).unregisterStickyModifierStateListener(any()) + } + } + + @Test + fun emitsStickyKeysListWhenStickyKeyIsPressed() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf(ALT to false)) + + assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(false))) + } + } + + @Test + fun emitsEmptyListWhenNoStickyKeysAreActive() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(emptyMap()) + + assertThat(stickyKeys).isEqualTo(emptyMap<ModifierKey, Locked>()) + } + } + + @Test + fun passesAllStickyKeysToDialog() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf( + ALT to false, + META to false, + SHIFT to false)) + + assertThat(stickyKeys).isEqualTo(mapOf( + ALT to Locked(false), + META to Locked(false), + SHIFT to Locked(false), + )) + } + } + + @Test + fun showsOnlyLockedStateIfKeyIsStickyAndLocked() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf( + ALT to false, + ALT to true)) + + assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(true))) + } + } + + @Test + fun doesNotChangeOrderOfKeysIfTheyBecomeLocked() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf( + META to false, + SHIFT to false, // shift is sticky but not locked + CTRL to false)) + val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false)) + + setStickyKeys(mapOf( + SHIFT to false, + SHIFT to true, // shift is now locked + META to false, + CTRL to false)) + assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true))) + .isEqualTo(previousShiftIndex) + } + } + + private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) { + runCurrent() + verify(inputManager).registerStickyModifierStateListener(any(), captor.capture()) + captor.value.onStickyModifierStateChanged(TestStickyModifierState(keys)) + runCurrent() + } + + private class TestStickyModifierState(private val keys: Map<ModifierKey, Boolean>) : + StickyModifierState() { + + private fun isOn(key: ModifierKey) = keys.any { it.key == key && !it.value } + private fun isLocked(key: ModifierKey) = keys.any { it.key == key && it.value } + + override fun isAltGrModifierLocked() = isLocked(ALT_GR) + override fun isAltGrModifierOn() = isOn(ALT_GR) + override fun isAltModifierLocked() = isLocked(ALT) + override fun isAltModifierOn() = isOn(ALT) + override fun isCtrlModifierLocked() = isLocked(CTRL) + override fun isCtrlModifierOn() = isOn(CTRL) + override fun isMetaModifierLocked() = isLocked(META) + override fun isMetaModifierOn() = isOn(META) + override fun isShiftModifierLocked() = isLocked(SHIFT) + override fun isShiftModifierOn() = isOn(SHIFT) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 0ea4e9f8d416..8b6611f339c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -1342,14 +1342,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD) runCurrent() - // WHEN the keyguard is occluded and aod ends + // WHEN the keyguard is occluded keyguardRepository.setKeyguardOccluded(true) - keyguardRepository.setDozeTransitionModel( - DozeTransitionModel( - from = DozeStateModel.DOZE_AOD, - to = DozeStateModel.FINISH, - ) - ) runCurrent() val info = @@ -1366,6 +1360,30 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun aodToPrimaryBouncer() = + testScope.runTest { + // GIVEN a prior transition has run to AOD + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD) + runCurrent() + + // WHEN the primary bouncer is set to show + bouncerRepository.setPrimaryShow(true) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.AOD) + assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun lockscreenToOccluded_fromCameraGesture() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt new file mode 100644 index 000000000000..60eb3aec190f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs + +import android.testing.AndroidTestingRunner +import android.view.KeyEvent +import android.view.KeyEvent.KEYCODE_DPAD_LEFT +import android.view.View +import androidx.core.util.Consumer +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class LeftRightArrowPressedListenerTest : SysuiTestCase() { + + private lateinit var underTest: LeftRightArrowPressedListener + private val callback = + object : Consumer<Int> { + var lastValue: Int? = null + + override fun accept(keyCode: Int?) { + lastValue = keyCode + } + } + + private val view = View(context) + + @Before + fun setUp() { + underTest = LeftRightArrowPressedListener.createAndRegisterListenerForView(view) + underTest.setArrowKeyPressedListener(callback) + } + + @Test + fun shouldTriggerCallback_whenArrowUpReceived_afterArrowDownReceived() { + underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT) + + underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT) + + assertThat(callback.lastValue).isEqualTo(KEYCODE_DPAD_LEFT) + } + + @Test + fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownNotReceived() { + underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT) + + assertThat(callback.lastValue).isNull() + } + + @Test + fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownWasRepeated() { + underTest.sendKeyWithRepeat(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT, repeat = 2) + underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT) + + assertThat(callback.lastValue).isNull() + } + + @Test + fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownReceivedBeforeLosingFocus() { + underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT) + underTest.onFocusChange(view, hasFocus = false) + underTest.onFocusChange(view, hasFocus = true) + + underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT) + + assertThat(callback.lastValue).isNull() + } + + private fun LeftRightArrowPressedListener.sendKey(action: Int, keyCode: Int) { + onKey(view, keyCode, KeyEvent(action, keyCode)) + } + + private fun LeftRightArrowPressedListener.sendKeyWithRepeat( + action: Int, + keyCode: Int, + repeat: Int + ) { + val keyEvent = + KeyEvent( + /* downTime= */ 0L, + /* eventTime= */ 0L, + /* action= */ action, + /* code= */ KEYCODE_DPAD_LEFT, + /* repeat= */ repeat + ) + onKey(view, keyCode, keyEvent) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt index db9e548e74c8..8ef3f57103a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt @@ -2,11 +2,13 @@ package com.android.systemui.qs import android.content.Context import android.testing.AndroidTestingRunner -import android.view.KeyEvent import android.view.View import android.widget.Scroller import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.PageIndicator.PageScrollActionListener +import com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT +import com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -22,7 +24,7 @@ import org.mockito.MockitoAnnotations class PagedTileLayoutTest : SysuiTestCase() { @Mock private lateinit var pageIndicator: PageIndicator - @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener> + @Captor private lateinit var captor: ArgumentCaptor<PageScrollActionListener> private lateinit var pageTileLayout: TestPagedTileLayout private lateinit var scroller: Scroller @@ -32,7 +34,7 @@ class PagedTileLayoutTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) pageTileLayout = TestPagedTileLayout(mContext) pageTileLayout.setPageIndicator(pageIndicator) - verify(pageIndicator).setOnKeyListener(captor.capture()) + verify(pageIndicator).setPageScrollActionListener(captor.capture()) setViewWidth(pageTileLayout, width = PAGE_WIDTH) scroller = pageTileLayout.mScroller } @@ -43,28 +45,27 @@ class PagedTileLayoutTest : SysuiTestCase() { } @Test - fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() { + fun scrollsRight_afterRightScrollActionTriggered() { pageTileLayout.currentPageIndex = 0 - sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT) + sendScrollActionEvent(RIGHT) assertThat(scroller.isFinished).isFalse() // aka we're scrolling assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH) } @Test - fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() { + fun scrollsLeft_afterLeftScrollActionTriggered() { pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page - sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT) + sendScrollActionEvent(LEFT) assertThat(scroller.isFinished).isFalse() // aka we're scrolling assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH) } - private fun sendUpEvent(keyCode: Int) { - val event = KeyEvent(KeyEvent.ACTION_UP, keyCode) - captor.value.onKey(pageIndicator, keyCode, event) + private fun sendScrollActionEvent(@PageScrollActionListener.Direction direction: Int) { + captor.value.onScrollActionTriggered(direction) } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 49579f6f46b4..b3e386e69905 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -113,6 +113,7 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransition import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.KeyguardMediaController; import com.android.systemui.media.controls.ui.MediaHierarchyManager; @@ -125,7 +126,6 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeAnimationRepository; @@ -355,8 +355,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected FakeKeyguardRepository mFakeKeyguardRepository; protected KeyguardInteractor mKeyguardInteractor; protected ShadeAnimationInteractor mShadeAnimationInteractor; - protected SceneTestUtils mUtils = new SceneTestUtils(this); - protected TestScope mTestScope = mUtils.getTestScope(); + protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + protected TestScope mTestScope = mKosmos.getTestScope(); protected ShadeInteractor mShadeInteractor; protected PowerInteractor mPowerInteractor; protected NotificationPanelViewController.TouchHandler mTouchHandler; diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 971c8a3d1c05..a894f877fe3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -73,11 +73,11 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions; import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.kosmos.KosmosJavaAdapter; 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.FakeWindowRootViewComponent; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; @@ -150,8 +150,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { private final Executor mMainExecutor = MoreExecutors.directExecutor(); private final Executor mBackgroundExecutor = MoreExecutors.directExecutor(); - private final SceneTestUtils mUtils = new SceneTestUtils(this); - private final TestScope mTestScope = mUtils.getTestScope(); + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + private final TestScope mTestScope = mKosmos.getTestScope(); private ShadeInteractor mShadeInteractor; private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; @@ -178,15 +178,15 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic(); FakeShadeRepository shadeRepository = new FakeShadeRepository(); - mScreenOffAnimationController = mUtils.getScreenOffAnimationController(); - mStatusBarStateController = spy(mUtils.getStatusBarStateController()); - PowerInteractor powerInteractor = mUtils.powerInteractor(); + mScreenOffAnimationController = mKosmos.getScreenOffAnimationController(); + mStatusBarStateController = spy(mKosmos.getStatusBarStateController()); + PowerInteractor powerInteractor = mKosmos.getPowerInteractor(); SceneInteractor sceneInteractor = new SceneInteractor( mTestScope.getBackgroundScope(), new SceneContainerRepository( mTestScope.getBackgroundScope(), - mUtils.fakeSceneContainerConfig()), + mKosmos.getFakeSceneContainerConfig()), powerInteractor, mock(SceneLogger.class)); @@ -219,8 +219,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, shadeRepository, @@ -245,8 +245,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, mKeyguardSecurityModel, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index ca68fd867b39..cbd4d2bfe377 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -61,6 +61,7 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions; import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; @@ -68,7 +69,6 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; @@ -131,8 +131,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { protected QuickSettingsController mQsController; - protected SceneTestUtils mUtils = new SceneTestUtils(this); - protected TestScope mTestScope = mUtils.getTestScope(); + protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + protected TestScope mTestScope = mKosmos.getTestScope(); @Mock protected Resources mResources; @Mock protected KeyguardBottomAreaView mQsFrame; @@ -203,8 +203,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController); - mStatusBarStateController = mUtils.getStatusBarStateController(); - mInteractionJankMonitor = mUtils.getInteractionJankMonitor(); + mStatusBarStateController = mKosmos.getStatusBarStateController(); + mInteractionJankMonitor = mKosmos.getInteractionJankMonitor(); FakeDeviceProvisioningRepository deviceProvisioningRepository = new FakeDeviceProvisioningRepository(); @@ -212,13 +212,13 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic(); FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository(); - PowerInteractor powerInteractor = mUtils.powerInteractor(); + PowerInteractor powerInteractor = mKosmos.getPowerInteractor(); SceneInteractor sceneInteractor = new SceneInteractor( mTestScope.getBackgroundScope(), new SceneContainerRepository( mTestScope.getBackgroundScope(), - mUtils.fakeSceneContainerConfig()), + mKosmos.getFakeSceneContainerConfig()), powerInteractor, mock(SceneLogger.class)); @@ -250,8 +250,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, mShadeRepository, @@ -276,8 +276,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, mock(KeyguardSecurityModel.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 215f8b1c462f..c4911a41b4a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -33,6 +33,8 @@ import com.android.systemui.scene.data.repository.WindowRootViewVisibilityReposi import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.row.NotificationGutsManager import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -45,6 +47,7 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import dagger.Lazy +import kotlinx.coroutines.test.StandardTestDispatcher import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -59,6 +62,8 @@ import org.mockito.MockitoAnnotations @SmallTest class ShadeControllerImplTest : SysuiTestCase() { private val executor = FakeExecutor(FakeSystemClock()) + private val testDispatcher = StandardTestDispatcher() + private val activeNotificationsRepository = ActiveNotificationListRepository() @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var keyguardStateController: KeyguardStateController @@ -84,6 +89,7 @@ class ShadeControllerImplTest : SysuiTestCase() { FakeKeyguardRepository(), headsUpManager, PowerInteractorFactory.create().powerInteractor, + ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 4a365b9ab084..05e866e85112 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -41,10 +41,12 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.shade.data.repository.FakeShadeRepository @@ -56,6 +58,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Share import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import kotlinx.coroutines.flow.emptyFlow import org.junit.Assert.assertEquals @@ -71,17 +74,17 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito import org.mockito.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) @TestableLooper.RunWithLooper class StatusBarStateControllerImplTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val testDispatcher = utils.testDispatcher + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val testDispatcher = kosmos.testDispatcher private lateinit var shadeInteractor: ShadeInteractor private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor private lateinit var fromPrimaryBouncerTransitionInteractor: @@ -131,7 +134,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { FakeKeyguardBouncerRepository(), ConfigurationInteractor(configurationRepository), shadeRepository, - utils::sceneInteractor + { kosmos.sceneInteractor }, ) val keyguardTransitionInteractor = KeyguardTransitionInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt index 65697b736cbd..36f643ab9cca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -24,7 +24,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -53,8 +52,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @@ -78,28 +77,22 @@ class ConversationCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: ConversationCoordinator - private val featureFlags = FakeFeatureFlagsClassic() - @Before fun setUp() { MockitoAnnotations.initMocks(this) - coordinator = ConversationCoordinator( - peopleNotificationIdentifier, - conversationIconManager, - HighPriorityProvider( + coordinator = + ConversationCoordinator( peopleNotificationIdentifier, - GroupMembershipManagerImpl(featureFlags) - ), - headerController - ) + conversationIconManager, + HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()), + headerController + ) whenever(channel.isImportantConversation).thenReturn(true) coordinator.attach(pipeline) // capture arguments: - promoter = withArgCaptor { - verify(pipeline).addPromoter(capture()) - } + promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) } beforeRenderListListener = withArgCaptor { verify(pipeline).addOnBeforeRenderListListener(capture()) } @@ -111,10 +104,10 @@ class ConversationCoordinatorTest : SysuiTestCase() { entry = NotificationEntryBuilder().setChannel(channel).build() val section = NotifSection(peopleAlertingSectioner, 0) - entryA = NotificationEntryBuilder().setChannel(channel) - .setSection(section).setTag("A").build() - entryB = NotificationEntryBuilder().setChannel(channel) - .setSection(section).setTag("B").build() + entryA = + NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build() + entryB = + NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build() } @Test @@ -129,11 +122,12 @@ class ConversationCoordinatorTest : SysuiTestCase() { val altChildA = NotificationEntryBuilder().setTag("A").build() val altChildB = NotificationEntryBuilder().setTag("B").build() val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build() - val groupEntry = GroupEntryBuilder() - .setParent(GroupEntry.ROOT_ENTRY) - .setSummary(summary) - .setChildren(listOf(entry, altChildA, altChildB)) - .build() + val groupEntry = + GroupEntryBuilder() + .setParent(GroupEntry.ROOT_ENTRY) + .setSummary(summary) + .setChildren(listOf(entry, altChildA, altChildB)) + .build() assertTrue(promoter.shouldPromoteToTopLevel(entry)) assertFalse(promoter.shouldPromoteToTopLevel(altChildA)) assertFalse(promoter.shouldPromoteToTopLevel(altChildB)) @@ -146,41 +140,42 @@ class ConversationCoordinatorTest : SysuiTestCase() { @Test fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() { // GIVEN - val alertingEntry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_DEFAULT).build() + val alertingEntry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry)) - .thenReturn(TYPE_PERSON) + .thenReturn(TYPE_PERSON) // put alerting people notifications in this section assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue() - } + } @Test fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() { // GIVEN - val silentEntry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_LOW).build() + val silentEntry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry)) - .thenReturn(TYPE_PERSON) + .thenReturn(TYPE_PERSON) // THEN put silent people notifications in this section assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue() // People Alerting sectioning happens before the silent one. - // It claims high important conversations and rest of conversations will be considered as silent. + // It claims high important conversations and rest of conversations will be considered as + // silent. assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse() } @Test fun testNotInPeopleSection() { // GIVEN - val entry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_LOW).build() - val importantEntry = NotificationEntryBuilder().setChannel(channel) - .setImportance(IMPORTANCE_HIGH).build() + val entry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build() + val importantEntry = + NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry)) - .thenReturn(TYPE_NON_PERSON) + .thenReturn(TYPE_NON_PERSON) whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry)) - .thenReturn(TYPE_NON_PERSON) + .thenReturn(TYPE_NON_PERSON) // THEN - only put people notification either silent or alerting assertThat(peopleSilentSectioner.isInSection(entry)).isFalse() @@ -190,19 +185,23 @@ class ConversationCoordinatorTest : SysuiTestCase() { @Test fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() { // GIVEN - val altChildA = NotificationEntryBuilder().setTag("A") - .setImportance(IMPORTANCE_DEFAULT).build() - val altChildB = NotificationEntryBuilder().setTag("B") - .setImportance(IMPORTANCE_LOW).build() - val summary = NotificationEntryBuilder().setId(2) - .setImportance(IMPORTANCE_LOW).setChannel(channel).build() - val groupEntry = GroupEntryBuilder() + val altChildA = + NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build() + val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build() + val summary = + NotificationEntryBuilder() + .setId(2) + .setImportance(IMPORTANCE_LOW) + .setChannel(channel) + .build() + val groupEntry = + GroupEntryBuilder() .setParent(GroupEntry.ROOT_ENTRY) .setSummary(summary) .setChildren(listOf(altChildA, altChildB)) .build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary)) - .thenReturn(TYPE_PERSON) + .thenReturn(TYPE_PERSON) // THEN assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 168e782d0481..ff02ef3d4e62 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -28,6 +28,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; + import android.app.Notification; import android.os.Handler; import android.os.Looper; @@ -56,6 +58,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.logging.nano.Notifications; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -110,6 +114,11 @@ public class NotificationLoggerTest extends SysuiTestCase { private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); private final PowerInteractor mPowerInteractor = PowerInteractorFactory.create().getPowerInteractor(); + private final ActiveNotificationListRepository mActiveNotificationListRepository = + new ActiveNotificationListRepository(); + private final ActiveNotificationsInteractor mActiveNotificationsInteractor = + new ActiveNotificationsInteractor(mActiveNotificationListRepository, + StandardTestDispatcher(null, null)); private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); @@ -123,7 +132,8 @@ public class NotificationLoggerTest extends SysuiTestCase { new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor), mKeyguardRepository, mHeadsUpManager, - mPowerInteractor); + mPowerInteractor, + mActiveNotificationsInteractor); mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); mEntry = new NotificationEntryBuilder() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 8a730cfd7ddd..71613edb8737 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -42,6 +42,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; + import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; @@ -85,6 +87,8 @@ import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -156,6 +160,12 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private UserManager mUserManager; + private final ActiveNotificationListRepository mActiveNotificationListRepository = + new ActiveNotificationListRepository(); + private final ActiveNotificationsInteractor mActiveNotificationsInteractor = + new ActiveNotificationsInteractor(mActiveNotificationListRepository, + StandardTestDispatcher(null, null)); + private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; @Before @@ -171,7 +181,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { new WindowRootViewVisibilityRepository(mBarService, mExecutor), new FakeKeyguardRepository(), mHeadsUpManager, - PowerInteractorFactory.create().getPowerInteractor()); + PowerInteractorFactory.create().getPowerInteractor(), + mActiveNotificationsInteractor); mGutsManager = new NotificationGutsManager( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 88662b60c7d1..89f826b2049d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -66,6 +66,8 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -91,6 +93,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScrimController; @@ -112,6 +115,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import javax.inject.Provider; + /** * Tests for {@link NotificationStackScrollLayoutController}. */ @@ -153,6 +158,9 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; @Mock private ShadeController mShadeController; + @Mock private SceneContainerFlags mSceneContainerFlags; + @Mock private Provider<WindowRootView> mWindowRootView; + @Mock private NotificationStackAppearanceInteractor mNotificationStackAppearanceInteractor; @Mock private InteractionJankMonitor mJankMonitor; private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(), logcatLogBuffer()); @@ -724,6 +732,9 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mSeenNotificationsInteractor, mViewBinder, mShadeController, + mSceneContainerFlags, + mWindowRootView, + mNotificationStackAppearanceInteractor, mJankMonitor, mStackLogger, mLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index a1721208b2f2..4afcc8c9da43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -20,6 +20,7 @@ import static android.view.View.GONE; import static android.view.WindowInsets.Type.ime; import static com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION; +import static com.android.systemui.Flags.FLAG_SCENE_CONTAINER; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL; @@ -37,6 +38,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; @@ -51,6 +53,7 @@ import static org.mockito.Mockito.when; import android.graphics.Insets; import android.graphics.Rect; +import android.os.SystemClock; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; @@ -74,6 +77,7 @@ import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.EmptyShadeView; @@ -93,11 +97,13 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -947,6 +953,78 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { verify(runnable).run(); } + @Test + public void testDispatchTouchEvent_sceneContainerDisabled() { + Assume.assumeFalse(SceneContainerFlag.isEnabled()); + + MotionEvent event = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_MOVE, + 0, + 0, + 0 + ); + + mStackScroller.dispatchTouchEvent(event); + + verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any()); + } + + @Test + public void testDispatchTouchEvent_sceneContainerEnabled() { + Assume.assumeTrue(SceneContainerFlag.isEnabled()); + mStackScroller.setIsBeingDragged(true); + + MotionEvent moveEvent = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_MOVE, + 0, + 0, + 0 + ); + MotionEvent syntheticDownEvent = moveEvent.copy(); + syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN); + mStackScroller.dispatchTouchEvent(moveEvent); + + verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat( + new MotionEventMatcher(syntheticDownEvent))); + + mStackScroller.dispatchTouchEvent(moveEvent); + + verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent); + } + + @Test + @EnableFlags(FLAG_SCENE_CONTAINER) + public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() { + Assume.assumeTrue(SceneContainerFlag.isEnabled()); + mStackScroller.setIsBeingDragged(true); + + MotionEvent upEvent = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_UP, + 0, + 0, + 0 + ); + MotionEvent syntheticDownEvent = upEvent.copy(); + syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN); + + mStackScroller.dispatchTouchEvent(upEvent); + + verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat( + new MotionEventMatcher(syntheticDownEvent))); + + mStackScroller.dispatchTouchEvent(upEvent); + + verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat( + new MotionEventMatcher(upEvent))); + assertFalse(mStackScroller.getIsBeingDragged()); + } + private void setBarStateForTest(int state) { // Can't inject this through the listener or we end up on the actual implementation // rather than the mock because the spy just coppied the anonymous inner /shruggie. @@ -993,4 +1071,21 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { /* metaState= */0 ); } + + private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> { + private final MotionEvent mLeftEvent; + + MotionEventMatcher(MotionEvent leftEvent) { + mLeftEvent = leftEvent; + } + + @Override + public boolean matches(MotionEvent right) { + return mLeftEvent.getActionMasked() == right.getActionMasked() + && mLeftEvent.getDownTime() == right.getDownTime() + && mLeftEvent.getEventTime() == right.getEventTime() + && mLeftEvent.getX() == right.getX() + && mLeftEvent.getY() == right.getY(); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt index 91cbc3274900..7362e342f321 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt @@ -24,9 +24,9 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.policy.DevicePostureController @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POST import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.tuner.TunerService import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -60,8 +61,8 @@ import org.mockito.junit.MockitoJUnit @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class KeyguardBypassControllerTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private val featureFlags = FakeFeatureFlags() private val shadeRepository = FakeShadeRepository() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 5fa7f13ecd6e..2d120cd9b13f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -60,10 +60,10 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; import com.android.systemui.res.R; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.shade.ShadeViewStateProvider; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.statusbar.CommandQueue; @@ -149,7 +149,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private final TestScope mTestScope = TestScopeProvider.getTestScope(); private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); - private final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this); + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); private KeyguardInteractor mKeyguardInteractor; private KeyguardStatusBarViewModel mViewModel; @@ -166,11 +166,11 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mKeyguardRepository, mCommandQueue, PowerInteractorFactory.create().getPowerInteractor(), - mSceneTestUtils.getFakeSceneContainerFlags(), + mKosmos.getFakeSceneContainerFlags(), new FakeKeyguardBouncerRepository(), new ConfigurationInteractor(new FakeConfigurationRepository()), new FakeShadeRepository(), - () -> mSceneTestUtils.sceneInteractor()); + () -> mKosmos.getSceneInteractor()); mViewModel = new KeyguardStatusBarViewModel( mTestScope.getBackgroundScope(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index 5f9c096b1f3c..ca0e526bbc30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -25,20 +25,22 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractorFactory -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -47,20 +49,20 @@ import org.mockito.Mockito.verify @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class KeyguardStatusBarViewModelTest : SysuiTestCase() { - private val testScope = TestScope() - private val sceneTestUtils = SceneTestUtils(this) + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private val keyguardRepository = FakeKeyguardRepository() private val keyguardInteractor = KeyguardInteractor( keyguardRepository, mock<CommandQueue>(), PowerInteractorFactory.create().powerInteractor, - sceneTestUtils.fakeSceneContainerFlags, + kosmos.fakeSceneContainerFlags, FakeKeyguardBouncerRepository(), ConfigurationInteractor(FakeConfigurationRepository()), FakeShadeRepository(), ) { - sceneTestUtils.sceneInteractor() + kosmos.sceneInteractor } private val keyguardStatusBarInteractor = KeyguardStatusBarInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt index e6b9d9b09246..b6a033a7c5f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt @@ -40,13 +40,18 @@ import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Text import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.telephony.data.repository.fakeTelephonyRepository +import com.android.systemui.telephony.domain.interactor.telephonyInteractor +import com.android.systemui.testKosmos import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.source.UserRecord @@ -98,8 +103,8 @@ class UserSwitcherInteractorTest : SysuiTestCase() { @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var spyContext: Context private lateinit var userRepository: FakeUserRepository private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies @@ -121,16 +126,17 @@ class UserSwitcherInteractorTest : SysuiTestCase() { SUPERVISED_USER_CREATION_APP_PACKAGE, ) - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false) mSetFlagsRule.enableFlags(AConfigFlags.FLAG_SWITCH_USER_ON_BG) spyContext = spy(context) - keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.fakeFeatureFlags) + keyguardReply = + KeyguardInteractorFactory.create(featureFlags = kosmos.fakeFeatureFlagsClassic) keyguardRepository = keyguardReply.repository userRepository = FakeUserRepository() refreshUsersScheduler = RefreshUsersScheduler( applicationScope = testScope.backgroundScope, - mainDispatcher = utils.testDispatcher, + mainDispatcher = kosmos.testDispatcher, repository = userRepository, ) } @@ -363,7 +369,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { fun actions_deviceUnlocked_fullScreen() { createUserInteractor() testScope.runTest { - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) @@ -447,7 +453,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() { createUserInteractor() testScope.runTest { - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -640,7 +646,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { val refreshUsersCallCount = userRepository.refreshUsersCallCount - utils.telephonyRepository.setCallState(1) + kosmos.fakeTelephonyRepository.setCallState(1) runCurrent() assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) @@ -792,7 +798,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { fun userRecordsFullScreen() { createUserInteractor() testScope.runTest { - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val userInfos = createUserInfos(count = 3, includeGuest = false) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) userRepository.setUserInfos(userInfos) @@ -901,7 +907,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() { createUserInteractor() testScope.runTest { - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val expandable = mock<Expandable>() underTest.showUserSwitcher(expandable) @@ -1116,19 +1122,19 @@ class UserSwitcherInteractorTest : SysuiTestCase() { manager = manager, headlessSystemUserMode = headlessSystemUserMode, applicationScope = testScope.backgroundScope, - telephonyInteractor = utils.telephonyInteractor(), + telephonyInteractor = kosmos.telephonyInteractor, broadcastDispatcher = fakeBroadcastDispatcher, keyguardUpdateMonitor = keyguardUpdateMonitor, - backgroundDispatcher = utils.testDispatcher, - mainDispatcher = utils.testDispatcher, + backgroundDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, activityManager = activityManager, refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = GuestUserInteractor( applicationContext = spyContext, applicationScope = testScope.backgroundScope, - mainDispatcher = utils.testDispatcher, - backgroundDispatcher = utils.testDispatcher, + mainDispatcher = kosmos.testDispatcher, + backgroundDispatcher = kosmos.testDispatcher, manager = manager, repository = userRepository, deviceProvisionedController = deviceProvisionedController, @@ -1139,7 +1145,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { resetOrExitSessionReceiver = resetOrExitSessionReceiver, ), uiEventLogger = uiEventLogger, - featureFlags = utils.fakeFeatureFlags, + featureFlags = kosmos.fakeFeatureFlagsClassic, userRestrictionChecker = mock(), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 8920d4df2a91..1ed045ff6546 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -117,11 +117,11 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions; import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.scene.FakeWindowRootViewComponent; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; @@ -356,8 +356,8 @@ public class BubblesTest extends SysuiTestCase { @Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper; - private final SceneTestUtils mUtils = new SceneTestUtils(this); - private final TestScope mTestScope = mUtils.getTestScope(); + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + private final TestScope mTestScope = mKosmos.getTestScope(); private ShadeInteractor mShadeInteractor; private ShellTaskOrganizer mShellTaskOrganizer; private TaskViewTransitions mTaskViewTransitions; @@ -408,8 +408,8 @@ public class BubblesTest extends SysuiTestCase { FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository(); PowerInteractor powerInteractor = new PowerInteractor( - mUtils.getPowerRepository(), - mUtils.falsingCollector(), + mKosmos.getPowerRepository(), + mKosmos.getFalsingCollector(), mock(ScreenOffAnimationController.class), mStatusBarStateController); @@ -417,7 +417,7 @@ public class BubblesTest extends SysuiTestCase { mTestScope.getBackgroundScope(), new SceneContainerRepository( mTestScope.getBackgroundScope(), - mUtils.fakeSceneContainerConfig()), + mKosmos.getFakeSceneContainerConfig()), powerInteractor, mock(SceneLogger.class)); @@ -449,8 +449,8 @@ public class BubblesTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, shadeRepository, @@ -475,8 +475,8 @@ public class BubblesTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, mock(KeyguardSecurityModel.class), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt index 42ec8fed0127..f192de23fecc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt @@ -26,12 +26,24 @@ class FakePromptRepository : PromptRepository { private val _isConfirmationRequired = MutableStateFlow(false) override val isConfirmationRequired = _isConfirmationRequired.asStateFlow() + private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null) + override val opPackageName = _opPackageName.asStateFlow() + override fun setPrompt( promptInfo: PromptInfo, userId: Int, gatekeeperChallenge: Long?, kind: PromptKind, - ) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false) + opPackageName: String, + ) = + setPrompt( + promptInfo, + userId, + gatekeeperChallenge, + kind, + forceConfirmation = false, + opPackageName = opPackageName + ) fun setPrompt( promptInfo: PromptInfo, @@ -39,12 +51,14 @@ class FakePromptRepository : PromptRepository { gatekeeperChallenge: Long?, kind: PromptKind, forceConfirmation: Boolean = false, + opPackageName: String? = null, ) { _promptInfo.value = promptInfo _userId.value = userId _challenge.value = gatekeeperChallenge _kind.value = kind _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation + _opPackageName.value = opPackageName } override fun unsetPrompt() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt new file mode 100644 index 000000000000..79107cc818ba --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.communal.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.communalPrefsRepository: CommunalPrefsRepository by + Kosmos.Fixture { fakeCommunalPrefsRepository } +val Kosmos.fakeCommunalPrefsRepository by Kosmos.Fixture { FakeCommunalPrefsRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt new file mode 100644 index 000000000000..d3ed58bf5be0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.communal.data.repository + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Fake implementation of [CommunalPrefsRepository] */ +class FakeCommunalPrefsRepository : CommunalPrefsRepository { + private val _isCtaDismissed = MutableStateFlow(false) + override val isCtaDismissed: Flow<Boolean> = _isCtaDismissed.asStateFlow() + + override suspend fun setCtaDismissedForCurrentUser() { + _isCtaDismissed.value = true + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt index e82cae45c8f0..c85c27e277b4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn @@ -53,12 +52,4 @@ class FakeCommunalRepository( fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) { _isCommunalHubShowing.value = isCommunalHubShowing } - - private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true) - override val isCtaTileInViewModeVisible: Flow<Boolean> = - _isCtaTileInViewModeVisible.asStateFlow() - - override fun setCtaTileInViewModeVisibility(isVisible: Boolean) { - _isCtaTileInViewModeVisible.value = isVisible - } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt index 95ff889177b8..126bd6f03cca 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 @@ -17,11 +17,12 @@ package com.android.systemui.communal.domain.interactor -import android.appwidget.AppWidgetHost import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -42,7 +43,8 @@ object CommunalInteractorFactory { mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(), smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(), tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(), - appWidgetHost: AppWidgetHost = mock(), + communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(), + appWidgetHost: CommunalAppWidgetHost = mock(), editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(), ): WithDependencies { val withDeps = @@ -55,6 +57,7 @@ object CommunalInteractorFactory { testScope, communalRepository, widgetRepository, + communalPrefsRepository, mediaRepository, smartspaceRepository, tutorialRepository, @@ -66,6 +69,7 @@ object CommunalInteractorFactory { CommunalInteractor( communalRepository, widgetRepository, + communalPrefsRepository, mediaRepository, smartspaceRepository, withDeps.keyguardInteractor, @@ -79,13 +83,14 @@ object CommunalInteractorFactory { val testScope: TestScope, val communalRepository: FakeCommunalRepository, val widgetRepository: FakeCommunalWidgetRepository, + val communalPrefsRepository: FakeCommunalPrefsRepository, val mediaRepository: FakeCommunalMediaRepository, val smartspaceRepository: FakeSmartspaceRepository, val tutorialRepository: FakeCommunalTutorialRepository, val keyguardRepository: FakeKeyguardRepository, val keyguardInteractor: KeyguardInteractor, val tutorialInteractor: CommunalTutorialInteractor, - val appWidgetHost: AppWidgetHost, + val appWidgetHost: CommunalAppWidgetHost, val editWidgetsActivityStarter: EditWidgetsActivityStarter, val communalInteractor: CommunalInteractor, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 649b373258ef..7cbbaabe9dfa 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.domain.interactor import com.android.systemui.communal.data.repository.communalMediaRepository +import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -30,6 +31,7 @@ val Kosmos.communalInteractor by Fixture { communalRepository = communalRepository, widgetRepository = communalWidgetRepository, mediaRepository = communalMediaRepository, + communalPrefsRepository = communalPrefsRepository, smartspaceRepository = smartspaceRepository, appWidgetHost = mock(), keyguardInteractor = keyguardInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt index 59f0ec3cd3a5..ffca83bc6f08 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 @@ -16,11 +16,12 @@ package com.android.systemui.keyguard.domain.interactor -import android.appwidget.AppWidgetHost import com.android.systemui.communal.data.repository.communalMediaRepository +import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.kosmos.Kosmos import com.android.systemui.smartspace.data.repository.smartspaceRepository @@ -32,9 +33,10 @@ val Kosmos.communalInteractor by communalRepository = communalRepository, widgetRepository = communalWidgetRepository, mediaRepository = communalMediaRepository, + communalPrefsRepository = communalPrefsRepository, smartspaceRepository = smartspaceRepository, keyguardInteractor = keyguardInteractor, - appWidgetHost = mock(AppWidgetHost::class.java), + appWidgetHost = mock(CommunalAppWidgetHost::class.java), editWidgetsActivityStarter = mock(EditWidgetsActivityStarter::class.java), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt new file mode 100644 index 000000000000..24670b12193a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -0,0 +1,73 @@ +/* + * 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.kosmos + +import android.content.applicationContext +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.bouncerRepository +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor +import com.android.systemui.classifier.falsingCollector +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.communal.data.repository.fakeCommunalRepository +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.jank.interactionJankMonitor +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.sceneContainerConfig +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags +import com.android.systemui.statusbar.phone.screenOffAnimationController +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.util.time.systemClock + +/** Helper for using [Kosmos] from Java. */ +@Deprecated("Please convert your test to Kotlin and use [Kosmos] directly.") +class KosmosJavaAdapter( + testCase: SysuiTestCase, +) { + + private val kosmos = Kosmos() + + val testDispatcher by lazy { kosmos.testDispatcher } + val testScope by lazy { kosmos.testScope } + val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic } + val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags } + val configurationRepository by lazy { kosmos.fakeConfigurationRepository } + val configurationInteractor by lazy { kosmos.configurationInteractor } + val bouncerRepository by lazy { kosmos.bouncerRepository } + val communalRepository by lazy { kosmos.fakeCommunalRepository } + val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } + val powerRepository by lazy { kosmos.fakePowerRepository } + val clock by lazy { kosmos.systemClock } + val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository } + val simBouncerInteractor by lazy { kosmos.simBouncerInteractor } + val statusBarStateController by lazy { kosmos.statusBarStateController } + val interactionJankMonitor by lazy { kosmos.interactionJankMonitor } + val screenOffAnimationController by lazy { kosmos.screenOffAnimationController } + val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig } + val sceneInteractor by lazy { kosmos.sceneInteractor } + val falsingCollector by lazy { kosmos.falsingCollector } + val powerInteractor by lazy { kosmos.powerInteractor } + + init { + kosmos.applicationContext = testCase.context + kosmos.testCase = testCase + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt deleted file mode 100644 index d314a25658e8..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ /dev/null @@ -1,186 +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.scene - -import android.content.Context -import android.content.applicationContext -import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository -import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.domain.interactor.authenticationInteractor -import com.android.systemui.bouncer.data.repository.bouncerRepository -import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository -import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor -import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor -import com.android.systemui.bouncer.domain.interactor.bouncerInteractor -import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel -import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel -import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.classifier.domain.interactor.FalsingInteractor -import com.android.systemui.classifier.domain.interactor.falsingInteractor -import com.android.systemui.classifier.falsingCollector -import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository -import com.android.systemui.common.ui.domain.interactor.configurationInteractor -import com.android.systemui.communal.data.repository.fakeCommunalRepository -import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.domain.interactor.communalInteractor -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor -import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.jank.interactionJankMonitor -import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testCase -import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.statusbar.statusBarStateController -import com.android.systemui.power.data.repository.fakePowerRepository -import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.power.domain.interactor.powerInteractor -import com.android.systemui.scene.data.repository.SceneContainerRepository -import com.android.systemui.scene.data.repository.sceneContainerRepository -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.SceneContainerConfig -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel -import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel -import com.android.systemui.statusbar.phone.screenOffAnimationController -import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository -import com.android.systemui.telephony.data.repository.fakeTelephonyRepository -import com.android.systemui.telephony.domain.interactor.TelephonyInteractor -import com.android.systemui.telephony.domain.interactor.telephonyInteractor -import com.android.systemui.user.domain.interactor.SelectedUserInteractor -import com.android.systemui.user.domain.interactor.selectedUserInteractor -import com.android.systemui.util.time.systemClock -import kotlinx.coroutines.ExperimentalCoroutinesApi - -/** - * Utilities for creating scene container framework related repositories, interactors, and - * view-models for tests. - */ -@OptIn(ExperimentalCoroutinesApi::class) -@Deprecated("Please use Kosmos instead.") -class SceneTestUtils { - - val kosmos = Kosmos() - - constructor( - context: Context, - ) { - kosmos.applicationContext = context - } - - constructor(testCase: SysuiTestCase) : this(context = testCase.context) { - kosmos.testCase = testCase - } - - val testDispatcher by lazy { kosmos.testDispatcher } - val testScope by lazy { kosmos.testScope } - val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic } - val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags } - val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository } - val authenticationRepository by lazy { kosmos.fakeAuthenticationRepository } - val configurationRepository by lazy { kosmos.fakeConfigurationRepository } - val configurationInteractor by lazy { kosmos.configurationInteractor } - val telephonyRepository by lazy { kosmos.fakeTelephonyRepository } - val bouncerRepository by lazy { kosmos.bouncerRepository } - val communalRepository by lazy { kosmos.fakeCommunalRepository } - val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } - val powerRepository by lazy { kosmos.fakePowerRepository } - val simBouncerRepository by lazy { kosmos.fakeSimBouncerRepository } - val clock by lazy { kosmos.systemClock } - val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository } - val simBouncerInteractor by lazy { kosmos.simBouncerInteractor } - val statusBarStateController by lazy { kosmos.statusBarStateController } - val interactionJankMonitor by lazy { kosmos.interactionJankMonitor } - val screenOffAnimationController by lazy { kosmos.screenOffAnimationController } - - fun fakeSceneContainerRepository(): SceneContainerRepository { - return kosmos.sceneContainerRepository - } - - fun fakeSceneKeys(): List<SceneKey> { - return kosmos.sceneKeys - } - - fun fakeSceneContainerConfig(): SceneContainerConfig { - return kosmos.sceneContainerConfig - } - - fun sceneInteractor(): SceneInteractor { - return kosmos.sceneInteractor - } - - fun deviceEntryInteractor(): DeviceEntryInteractor { - return kosmos.deviceEntryInteractor - } - - fun authenticationInteractor(): AuthenticationInteractor { - return kosmos.authenticationInteractor - } - - fun keyguardInteractor(): KeyguardInteractor { - return kosmos.keyguardInteractor - } - - fun communalInteractor(): CommunalInteractor { - return kosmos.communalInteractor - } - - fun bouncerInteractor(): BouncerInteractor { - return kosmos.bouncerInteractor - } - - fun notificationsPlaceholderViewModel(): NotificationsPlaceholderViewModel { - return kosmos.notificationsPlaceholderViewModel - } - - fun bouncerViewModel(): BouncerViewModel { - return kosmos.bouncerViewModel - } - - fun telephonyInteractor(): TelephonyInteractor { - return kosmos.telephonyInteractor - } - - fun falsingInteractor(): FalsingInteractor { - return kosmos.falsingInteractor - } - - fun falsingCollector(): FalsingCollector { - return kosmos.falsingCollector - } - - fun powerInteractor(): PowerInteractor { - return kosmos.powerInteractor - } - - fun selectedUserInteractor(): SelectedUserInteractor { - return kosmos.selectedUserInteractor - } - - fun bouncerActionButtonInteractor(): BouncerActionButtonInteractor { - return kosmos.bouncerActionButtonInteractor - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt index da956ec67696..da956ec67696 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java index dda7fadde2d7..4efcada96a14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java @@ -52,32 +52,38 @@ public class GroupEntryBuilder { return ge; } + /** Sets the group key. */ public GroupEntryBuilder setKey(String key) { mKey = key; return this; } + /** Sets the creation time. */ public GroupEntryBuilder setCreationTime(long creationTime) { mCreationTime = creationTime; return this; } + /** Sets the parent entry of the group. */ public GroupEntryBuilder setParent(@Nullable GroupEntry entry) { mParent = entry; return this; } + /** Sets the section the group belongs to. */ public GroupEntryBuilder setSection(@Nullable NotifSection section) { mNotifSection = section; return this; } + /** Sets the group summary. */ public GroupEntryBuilder setSummary( NotificationEntry summary) { mSummary = summary; return this; } + /** Sets the group children. */ public GroupEntryBuilder setChildren(List<NotificationEntry> children) { mChildren.clear(); mChildren.addAll(children); @@ -90,6 +96,7 @@ public class GroupEntryBuilder { return this; } + /** Get the group's internal children list. */ public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) { return groupEntry.getRawChildren(); } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt index 0dbade76979c..d7e948eefc95 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt @@ -20,11 +20,13 @@ import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.scene.shared.flag.sceneContainerFlags +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor val Kosmos.notificationsPlaceholderViewModel by Fixture { NotificationsPlaceholderViewModel( interactor = notificationStackAppearanceInteractor, + shadeInteractor = shadeInteractor, flags = sceneContainerFlags, featureFlags = featureFlagsClassic, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt index d9beabb11ad9..d80ee758269f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt @@ -23,6 +23,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.policy.headsUpManager val Kosmos.windowRootViewVisibilityInteractor by Fixture { @@ -32,5 +33,6 @@ val Kosmos.windowRootViewVisibilityInteractor by Fixture { keyguardRepository = keyguardRepository, headsUpManager = headsUpManager, powerInteractor = powerInteractor, + activeNotificationsInteractor = activeNotificationsInteractor, ) } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index a856f424be5c..f3b74ea00a58 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1735,6 +1735,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState processResponseLockedForPcc(response, response.getClientState(), requestFlags); mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis(); + mFillResponseEventLogger.logAndEndEvent(); } @@ -1847,6 +1848,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } synchronized (mLock) { + // TODO(b/319913595): refactor logging for fill response for primary and secondary + // providers + // Start a new FillResponse logger for the success case. + mFillResponseEventLogger.startLogForNewResponse(); + mFillResponseEventLogger.maybeSetRequestId(fillResponse.getRequestId()); + mFillResponseEventLogger.maybeSetAppPackageUid(uid); + mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SUCCESS); + mFillResponseEventLogger.startResponseProcessingTime(); + // Time passed since session was created + final long fillRequestReceivedRelativeTimestamp = + SystemClock.elapsedRealtime() - mLatencyBaseTime; + mPresentationStatsEventLogger.maybeSetFillResponseReceivedTimestampMs( + (int) (fillRequestReceivedRelativeTimestamp)); + mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis( + (int) (fillRequestReceivedRelativeTimestamp)); + if (mDestroyed) { + Slog.w(TAG, "Call to Session#onSecondaryFillResponse() rejected - session: " + + id + " destroyed"); + mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); + mFillResponseEventLogger.logAndEndEvent(); + return; + } + + List<Dataset> datasetList = fillResponse.getDatasets(); + int datasetCount = (datasetList == null) ? 0 : datasetList.size(); + mFillResponseEventLogger.maybeSetTotalDatasetsProvided(datasetCount); + mFillResponseEventLogger.maybeSetAvailableCount(datasetCount); if (mSecondaryResponses == null) { mSecondaryResponses = new SparseArray<>(2); } @@ -1859,6 +1887,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (currentView != null) { currentView.maybeCallOnFillReady(flags); } + mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis(); + mFillResponseEventLogger.logAndEndEvent(); } } diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java index a0301a920d96..6e906ebe887a 100644 --- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java +++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java @@ -34,7 +34,7 @@ class SecureTransport extends Transport implements SecureChannel.Callback { private volatile boolean mShouldProcessRequests = false; - private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100); + private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(500); SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) { super(associationId, fd, context); diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 5a44ac803cb4..9d9e7c9345be 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -1387,6 +1387,8 @@ public final class BatteryService extends SystemService { case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE: case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE: case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY: + case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER: + case BatteryManager.BATTERY_PROPERTY_PART_STATUS: mContext.enforceCallingPermission( android.Manifest.permission.BATTERY_STATS, null); break; diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index eb6fdd72f2c3..f921b0b94b25 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -418,6 +418,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { LinkCapacityEstimate.INVALID, LinkCapacityEstimate.INVALID))); private List<List<LinkCapacityEstimate>> mLinkCapacityEstimateLists; + private int[] mSimultaneousCellularCallingSubIds = {}; + private int[] mECBMReason; private boolean[] mECBMStarted; private int[] mSCBMReason; @@ -564,7 +566,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { || events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED) || events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED) || events.contains(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED) - || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED); + || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED) + || events.contains(TelephonyCallback + .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED); } private static final int MSG_USER_SWITCHED = 1; @@ -1427,6 +1431,15 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } + if (events.contains(TelephonyCallback + .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) { + try { + r.callback.onSimultaneousCallingStateChanged( + mSimultaneousCellularCallingSubIds); + } catch (RemoteException ex) { + remove(r.binder); + } + } if (events.contains( TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED)) { try { @@ -3092,6 +3105,43 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + /** + * Notify the listeners that simultaneous cellular calling subscriptions have changed + * @param subIds The set of subIds that support simultaneous cellular calling + */ + public void notifySimultaneousCellularCallingSubscriptionsChanged(int[] subIds) { + if (!checkNotifyPermission("notifySimultaneousCellularCallingSubscriptionsChanged()")) { + return; + } + + if (VDBG) { + StringBuilder b = new StringBuilder(); + b.append("notifySimultaneousCellularCallingSubscriptionsChanged: "); + b.append("subIds = {"); + for (int i : subIds) { + b.append(" "); + b.append(i); + } + b.append("}"); + log(b.toString()); + } + + synchronized (mRecords) { + mSimultaneousCellularCallingSubIds = subIds; + for (Record r : mRecords) { + if (r.matchTelephonyCallbackEvent(TelephonyCallback + .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) { + try { + r.callback.onSimultaneousCallingStateChanged(subIds); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + } + handleRemoveListLocked(); + } + } + @Override public void addCarrierPrivilegesCallback( int phoneId, diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java index 5189017f5bf0..b084cf3c3b12 100644 --- a/services/core/java/com/android/server/app/GameManagerSettings.java +++ b/services/core/java/com/android/server/app/GameManagerSettings.java @@ -251,6 +251,7 @@ public class GameManagerSettings { + type); } } + str.close(); } catch (XmlPullParserException | java.io.IOException e) { Slog.wtf(TAG, "Error reading game manager settings", e); return false; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index df8d9e1a406c..8fa0089c6db4 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -65,6 +65,9 @@ import static android.app.AppOpsManager.opRestrictsRead; import static android.app.AppOpsManager.opToName; import static android.app.AppOpsManager.opToPublicName; import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; @@ -973,7 +976,29 @@ public class AppOpsService extends IAppOpsService.Stub { String pkgName = intent.getData().getEncodedSchemeSpecificPart(); int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID); - if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) { + if (action.equals(ACTION_PACKAGE_ADDED) + && !intent.getBooleanExtra(EXTRA_REPLACING, false)) { + PackageInfo pi = getPackageManagerInternal().getPackageInfo(pkgName, + PackageManager.GET_PERMISSIONS, Process.myUid(), + UserHandle.getUserId(uid)); + boolean isSamplingTarget = isSamplingTarget(pi); + synchronized (AppOpsService.this) { + if (isSamplingTarget) { + mRarelyUsedPackages.add(pkgName); + } + UidState uidState = getUidStateLocked(uid, true); + if (!uidState.pkgOps.containsKey(pkgName)) { + uidState.pkgOps.put(pkgName, + new Ops(pkgName, uidState)); + } + + createSandboxUidStateIfNotExistsForAppLocked(uid); + } + } else if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) { + synchronized (AppOpsService.this) { + packageRemovedLocked(uid, pkgName); + } + } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) { AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName); if (pkg == null) { return; @@ -1052,7 +1077,9 @@ public class AppOpsService extends IAppOpsService.Stub { mHistoricalRegistry.systemReady(mContext.getContentResolver()); IntentFilter packageUpdateFilter = new IntentFilter(); + packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED); packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED); packageUpdateFilter.addDataScheme("package"); mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL, @@ -1079,7 +1106,7 @@ public class AppOpsService extends IAppOpsService.Stub { String action; if (!ArrayUtils.contains(pkgsInUid, pkg)) { - action = Intent.ACTION_PACKAGE_REMOVED; + action = ACTION_PACKAGE_REMOVED; } else { action = Intent.ACTION_PACKAGE_REPLACED; } @@ -1160,44 +1187,6 @@ public class AppOpsService extends IAppOpsService.Stub { // onUserRemoved handled by #removeUser }); - - getPackageManagerInternal().getPackageList( - new PackageManagerInternal.PackageListObserver() { - @Override - public void onPackageAdded(String packageName, int appId) { - PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName, - PackageManager.GET_PERMISSIONS, Process.myUid(), - mContext.getUserId()); - boolean isSamplingTarget = isSamplingTarget(pi); - int[] userIds = getUserManagerInternal().getUserIds(); - synchronized (AppOpsService.this) { - if (isSamplingTarget) { - mRarelyUsedPackages.add(packageName); - } - for (int i = 0; i < userIds.length; i++) { - int uid = UserHandle.getUid(userIds[i], appId); - UidState uidState = getUidStateLocked(uid, true); - if (!uidState.pkgOps.containsKey(packageName)) { - uidState.pkgOps.put(packageName, - new Ops(packageName, uidState)); - } - - createSandboxUidStateIfNotExistsForAppLocked(uid); - } - } - } - - @Override - public void onPackageRemoved(String packageName, int appId) { - int[] userIds = getUserManagerInternal().getUserIds(); - synchronized (AppOpsService.this) { - for (int i = 0; i < userIds.length; i++) { - int uid = UserHandle.getUid(userIds[i], appId); - packageRemovedLocked(uid, packageName); - } - } - } - }); } /** diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index d5d8fd22314b..8fd2ee2bdc33 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -20,6 +20,7 @@ package com.android.server.biometrics; // TODO(b/141025588): Create separate internal and external permissions for AuthService. // TODO(b/141025588): Get rid of the USE_FINGERPRINT permission. +import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG; import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; @@ -304,6 +305,9 @@ public class AuthService extends SystemService { if (promptInfo.containsPrivateApiConfigurations()) { checkInternalPermission(); } + if (promptInfo.containsManageBioApiConfigurations()) { + checkManageBiometricPermission(); + } final long identity = Binder.clearCallingIdentity(); try { @@ -984,6 +988,11 @@ public class AuthService extends SystemService { "Must have USE_BIOMETRIC_INTERNAL permission"); } + private void checkManageBiometricPermission() { + getContext().enforceCallingOrSelfPermission(MANAGE_BIOMETRIC_DIALOG, + "Must have MANAGE_BIOMETRIC_DIALOG permission"); + } + private void checkPermission() { if (getContext().checkCallingOrSelfPermission(USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { diff --git a/services/core/java/com/android/server/broadcastradio/TEST_MAPPING b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING new file mode 100644 index 000000000000..ee4eeb634c84 --- /dev/null +++ b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "frameworks/base/core/tests/BroadcastRadioTests" + } + ] +} diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java index 1153cc37e3da..8a3a56cdc9ca 100644 --- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java +++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java @@ -16,6 +16,8 @@ package com.android.server.health; +import static android.os.Flags.batteryPartStatusApi; + import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.health.BatteryHealthData; @@ -150,6 +152,18 @@ class HealthServiceWrapperAidl extends HealthServiceWrapper { healthData = service.getBatteryHealthData(); prop.setLong(healthData.batteryStateOfHealth); break; + case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER: + if (batteryPartStatusApi()) { + healthData = service.getBatteryHealthData(); + prop.setString(healthData.batterySerialNumber); + } + break; + case BatteryManager.BATTERY_PROPERTY_PART_STATUS: + if (batteryPartStatusApi()) { + healthData = service.getBatteryHealthData(); + prop.setLong(healthData.batteryPartStatus); + } + break; } } catch (UnsupportedOperationException e) { // Leave prop untouched. diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index 6236e2b933bc..46668de042d4 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -1370,7 +1370,7 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { public boolean isVirtualDevice(int deviceId) { VirtualDeviceManagerInternal vdm = LocalServices.getService( VirtualDeviceManagerInternal.class); - return vdm == null || vdm.isInputDeviceOwnedByVirtualDevice(deviceId); + return vdm != null && vdm.isInputDeviceOwnedByVirtualDevice(deviceId); } private static int[] getScriptCodes(@Nullable Locale locale) { diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 4d19eade5a05..d7188c7f10c6 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -42,7 +42,6 @@ import android.util.ArraySet; import android.util.Log; import android.util.Slog; -import com.android.internal.annotations.Keep; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.RingBuffer; import com.android.server.am.ProcessList; @@ -414,7 +413,7 @@ public class NetworkPolicyLogger { private static final Date sDate = new Date(); public LogBuffer(int capacity) { - super(Data.class, capacity); + super(Data::new, Data[]::new, capacity); } public void uidStateChanged(int uid, int procState, long procStateSeq, @@ -690,12 +689,8 @@ public class NetworkPolicyLogger { /** * Container class for all networkpolicy events data. - * - * Note: This class needs to be public for RingBuffer class to be able to create - * new instances of this. */ - @Keep - public static final class Data { + private static final class Data { public int type; public long timeStamp; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c9f45e523e3b..2ae040a69583 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5931,8 +5931,7 @@ public class NotificationManagerService extends SystemService { newVisualEffects, policy.priorityConversationSenders); if (shouldApplyAsImplicitRule) { - mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy, - origin); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy); } else { ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion, policy); diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index c7f570353a5b..93ffd974bb80 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -32,6 +32,7 @@ import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; +import static com.android.internal.util.Preconditions.checkArgument; import android.annotation.DrawableRes; import android.annotation.NonNull; @@ -427,6 +428,7 @@ public class ZenModeHelper { public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule, @ConfigChangeOrigin int origin, String reason, int callingUid) { + requirePublicOrigin("addAutomaticZenRule", origin); if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) { PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner()); if (component == null) { @@ -525,6 +527,7 @@ public class ZenModeHelper { public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule, @ConfigChangeOrigin int origin, String reason, int callingUid) { + requirePublicOrigin("updateAutomaticZenRule", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -602,7 +605,11 @@ public class ZenModeHelper { rule = newImplicitZenRule(callingPkg); newConfig.automaticRules.put(rule.id, rule); } - rule.zenMode = zenMode; + // If the user has changed the rule's *zenMode*, then don't let app overwrite it. + // We allow the update if the user has only changed other aspects of the rule. + if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) { + rule.zenMode = zenMode; + } rule.snoozing = false; rule.condition = new Condition(rule.conditionId, mContext.getString(R.string.zen_mode_implicit_activated), @@ -625,7 +632,7 @@ public class ZenModeHelper { * {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}. */ void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid, - NotificationManager.Policy policy, @ConfigChangeOrigin int origin) { + NotificationManager.Policy policy) { if (!android.app.Flags.modesApi()) { Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!"); return; @@ -641,10 +648,17 @@ public class ZenModeHelper { rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; newConfig.automaticRules.put(rule.id, rule); } - // TODO: b/308673679 - Keep user customization of this rule! - rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy); - setConfigLocked(newConfig, /* triggeringComponent= */ null, origin, - "applyGlobalPolicyAsImplicitZenRule", callingUid); + // If the user has changed the rule's *ZenPolicy*, then don't let app overwrite it. + // We allow the update if the user has only changed other aspects of the rule. + if (rule.zenPolicyUserModifiedFields == 0) { + updatePolicy( + rule, + ZenAdapters.notificationPolicyToZenPolicy(policy), + /* updateBitmask= */ false); + + setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP, + "applyGlobalPolicyAsImplicitZenRule", callingUid); + } } } @@ -726,6 +740,7 @@ public class ZenModeHelper { boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason, int callingUid) { + requirePublicOrigin("removeAutomaticZenRule", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -758,6 +773,7 @@ public class ZenModeHelper { boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin, String reason, int callingUid) { + requirePublicOrigin("removeAutomaticZenRules", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -806,6 +822,7 @@ public class ZenModeHelper { void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin, int callingUid) { + requirePublicOrigin("setAutomaticZenRuleState", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return; @@ -819,6 +836,7 @@ public class ZenModeHelper { void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, @ConfigChangeOrigin int origin, int callingUid) { + requirePublicOrigin("setAutomaticZenRuleState", origin); ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return; @@ -988,7 +1006,7 @@ public class ZenModeHelper { return null; } - void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, + private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, @ConfigChangeOrigin int origin, boolean isNew) { if (Flags.modesApi()) { // These values can always be edited by the app, so we apply changes immediately. @@ -1053,11 +1071,9 @@ public class ZenModeHelper { rule.zenMode = newZenMode; // Updates the bitmask and values for all policy fields, based on the origin. - rule.zenPolicy = updatePolicy(rule.zenPolicy, automaticZenRule.getZenPolicy(), - updateBitmask); + updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask); // Updates the bitmask and values for all device effect fields, based on the origin. - rule.zenDeviceEffects = updateZenDeviceEffects( - rule.zenDeviceEffects, automaticZenRule.getDeviceEffects(), + updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(), origin == UPDATE_ORIGIN_APP, updateBitmask); } else { if (rule.enabled != automaticZenRule.isEnabled()) { @@ -1069,13 +1085,6 @@ public class ZenModeHelper { rule.enabled = automaticZenRule.isEnabled(); rule.modified = automaticZenRule.isModified(); rule.zenPolicy = automaticZenRule.getZenPolicy(); - if (Flags.modesApi()) { - rule.zenDeviceEffects = updateZenDeviceEffects( - rule.zenDeviceEffects, - automaticZenRule.getDeviceEffects(), - origin == UPDATE_ORIGIN_APP, - origin == UPDATE_ORIGIN_USER); - } rule.zenMode = NotificationManager.zenModeFromInterruptionFilter( automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF); rule.configurationActivity = automaticZenRule.getConfigurationActivity(); @@ -1099,28 +1108,28 @@ public class ZenModeHelper { } /** - * Modifies {@link ZenPolicy} that is being stored as part of a new or updated ZenRule. - * Returns a policy based on {@code oldPolicy}, but with fields updated to match - * {@code newPolicy} where they differ, and updating the internal user-modified bitmask to - * track these changes, if applicable based on {@code origin}. + * Modifies the {@link ZenPolicy} associated to a new or updated ZenRule. + * + * <p>The new policy is {@code newPolicy}, while the user-modified bitmask is updated to reflect + * the changes being applied (if applicable, i.e. if the update is from the user). */ - @Nullable - private ZenPolicy updatePolicy(@Nullable ZenPolicy oldPolicy, @Nullable ZenPolicy newPolicy, - boolean updateBitmask) { - // If the update is to make the policy null, we don't need to update the bitmask, - // because it won't be stored anywhere anyway. + private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy, + boolean updateBitmask) { if (newPolicy == null) { - return null; + // TODO: b/319242206 - Treat as newPolicy == default policy and continue below. + zenRule.zenPolicy = null; + return; } // If oldPolicy is null, we compare against the default policy when determining which // fields in the bitmask should be marked as updated. - if (oldPolicy == null) { - oldPolicy = mDefaultConfig.toZenPolicy(); - } + ZenPolicy oldPolicy = + zenRule.zenPolicy != null ? zenRule.zenPolicy : mDefaultConfig.toZenPolicy(); + + zenRule.zenPolicy = newPolicy; - int userModifiedFields = oldPolicy.getUserModifiedFields(); if (updateBitmask) { + int userModifiedFields = zenRule.zenPolicyUserModifiedFields; if (oldPolicy.getPriorityMessageSenders() != newPolicy.getPriorityMessageSenders()) { userModifiedFields |= ZenPolicy.FIELD_MESSAGES; } @@ -1178,66 +1187,47 @@ public class ZenModeHelper { != newPolicy.getVisualEffectNotificationList()) { userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_NOTIFICATION_LIST; } + zenRule.zenPolicyUserModifiedFields = userModifiedFields; } - - // After all bitmask changes have been made, sets the bitmask. - return new ZenPolicy.Builder(newPolicy).setUserModifiedFields(userModifiedFields).build(); } /** - * Modifies {@link ZenDeviceEffects} that are being stored as part of a new or updated ZenRule. - * Returns a {@link ZenDeviceEffects} based on {@code oldEffects}, but with fields updated to - * match {@code newEffects} where they differ, and updating the internal user-modified bitmask - * to track these changes, if applicable based on {@code origin}. - * <ul> - * <li> Apps cannot turn on hidden effects (those tagged as {@code @hide}) since they are - * intended for platform-specific rules (e.g. wearables). If it's a new rule, we blank them - * out; if it's an update, we preserve the previous values. - * </ul> + * Modifies the {@link ZenDeviceEffects} associated to a new or updated ZenRule. + * + * <p>The new value is {@code newEffects}, while the user-modified bitmask is updated to reflect + * the changes being applied (if applicable, i.e. if the update is from the user). + * + * <p>Apps cannot turn on hidden effects (those tagged as {@code @hide}), so those fields are + * treated especially: for a new rule, they are blanked out; for an updated rule, previous + * values are preserved. */ - @Nullable - private static ZenDeviceEffects updateZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects, - @Nullable ZenDeviceEffects newEffects, - boolean isFromApp, - boolean updateBitmask) { + private static void updateZenDeviceEffects(ZenRule zenRule, + @Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) { if (newEffects == null) { - return null; + zenRule.zenDeviceEffects = null; + return; } - // Since newEffects is not null, we want to adopt all the new provided device effects. - ZenDeviceEffects.Builder builder = new ZenDeviceEffects.Builder(newEffects); + ZenDeviceEffects oldEffects = zenRule.zenDeviceEffects != null + ? zenRule.zenDeviceEffects + : new ZenDeviceEffects.Builder().build(); if (isFromApp) { - if (oldEffects != null) { - // We can do this because we know we don't need to update the bitmask FROM_APP. - return builder - .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness()) - .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake()) - .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake()) - .setShouldDisableTouch(oldEffects.shouldDisableTouch()) - .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage()) - .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze()) - .build(); - } else { - return builder - .setShouldDisableAutoBrightness(false) - .setShouldDisableTapToWake(false) - .setShouldDisableTiltToWake(false) - .setShouldDisableTouch(false) - .setShouldMinimizeRadioUsage(false) - .setShouldMaximizeDoze(false) - .build(); - } + // Don't allow apps to toggle hidden effects. + newEffects = new ZenDeviceEffects.Builder(newEffects) + .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness()) + .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake()) + .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake()) + .setShouldDisableTouch(oldEffects.shouldDisableTouch()) + .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage()) + .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze()) + .build(); } - // If oldEffects is null, we compare against the default device effects object when - // determining which fields in the bitmask should be marked as updated. - if (oldEffects == null) { - oldEffects = new ZenDeviceEffects.Builder().build(); - } + zenRule.zenDeviceEffects = newEffects; - int userModifiedFields = oldEffects.getUserModifiedFields(); if (updateBitmask) { + int userModifiedFields = zenRule.zenDeviceEffectsUserModifiedFields; if (oldEffects.shouldDisplayGrayscale() != newEffects.shouldDisplayGrayscale()) { userModifiedFields |= ZenDeviceEffects.FIELD_GRAYSCALE; } @@ -1270,11 +1260,8 @@ public class ZenModeHelper { if (oldEffects.shouldMaximizeDoze() != newEffects.shouldMaximizeDoze()) { userModifiedFields |= ZenDeviceEffects.FIELD_MAXIMIZE_DOZE; } + zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields; } - - // Since newEffects is not null, we want to adopt all the new provided device effects. - // Set the usermodifiedFields value separately, to reflect the updated bitmask. - return builder.setUserModifiedFields(userModifiedFields).build(); } private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) { @@ -1293,7 +1280,6 @@ public class ZenModeHelper { .setOwner(rule.component) .setConfigurationActivity(rule.configurationActivity) .setTriggerDescription(rule.triggerDescription) - .setUserModifiedFields(rule.userModifiedFields) .build(); } else { azr = new AutomaticZenRule(rule.name, rule.component, @@ -2369,6 +2355,19 @@ public class ZenModeHelper { return null; } } + + /** Checks that the {@code origin} supplied to a ZenModeHelper "API" method makes sense. */ + private static void requirePublicOrigin(String method, @ConfigChangeOrigin int origin) { + if (!Flags.modesApi()) { + return; + } + checkArgument(origin == UPDATE_ORIGIN_APP || origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + || origin == UPDATE_ORIGIN_USER, + "Expected one of UPDATE_ORIGIN_APP, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, or " + + "UPDATE_ORIGIN_USER for %s, but received '%s'.", + method, origin); + } + private final class Metrics extends Callback { private static final String COUNTER_MODE_PREFIX = "dnd_mode_"; private static final String COUNTER_TYPE_PREFIX = "dnd_type_"; diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index 3e5759a88213..b18f2bf21a2b 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -51,6 +51,7 @@ import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.ArchivedActivityParcel; +import android.content.pm.ArchivedPackageInfo; import android.content.pm.ArchivedPackageParcel; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; @@ -402,23 +403,30 @@ public class PackageArchiver { installerPackage, /* flags= */ 0, userId); if (installerInfo == null) { // Should never happen because we just fetched the installerInfo. - Slog.e(TAG, "Couldnt find installer " + installerPackage); + Slog.e(TAG, "Couldn't find installer " + installerPackage); return null; } + final int iconSize = mContext.getSystemService( + ActivityManager.class).getLauncherLargeIconSize(); + + var info = new ArchivedPackageInfo(archivedPackage); try { - var packageName = archivedPackage.packageName; - var mainActivities = archivedPackage.archivedActivities; - List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.length); - for (int i = 0, size = mainActivities.length; i < size; ++i) { - var mainActivity = mainActivities[i]; - Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i); + var packageName = info.getPackageName(); + var mainActivities = info.getLauncherActivities(); + List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size()); + for (int i = 0, size = mainActivities.size(); i < size; ++i) { + var mainActivity = mainActivities.get(i); + Path iconPath = storeDrawable( + packageName, mainActivity.getIcon(), userId, i, iconSize); + Path monochromePath = storeDrawable( + packageName, mainActivity.getMonochromeIcon(), userId, i, iconSize); ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( - mainActivity.title, - mainActivity.originalComponentName, + mainActivity.getLabel().toString(), + mainActivity.getComponentName(), iconPath, - null); + monochromePath); archiveActivityInfos.add(activityInfo); } @@ -452,21 +460,6 @@ public class PackageArchiver { return new ArchiveState(archiveActivityInfos, installerTitle); } - // TODO(b/298452477) Handle monochrome icons. - private static Path storeIconForParcel(String packageName, ArchivedActivityParcel mainActivity, - @UserIdInt int userId, int index) throws IOException { - if (mainActivity.iconBitmap == null) { - return null; - } - File iconsDir = createIconsDir(packageName, userId); - File iconFile = new File(iconsDir, index + ".png"); - try (FileOutputStream out = new FileOutputStream(iconFile)) { - out.write(mainActivity.iconBitmap); - out.flush(); - } - return iconFile.toPath(); - } - @VisibleForTesting Path storeIcon(String packageName, LauncherActivityInfo mainActivity, @UserIdInt int userId, int index, int iconSize) throws IOException { @@ -475,9 +468,18 @@ public class PackageArchiver { // The app doesn't define an icon. No need to store anything. return null; } + return storeDrawable(packageName, mainActivity.getIcon(/* density= */ 0), userId, index, + iconSize); + } + + private static Path storeDrawable(String packageName, @Nullable Drawable iconDrawable, + @UserIdInt int userId, int index, int iconSize) throws IOException { + if (iconDrawable == null) { + return null; + } File iconsDir = createIconsDir(packageName, userId); File iconFile = new File(iconsDir, index + ".png"); - Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0), iconSize); + Bitmap icon = drawableToBitmap(iconDrawable, iconSize); try (FileOutputStream out = new FileOutputStream(iconFile)) { // Note: Quality is ignored for PNGs. if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) { diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 3e3b72caaf2e..3659cb779723 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -485,8 +485,6 @@ final class RemovePackageHelper { synchronized (mPm.mInstallLock) { cleanUpResourcesLI(codeFile, instructionSets); } - // TODO: open logging to help debug, will delete or add debug flag - Slog.d(TAG, "cleanUpResources for " + codeFile); if (packageName == null) { return; } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index e993d9e5b724..d644235b8714 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -33,6 +33,7 @@ import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaRequest; +import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -47,6 +48,7 @@ import android.graphics.drawable.Icon; import android.os.Binder; import android.os.PersistableBundle; import android.os.StrictMode; +import android.os.SystemClock; import android.text.format.Formatter; import android.util.ArrayMap; import android.util.ArraySet; @@ -192,6 +194,9 @@ class ShortcutPackage extends ShortcutPackageItem { private long mLastKnownForegroundElapsedTime; @GuardedBy("mLock") + private long mLastReportedTime; + + @GuardedBy("mLock") private boolean mIsAppSearchSchemaUpToDate; private ShortcutPackage(ShortcutUser shortcutUser, @@ -1673,6 +1678,26 @@ class ShortcutPackage extends ShortcutPackageItem { return condition[0]; } + void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal, + @NonNull final String shortcutId) { + synchronized (mLock) { + final long currentTS = SystemClock.elapsedRealtime(); + final ShortcutService s = mShortcutUser.mService; + if (currentTS - mLastReportedTime > s.mSaveDelayMillis) { + mLastReportedTime = currentTS; + } else { + return; + } + final long token = s.injectClearCallingIdentity(); + try { + usageStatsManagerInternal.reportShortcutUsage(getPackageName(), shortcutId, + getPackageUserId()); + } finally { + s.injectRestoreCallingIdentity(token); + } + } + } + public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) { pw.println(); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 96c205c00ea9..c23d2abf0853 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -371,7 +371,7 @@ public class ShortcutService extends IShortcutService.Stub { private CompressFormat mIconPersistFormat; private int mIconPersistQuality; - private int mSaveDelayMillis; + int mSaveDelayMillis; private final IPackageManager mIPackageManager; private final PackageManagerInternal mPackageManagerInternal; @@ -2291,7 +2291,7 @@ public class ShortcutService extends IShortcutService.Stub { packageShortcutsChanged(ps, changedShortcuts, removedShortcuts); - reportShortcutUsedInternal(packageName, shortcut.getId(), userId); + ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId()); verifyStates(); } @@ -2695,25 +2695,17 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d", shortcutId, packageName, userId)); } + final ShortcutPackage ps; synchronized (mLock) { throwIfUserLockedL(userId); - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); + ps = getPackageShortcutsForPublisherLocked(packageName, userId); if (ps.findShortcutById(shortcutId) == null) { Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s", packageName, shortcutId)); return; } } - reportShortcutUsedInternal(packageName, shortcutId, userId); - } - - private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) { - final long token = injectClearCallingIdentity(); - try { - mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId); - } finally { - injectRestoreCallingIdentity(token); - } + ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId); } @Override @@ -5202,13 +5194,11 @@ public class ShortcutService extends IShortcutService.Stub { } // Injection point. - @VisibleForTesting long injectClearCallingIdentity() { return Binder.clearCallingIdentity(); } // Injection point. - @VisibleForTesting void injectRestoreCallingIdentity(long token) { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index c1b74898e5ae..7b0a69bc6786 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -46,6 +46,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityManagerNative; +import android.app.ActivityOptions; import android.app.BroadcastOptions; import android.app.IActivityManager; import android.app.IStopUserCallback; @@ -587,7 +588,10 @@ public class UserManagerService extends IUserManager.Stub { public void onFinished(int id, Bundle extras) { mHandler.post(() -> { try { - mContext.startIntentSender(mTarget, null, 0, 0, 0); + ActivityOptions activityOptions = + ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + mContext.startIntentSender(mTarget, null, 0, 0, 0, activityOptions.toBundle()); } catch (IntentSender.SendIntentException e) { Slog.e(LOG_TAG, "Failed to start the target in the callback", e); } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index e2269535d931..a172de0bb0ff 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -4413,8 +4413,8 @@ public final class PowerManagerService extends SystemService private boolean setPowerModeInternal(int mode, boolean enabled) { // Maybe filter the event. - if (mBatterySaverStateMachine == null || (mode == Mode.LAUNCH && enabled - && mBatterySaverStateMachine.getBatterySaverController().isLaunchBoostDisabled())) { + if (mode == Mode.LAUNCH && enabled && mBatterySaverStateMachine != null + && mBatterySaverStateMachine.getBatterySaverController().isLaunchBoostDisabled()) { return false; } return mNativeWrapper.nativeSetPowerMode(mode, enabled); diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index 60dc4ff224bc..d95b431752ec 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -348,6 +348,10 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { private void pinWebviewIfRequired(ApplicationInfo appInfo) { PinnerService pinnerService = LocalServices.getService(PinnerService.class); + if (pinnerService == null) { + // This happens in unit tests which do not have services. + return; + } int webviewPinQuota = pinnerService.getWebviewPinQuota(); if (webviewPinQuota <= 0) { return; diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 676203bc746a..2e0546eee8e7 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -778,17 +778,22 @@ class ActivityClientController extends IActivityClientController.Stub { try { synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); + if (r == null) { + return false; + } // Create a transition if the activity is playing in case the below activity didn't // commit invisible. That's because if any activity below this one has changed its // visibility while playing transition, there won't able to commit visibility until // the running transition finish. - final Transition transition = r != null - && r.mTransitionController.inPlayingTransition(r) + final Transition transition = r.mTransitionController.isShellTransitionsEnabled() && !r.mTransitionController.isCollecting() ? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null; - final boolean changed = r != null && r.setOccludesParent(true); + final boolean changed = r.setOccludesParent(true); if (transition != null) { if (changed) { + // Always set as scene transition because it expects to be a jump-cut. + transition.setOverrideAnimation(TransitionInfo.AnimationOptions + .makeSceneTransitionAnimOptions(), null, null); r.mTransitionController.requestStartTransition(transition, null /*startTask */, null /* remoteTransition */, null /* displayChange */); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 69fbe6ba3c29..9b1f9c8441ad 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3093,7 +3093,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean changed = occludesParent != mOccludesParent; mOccludesParent = occludesParent; setMainWindowOpaque(occludesParent); - mWmService.mWindowPlacerLocked.requestTraversal(); if (changed && task != null && !occludesParent) { getRootTask().convertActivityToTranslucent(this); diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index e7621ffe8e3c..182e1c133790 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY; import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; @@ -144,22 +145,20 @@ class ActivityStartInterceptor { } private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) { - Bundle bOptions = deferCrossProfileAppsAnimationIfNecessary(); + ActivityOptions activityOptions = deferCrossProfileAppsAnimationIfNecessary(); + activityOptions.setPendingIntentCreatorBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOWED); final TaskFragment taskFragment = getLaunchTaskFragment(); // If the original intent is going to be embedded, try to forward the embedding TaskFragment // and its task id to embed back the original intent. if (taskFragment != null) { - ActivityOptions activityOptions = bOptions != null - ? ActivityOptions.fromBundle(bOptions) - : ActivityOptions.makeBasic(); activityOptions.setLaunchTaskFragmentToken(taskFragment.getFragmentToken()); - bOptions = activityOptions.toBundle(); } final IIntentSender target = mService.getIntentSenderLocked( INTENT_SENDER_ACTIVITY, mCallingPackage, mCallingFeatureId, callingUid, mUserId, null /*token*/, null /*resultCode*/, 0 /*requestCode*/, new Intent[] { mIntent }, new String[] { mResolvedType }, - flags, bOptions); + flags, activityOptions.toBundle()); return new IntentSender(target); } @@ -272,12 +271,12 @@ class ActivityStartInterceptor { * * @return the activity option used to start the original intent. */ - private Bundle deferCrossProfileAppsAnimationIfNecessary() { + private ActivityOptions deferCrossProfileAppsAnimationIfNecessary() { if (hasCrossProfileAnimation()) { mActivityOptions = null; - return ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(); + return ActivityOptions.makeOpenCrossProfileAppsAnimation(); } - return null; + return ActivityOptions.makeBasic(); } private boolean interceptQuietProfileIfNeeded() { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f620a9743eb4..2accf9a2a43a 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2841,6 +2841,19 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } + /** Returns {@code true} if the display should use high performance hint for this transition. */ + boolean shouldUsePerfHint(@NonNull DisplayContent dc) { + if (mOverrideOptions != null + && mOverrideOptions.getType() == ActivityOptions.ANIM_SCENE_TRANSITION + && mType == TRANSIT_TO_BACK && mParticipants.size() == 1) { + // This should be from convertFromTranslucent that makes the occluded activity invisible + // without animation. So do not use perf hint (especially early-wakeup) that may disturb + // SurfaceFlinger scheduling around the last frame. + return false; + } + return mTargetDisplays.contains(dc); + } + /** * Returns {@code true} if the transition and the corresponding transaction should be applied * on display thread. Currently, this only checks for display rotation change because the order diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 708d63e27ec2..59e3350d5c13 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -1237,8 +1237,15 @@ class TransitionController { // enableHighPerfTransition(true) is also called in Transition#recordDisplay. for (int i = mAtm.mRootWindowContainer.getChildCount() - 1; i >= 0; i--) { final DisplayContent dc = mAtm.mRootWindowContainer.getChildAt(i); - if (isTransitionOnDisplay(dc)) { + if (mCollectingTransition != null && mCollectingTransition.shouldUsePerfHint(dc)) { dc.enableHighPerfTransition(true); + continue; + } + for (int j = mPlayingTransitions.size() - 1; j >= 0; j--) { + if (mPlayingTransitions.get(j).shouldUsePerfHint(dc)) { + dc.enableHighPerfTransition(true); + break; + } } } // Usually transitions put quite a load onto the system already (with all the things diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt index 94caf2865b66..c8a65459d3df 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt @@ -17,6 +17,7 @@ package com.android.server.permission.access.appop import android.app.AppOpsManager +import android.util.Slog import com.android.server.permission.access.GetStateScope import com.android.server.permission.access.MutableAccessState import com.android.server.permission.access.MutateStateScope @@ -84,6 +85,10 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { appOpName: String, mode: Int ): Boolean { + if (userId !in newState.userStates) { + Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId") + return false + } val defaultMode = AppOpsManager.opToDefaultMode(appOpName) val oldMode = newState.userStates[userId]!! @@ -152,4 +157,8 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { */ abstract fun onStateMutated() } + + companion object { + private val LOG_TAG = AppIdAppOpPolicy::class.java.simpleName + } } diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt index 8f464d41792d..3ee7430fc486 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt @@ -17,29 +17,62 @@ package com.android.server.permission.access.appop import android.app.AppOpsManager +import android.os.Binder import android.os.Handler import android.os.UserHandle +import android.permission.flags.Flags import android.util.ArrayMap import android.util.ArraySet +import android.util.LongSparseArray +import android.util.Slog +import android.util.SparseArray import android.util.SparseBooleanArray import android.util.SparseIntArray import com.android.internal.annotations.VisibleForTesting +import com.android.internal.util.IntPair import com.android.server.appop.AppOpsCheckingServiceInterface import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener import com.android.server.permission.access.AccessCheckingService import com.android.server.permission.access.AppOpUri +import com.android.server.permission.access.GetStateScope import com.android.server.permission.access.PackageUri +import com.android.server.permission.access.PermissionUri import com.android.server.permission.access.UidUri +import com.android.server.permission.access.appop.AppOpModes.MODE_ALLOWED +import com.android.server.permission.access.appop.AppOpModes.MODE_FOREGROUND +import com.android.server.permission.access.appop.AppOpModes.MODE_IGNORED import com.android.server.permission.access.collection.forEachIndexed import com.android.server.permission.access.collection.set +import com.android.server.permission.access.permission.AppIdPermissionPolicy +import com.android.server.permission.access.permission.PermissionFlags +import com.android.server.permission.access.permission.PermissionService class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface { private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy private val appIdPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy + private val permissionPolicy = + service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy private val context = service.context + + // Maps appop code to its runtime permission + private val runtimeAppOpToPermissionNames = SparseArray<String>() + + // Maps runtime permission to its appop codes + private val runtimePermissionNameToAppOp = ArrayMap<String, Int>() + + private var foregroundableOps = SparseBooleanArray() + + /* Maps foreground permissions to their background permission. Background permissions aren't + required to be runtime */ + private val foregroundToBackgroundPermissionName = ArrayMap<String, String>() + + /* Maps background permissions to their foreground permissions. Background permissions aren't + required to be runtime */ + private val backgroundToForegroundPermissionNames = ArrayMap<String, ArraySet<String>>() + private lateinit var handler: Handler @Volatile private var listeners = ArraySet<AppOpsModeChangedListener>() @@ -68,11 +101,58 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS } override fun systemReady() { - // Not implemented because upgrades are handled automatically. + if (useRuntimePermissionAppOpMapping()) { + createPermissionAppOpMapping() + permissionPolicy.addOnPermissionFlagsChangedListener(OnPermissionFlagsChangedListener()) + } + } + + private fun createPermissionAppOpMapping() { + val permissions = service.getState { with(permissionPolicy) { getPermissions() } } + + for (appOpCode in 0 until AppOpsManager._NUM_OP) { + AppOpsManager.opToPermission(appOpCode)?.let { permissionName -> + // Multiple ops might map to a single permission but only one is considered the + // runtime appop calculations. + if (appOpCode == AppOpsManager.permissionToOpCode(permissionName)) { + val permission = permissions[permissionName]!! + if (permission.isRuntime) { + runtimePermissionNameToAppOp[permissionName] = appOpCode + runtimeAppOpToPermissionNames[appOpCode] = permissionName + permission.permissionInfo.backgroundPermission?.let { + backgroundPermissionName -> + // Note: background permission may not be runtime, + // e.g. microphone/camera. + foregroundableOps[appOpCode] = true + foregroundToBackgroundPermissionName[permissionName] = + backgroundPermissionName + backgroundToForegroundPermissionNames + .getOrPut(backgroundPermissionName, ::ArraySet) + .add(permissionName) + } + } + } + } + } } override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray { - return opNameMapToOpSparseArray(getUidModes(uid)) + val appId = UserHandle.getAppId(uid) + val userId = UserHandle.getUserId(uid) + service.getState { + val modes = + with(appIdPolicy) { opNameMapToOpSparseArray(getAppOpModes(appId, userId)?.map) } + if (useRuntimePermissionAppOpMapping()) { + runtimePermissionNameToAppOp.forEachIndexed { _, permissionName, appOpCode -> + val mode = getUidModeFromPermissionState(appId, userId, permissionName) + if (mode != AppOpsManager.opToDefaultMode(appOpCode)) { + modes[appOpCode] = mode + } + } + } + + return modes + } } override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray { @@ -83,7 +163,13 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) - return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } } + val permissionName = runtimeAppOpToPermissionNames[op] + + return if (!useRuntimePermissionAppOpMapping() || permissionName == null) { + service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } } + } else { + service.getState { getUidModeFromPermissionState(appId, userId, permissionName) } + } } private fun getUidModes(uid: Int): ArrayMap<String, Int>? { @@ -92,13 +178,63 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map } - override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean { + private fun GetStateScope.getUidModeFromPermissionState( + appId: Int, + userId: Int, + permissionName: String + ): Int { + val permissionFlags = + with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) } + val backgroundPermissionName = foregroundToBackgroundPermissionName[permissionName] + val backgroundPermissionFlags = + if (backgroundPermissionName != null) { + with(permissionPolicy) { + getPermissionFlags(appId, userId, backgroundPermissionName) + } + } else { + PermissionFlags.RUNTIME_GRANTED + } + val result = evaluateModeFromPermissionFlags(permissionFlags, backgroundPermissionFlags) + if (result != MODE_IGNORED) { + return result + } + + val fullerPermissionName = + PermissionService.getFullerPermission(permissionName) ?: return result + return getUidModeFromPermissionState(appId, userId, fullerPermissionName) + } + + private fun evaluateModeFromPermissionFlags( + foregroundFlags: Int, + backgroundFlags: Int = PermissionFlags.RUNTIME_GRANTED + ): Int = + if (PermissionFlags.isAppOpGranted(foregroundFlags)) { + if (PermissionFlags.isAppOpGranted(backgroundFlags)) { + MODE_ALLOWED + } else { + MODE_FOREGROUND + } + } else { + MODE_IGNORED + } + + override fun setUidMode(uid: Int, persistentDeviceId: String, code: Int, mode: Int): Boolean { + if (useRuntimePermissionAppOpMapping() && code in runtimeAppOpToPermissionNames) { + Slog.w( + LOG_TAG, + "Cannot set UID mode for runtime permission app op, uid = $uid," + + " code = ${AppOpsManager.opToName(code)}, mode = ${AppOpsManager.modeToName(mode)}", + RuntimeException() + ) + return false + } + val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) - val opName = AppOpsManager.opToPublicName(op) - var wasChanged = false + val appOpName = AppOpsManager.opToPublicName(code) + var wasChanged: Boolean service.mutateState { - wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) } + wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, appOpName, mode) } } return wasChanged } @@ -113,10 +249,22 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? = service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map - override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) { - val opName = AppOpsManager.opToPublicName(op) + override fun setPackageMode(packageName: String, appOpCode: Int, mode: Int, userId: Int) { + val appOpName = AppOpsManager.opToPublicName(appOpCode) + + if ( + useRuntimePermissionAppOpMapping() && runtimeAppOpToPermissionNames.contains(appOpCode) + ) { + Slog.w( + LOG_TAG, + "(packageName=$packageName, userId=$userId)'s appop state" + + " for runtime op $appOpName should not be set directly.", + RuntimeException() + ) + return + } service.mutateState { - with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) } + with(packagePolicy) { setAppOpMode(packageName, userId, appOpName, mode) } } } @@ -127,7 +275,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS } override fun removePackage(packageName: String, userId: Int): Boolean { - var wasChanged = false + var wasChanged: Boolean service.mutateState { wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) } } @@ -157,6 +305,13 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS this[AppOpsManager.strOpToOp(op)] = true } } + if (useRuntimePermissionAppOpMapping()) { + foregroundableOps.forEachIndexed { _, op, _ -> + if (getUidMode(uid, persistentDeviceId, op) == AppOpsManager.MODE_FOREGROUND) { + this[op] = true + } + } + } } } @@ -167,6 +322,13 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS this[AppOpsManager.strOpToOp(op)] = true } } + if (useRuntimePermissionAppOpMapping()) { + foregroundableOps.forEachIndexed { _, op, _ -> + if (getPackageMode(packageName, op, userId) == AppOpsManager.MODE_FOREGROUND) { + this[op] = true + } + } + } } } @@ -188,9 +350,10 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS } } - inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() { + private inner class OnAppIdAppOpModeChangedListener : + AppIdAppOpPolicy.OnAppOpModeChangedListener() { // (uid, appOpCode) -> newMode - val pendingChanges = ArrayMap<Pair<Int, Int>, Int>() + private val pendingChanges = LongSparseArray<Int>() override fun onAppOpModeChanged( appId: Int, @@ -201,7 +364,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS ) { val uid = UserHandle.getUid(userId, appId) val appOpCode = AppOpsManager.strOpToOp(appOpName) - val key = Pair(uid, appOpCode) + val key = IntPair.of(uid, appOpCode) pendingChanges[key] = newMode } @@ -210,8 +373,8 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS val listenersLocal = listeners pendingChanges.forEachIndexed { _, key, mode -> listenersLocal.forEachIndexed { _, listener -> - val uid = key.first - val appOpCode = key.second + val uid = IntPair.first(key) + val appOpCode = IntPair.second(key) listener.onUidModeChanged(uid, appOpCode, mode) } @@ -224,7 +387,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS private inner class OnPackageAppOpModeChangedListener : PackageAppOpPolicy.OnAppOpModeChangedListener() { // (packageName, userId, appOpCode) -> newMode - val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>() + private val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>() override fun onAppOpModeChanged( packageName: String, @@ -254,4 +417,115 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS pendingChanges.clear() } } + + private inner class OnPermissionFlagsChangedListener : + AppIdPermissionPolicy.OnPermissionFlagsChangedListener { + // (uid, appOpCode) -> newMode + private val pendingChanges = LongSparseArray<Int>() + + override fun onPermissionFlagsChanged( + appId: Int, + userId: Int, + permissionName: String, + oldFlags: Int, + newFlags: Int + ) { + backgroundToForegroundPermissionNames[permissionName]?.let { foregroundPermissions -> + // This is a background permission; there may be multiple foreground permissions + // affected. + foregroundPermissions.forEachIndexed { _, foregroundPermissionName -> + runtimePermissionNameToAppOp[foregroundPermissionName]?.let { appOpCode -> + val foregroundPermissionFlags = + getPermissionFlags(appId, userId, foregroundPermissionName) + addPendingChangedModeIfNeeded( + appId, + userId, + appOpCode, + foregroundPermissionFlags, + oldFlags, + foregroundPermissionFlags, + newFlags + ) + } + } + } + ?: foregroundToBackgroundPermissionName[permissionName]?.let { backgroundPermission + -> + runtimePermissionNameToAppOp[permissionName]?.let { appOpCode -> + val backgroundPermissionFlags = + getPermissionFlags(appId, userId, backgroundPermission) + addPendingChangedModeIfNeeded( + appId, + userId, + appOpCode, + oldFlags, + backgroundPermissionFlags, + newFlags, + backgroundPermissionFlags + ) + } + } + ?: runtimePermissionNameToAppOp[permissionName]?.let { appOpCode -> + addPendingChangedModeIfNeeded( + appId, + userId, + appOpCode, + oldFlags, + PermissionFlags.RUNTIME_GRANTED, + newFlags, + PermissionFlags.RUNTIME_GRANTED + ) + } + } + + private fun getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int = + service.getState { + with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) } + } + + private fun addPendingChangedModeIfNeeded( + appId: Int, + userId: Int, + appOpCode: Int, + oldForegroundFlags: Int, + oldBackgroundFlags: Int, + newForegroundFlags: Int, + newBackgroundFlags: Int, + ) { + val oldMode = evaluateModeFromPermissionFlags(oldForegroundFlags, oldBackgroundFlags) + val newMode = evaluateModeFromPermissionFlags(newForegroundFlags, newBackgroundFlags) + + if (oldMode != newMode) { + val uid = UserHandle.getUid(userId, appId) + pendingChanges[IntPair.of(uid, appOpCode)] = newMode + } + } + + override fun onStateMutated() { + val listenersLocal = listeners + pendingChanges.forEachIndexed { _, key, mode -> + listenersLocal.forEachIndexed { _, listener -> + val uid = IntPair.first(key) + val appOpCode = IntPair.second(key) + + listener.onUidModeChanged(uid, appOpCode, mode) + } + } + + pendingChanges.clear() + } + } + + companion object { + private val LOG_TAG = AppOpService::class.java.simpleName + + private fun useRuntimePermissionAppOpMapping(): Boolean { + val token = Binder.clearCallingIdentity() + return try { + Flags.runtimePermissionAppopsMapping() + } finally { + Binder.restoreCallingIdentity(token) + } + } + } } diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt index 0d9470edc4ea..2f15dc7b232a 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt @@ -17,6 +17,7 @@ package com.android.server.permission.access.appop import android.app.AppOpsManager +import android.util.Slog import com.android.server.permission.access.GetStateScope import com.android.server.permission.access.MutableAccessState import com.android.server.permission.access.MutateStateScope @@ -87,6 +88,10 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { appOpName: String, mode: Int ): Boolean { + if (userId !in newState.userStates) { + Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId") + return false + } val defaultMode = AppOpsManager.opToDefaultMode(appOpName) val oldMode = newState.userStates[userId]!! @@ -155,4 +160,8 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { */ abstract fun onStateMutated() } + + companion object { + private val LOG_TAG = PackageAppOpPolicy::class.java.simpleName + } } diff --git a/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt new file mode 100644 index 000000000000..827dd0e5d292 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.collection + +import android.util.LongSparseArray + +inline fun <T> LongSparseArray<T>.allIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (!predicate(index, key, value)) { + return false + } + } + return true +} + +inline fun <T> LongSparseArray<T>.anyIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return true + } + } + return false +} + +inline fun <T> LongSparseArray<T>.forEachIndexed(action: (Int, Long, T) -> Unit) { + for (index in 0 until size) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun <T> LongSparseArray<T>.forEachReversedIndexed(action: (Int, Long, T) -> Unit) { + for (index in lastIndex downTo 0) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun <T> LongSparseArray<T>.getOrPut(key: Long, defaultValue: () -> T): T { + val index = indexOfKey(key) + return if (index >= 0) { + valueAt(index) + } else { + defaultValue().also { put(key, it) } + } +} + +inline val <T> LongSparseArray<T>.lastIndex: Int + get() = size - 1 + +@Suppress("NOTHING_TO_INLINE") +inline operator fun <T> LongSparseArray<T>.minusAssign(key: Long) { + delete(key) +} + +inline fun <T> LongSparseArray<T>.noneIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return false + } + } + return true +} + +inline fun <T> LongSparseArray<T>.removeAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +inline fun <T> LongSparseArray<T>.retainAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (!predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +inline val <T> LongSparseArray<T>.size: Int + get() = size() + +@Suppress("NOTHING_TO_INLINE") +inline operator fun <T> LongSparseArray<T>.set(key: Long, value: T) { + put(key, value) +} diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt new file mode 100644 index 000000000000..a582431aa83c --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.permission.access.collection + +import android.util.SparseIntArray + +inline fun SparseIntArray.allIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (!predicate(index, key, value)) { + return false + } + } + return true +} + +inline fun SparseIntArray.anyIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return true + } + } + return false +} + +inline fun SparseIntArray.forEachIndexed(action: (Int, Int, Int) -> Unit) { + for (index in 0 until size) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun SparseIntArray.forEachReversedIndexed(action: (Int, Int, Int) -> Unit) { + for (index in lastIndex downTo 0) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun SparseIntArray.getOrPut(key: Int, defaultValue: () -> Int): Int { + val index = indexOfKey(key) + return if (index >= 0) { + valueAt(index) + } else { + defaultValue().also { put(key, it) } + } +} + +inline val SparseIntArray.lastIndex: Int + get() = size - 1 + +@Suppress("NOTHING_TO_INLINE") +inline operator fun SparseIntArray.minusAssign(key: Int) { + delete(key) +} + +inline fun SparseIntArray.noneIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return false + } + } + return true +} + +fun SparseIntArray.remove(key: Int) { + delete(key) +} + +fun SparseIntArray.remove(key: Int, defaultValue: Int): Int { + val index = indexOfKey(key) + return if (index >= 0) { + val oldValue = valueAt(index) + removeAt(index) + oldValue + } else { + defaultValue + } +} + +inline fun SparseIntArray.removeAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +inline fun SparseIntArray.retainAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (!predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +@Suppress("NOTHING_TO_INLINE") +inline operator fun SparseIntArray.set(key: Int, value: Int) { + put(key, value) +} + +inline val SparseIntArray.size: Int + get() = size() diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 022268df4a63..62d2d7ee848a 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -134,7 +134,9 @@ class AppIdPermissionPolicy : SchemePolicy() { ) { val changedPermissionNames = MutableIndexedSet<String>() packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName]!! + // The package may still be removed even if it was once notified as installed. + val packageState = newState.externalState.packageStates[packageName] + ?: return@forEachIndexed adoptPermissions(packageState, changedPermissionNames) addPermissionGroups(packageState) addPermissions(packageState, changedPermissionNames) @@ -147,12 +149,14 @@ class AppIdPermissionPolicy : SchemePolicy() { } packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName]!! + val packageState = newState.externalState.packageStates[packageName] + ?: return@forEachIndexed val installedPackageState = if (isSystemUpdated) packageState else null evaluateAllPermissionStatesForPackage(packageState, installedPackageState) } packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName]!! + val packageState = newState.externalState.packageStates[packageName] + ?: return@forEachIndexed newState.externalState.userIds.forEachIndexed { _, userId -> inheritImplicitPermissionStates(packageState.appId, userId) } @@ -1607,6 +1611,13 @@ class AppIdPermissionPolicy : SchemePolicy() { flagMask: Int, flagValues: Int ): Boolean { + if (userId !in newState.userStates) { + // Despite that we check UserManagerInternal.exists() in PermissionService, we may still + // sometimes get race conditions between that check and the actual mutateState() call. + // This should rarely happen but at least we should not crash. + Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId") + return false + } val oldFlags = newState.userStates[userId]!! .appIdPermissionFlags[appId] diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index f469ab547763..b162a1b88b76 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -2870,5 +2870,8 @@ class PermissionService(private val service: AccessCheckingService) : } else { emptySet<String>() } + + fun getFullerPermission(permissionName: String): String? = + FULLER_PERMISSIONS[permissionName] } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java index ff91d34470d4..92016dfc631b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java @@ -20,11 +20,10 @@ package com.android.server.display.mode; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.Mode.INVALID_MODE_ID; - import static com.android.server.display.mode.DisplayModeDirector.SYNCHRONIZED_REFRESH_RATE_TOLERANCE; import static com.android.server.display.mode.Vote.PRIORITY_LIMIT_MODE; -import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE; import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE; +import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE; import static com.android.server.display.mode.VotesStorage.GLOBAL_ID; import static com.google.common.truth.Truth.assertThat; @@ -43,6 +42,7 @@ import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfigInterface; +import android.test.mock.MockContentResolver; import android.view.Display; import android.view.DisplayInfo; @@ -51,21 +51,26 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.sensors.SensorManagerInternal; +import junitparams.JUnitParamsRunner; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import junitparams.JUnitParamsRunner; - - @SmallTest @RunWith(JUnitParamsRunner.class) public class DisplayObserverTest { + @Rule + public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); + private static final int EXTERNAL_DISPLAY = 1; private static final int MAX_WIDTH = 1920; private static final int MAX_HEIGHT = 1080; @@ -120,6 +125,8 @@ public class DisplayObserverTest { mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mResources = mock(Resources.class); when(mContext.getResources()).thenReturn(mResources); + MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext); + when(mContext.getContentResolver()).thenReturn(resolver); when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate)) .thenReturn(0); when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth)) diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 116d5db45023..97767a5dbd89 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -426,6 +426,8 @@ public class FlexibilityControllerTest { @Test public void testGetNextConstraintDropTimeElapsedLocked() { + setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS); + long nextTimeToDropNumConstraints; // no delay, deadline @@ -457,15 +459,18 @@ public class FlexibilityControllerTest { nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(130400100, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) / 2, + nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(1); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(156320100L, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 6 / 10, + nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(2); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(182240100L, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 7 / 10, + nextTimeToDropNumConstraints); // no delay, no deadline jb = createJob(0); @@ -473,15 +478,15 @@ public class FlexibilityControllerTest { nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(129600100, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(1); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(155520100L, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(2); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(181440100L, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints); // delay, deadline jb = createJob(0) @@ -1328,7 +1333,7 @@ public class FlexibilityControllerTest { final ArraySet<String> pkgs = new ArraySet<>(); pkgs.add(js.getSourcePackageName()); - when(mJobSchedulerService.getPackagesForUidLocked(mSourceUid)).thenReturn(pkgs); + doReturn(pkgs).when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid); setUidBias(mSourceUid, BIAS_TOP_APP); setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 00450267ee79..0831086b28ca 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -29,8 +29,6 @@ android_test { "src/**/*.java", "src/**/*.kt", - "test-apps/JobTestApp/src/**/*.java", - "test-apps/SuspendTestApp/src/**/*.java", ], static_libs: [ @@ -124,7 +122,6 @@ android_test { }, data: [ - ":JobTestApp", ":SimpleServiceTestApp1", ":SimpleServiceTestApp2", ":SimpleServiceTestApp3", diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index b1d50399416a..27c522d68119 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -29,7 +29,6 @@ <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> <option name="test-file-name" value="FrameworksServicesTests.apk" /> - <option name="test-file-name" value="JobTestApp.apk" /> <option name="test-file-name" value="SuspendTestApp.apk" /> <option name="test-file-name" value="SimpleServiceTestApp1.apk" /> <option name="test-file-name" value="SimpleServiceTestApp2.apk" /> diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java deleted file mode 100644 index e871fc567107..000000000000 --- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.server.job; - -import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STARTED; -import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STOPPED; -import static com.android.servicestests.apps.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.app.IActivityManager; -import android.app.job.JobParameters; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.IDeviceIdleController; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.UserHandle; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.FlakyTest; -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.servicestests.apps.jobtestapp.TestJobActivity; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests that background restrictions on jobs work as expected. - * This test requires test-apps/JobTestApp to be installed on the device. - * To run this test from root of checkout: - * <pre> - * mmm -j32 frameworks/base/services/tests/servicestests/ - * adb install -r $OUT/data/app/JobTestApp/JobTestApp.apk - * adb install -r $OUT/data/app/FrameworksServicesTests/FrameworksServicesTests.apk - * adb shell am instrument -e class 'com.android.server.job.BackgroundRestrictionsTest' -w \ - * com.android.frameworks.servicestests - * </pre> - */ -@RunWith(AndroidJUnit4.class) -@LargeTest -public class BackgroundRestrictionsTest { - private static final String TAG = BackgroundRestrictionsTest.class.getSimpleName(); - private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp"; - private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity"; - private static final long POLL_INTERVAL = 500; - private static final long DEFAULT_WAIT_TIMEOUT = 10_000; - - private Context mContext; - private AppOpsManager mAppOpsManager; - private IDeviceIdleController mDeviceIdleController; - private IActivityManager mIActivityManager; - private volatile int mTestJobId = -1; - private int mTestPackageUid; - /* accesses must be synchronized on itself */ - private final TestJobStatus mTestJobStatus = new TestJobStatus(); - private final BroadcastReceiver mJobStateChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY); - Log.d(TAG, "Received action " + intent.getAction()); - synchronized (mTestJobStatus) { - switch (intent.getAction()) { - case ACTION_JOB_STARTED: - mTestJobStatus.running = true; - mTestJobStatus.jobId = params.getJobId(); - mTestJobStatus.stopReason = JobParameters.STOP_REASON_UNDEFINED; - break; - case ACTION_JOB_STOPPED: - mTestJobStatus.running = false; - mTestJobStatus.jobId = params.getJobId(); - mTestJobStatus.stopReason = params.getStopReason(); - break; - } - } - } - }; - - @Before - public void setUp() throws Exception { - mContext = InstrumentationRegistry.getTargetContext(); - mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); - mDeviceIdleController = IDeviceIdleController.Stub.asInterface( - ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); - mIActivityManager = ActivityManager.getService(); - mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0); - mTestJobStatus.reset(); - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ACTION_JOB_STARTED); - intentFilter.addAction(ACTION_JOB_STOPPED); - mContext.registerReceiver(mJobStateChangeReceiver, intentFilter, - Context.RECEIVER_EXPORTED_UNAUDITED); - setAppOpsModeAllowed(true); - setPowerExemption(false); - } - - private void scheduleTestJob() { - mTestJobId = (int) (SystemClock.uptimeMillis() / 1000); - final Intent scheduleJobIntent = new Intent(TestJobActivity.ACTION_START_JOB); - scheduleJobIntent.putExtra(TestJobActivity.EXTRA_JOB_ID_KEY, mTestJobId); - scheduleJobIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY)); - mContext.startActivity(scheduleJobIntent); - } - - private void scheduleAndAssertJobStarted() throws Exception { - scheduleTestJob(); - Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY); - assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - } - - @FlakyTest - @Test - public void testPowerExemption() throws Exception { - scheduleAndAssertJobStarted(); - setAppOpsModeAllowed(false); - mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT); - assertTrue("Job did not stop after putting app under bg-restriction", - awaitJobStop(DEFAULT_WAIT_TIMEOUT, - JobParameters.STOP_REASON_BACKGROUND_RESTRICTION)); - - setPowerExemption(true); - scheduleTestJob(); - Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY); - assertTrue("Job did not start when the app was in the power exemption list", - awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - - setPowerExemption(false); - assertTrue("Job did not stop after removing from the power exemption list", - awaitJobStop(DEFAULT_WAIT_TIMEOUT, - JobParameters.STOP_REASON_BACKGROUND_RESTRICTION)); - - scheduleTestJob(); - Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY); - assertFalse("Job started under bg-restrictions", awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - setPowerExemption(true); - assertTrue("Job did not start when the app was in the power exemption list", - awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - } - - @After - public void tearDown() throws Exception { - final Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS); - cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY)); - cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(cancelJobsIntent); - mContext.unregisterReceiver(mJobStateChangeReceiver); - Thread.sleep(500); // To avoid race with register in the next setUp - setAppOpsModeAllowed(true); - setPowerExemption(false); - } - - private void setPowerExemption(boolean exempt) throws RemoteException { - if (exempt) { - mDeviceIdleController.addPowerSaveWhitelistApp(TEST_APP_PACKAGE); - } else { - mDeviceIdleController.removePowerSaveWhitelistApp(TEST_APP_PACKAGE); - } - } - - private void setAppOpsModeAllowed(boolean allow) { - mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mTestPackageUid, - TEST_APP_PACKAGE, allow ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); - } - - private boolean awaitJobStart(long timeout) throws InterruptedException { - return waitUntilTrue(timeout, () -> { - synchronized (mTestJobStatus) { - return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running; - } - }); - } - - private boolean awaitJobStop(long timeout, @JobParameters.StopReason int expectedStopReason) - throws InterruptedException { - return waitUntilTrue(timeout, () -> { - synchronized (mTestJobStatus) { - return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running - && (expectedStopReason == JobParameters.STOP_REASON_UNDEFINED - || mTestJobStatus.stopReason == expectedStopReason); - } - }); - } - - private boolean waitUntilTrue(long timeout, Condition condition) throws InterruptedException { - final long deadLine = SystemClock.uptimeMillis() + timeout; - do { - Thread.sleep(POLL_INTERVAL); - } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine); - return condition.isTrue(); - } - - private static final class TestJobStatus { - int jobId; - int stopReason; - boolean running; - - private void reset() { - running = false; - stopReason = jobId = 0; - } - } - - private interface Condition { - boolean isTrue(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 0f5fb91a140f..d50affba1ea1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -406,8 +406,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { public void testPushDynamicShortcut() { // Change the max number of shortcuts. - mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5"); - + mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5," + + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); setCaller(CALLING_PACKAGE_1, USER_0); final ShortcutInfo s1 = makeShortcut("s1"); @@ -545,6 +545,57 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_0)); } + public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled() + throws InterruptedException { + mService.updateConfigurationLocked( + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500"); + + // Verify calls to UsageStatsManagerInternal#reportShortcutUsage are throttled. + setCaller(CALLING_PACKAGE_1, USER_0); + { + final ShortcutInfo si = makeShortcut("s0"); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_1), eq("s0"), eq(USER_0)); + Mockito.reset(mMockUsageStatsManagerInternal); + for (int i = 2; i <= 10; i++) { + final ShortcutInfo si = makeShortcut("s" + i); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( + any(), any(), anyInt()); + + // Verify pkg2 isn't blocked by pkg1, but consecutive calls from pkg2 are throttled as well. + setCaller(CALLING_PACKAGE_2, USER_0); + { + final ShortcutInfo si = makeShortcut("s1"); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_2), eq("s1"), eq(USER_0)); + Mockito.reset(mMockUsageStatsManagerInternal); + for (int i = 2; i <= 10; i++) { + final ShortcutInfo si = makeShortcut("s" + i); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( + any(), any(), anyInt()); + + Mockito.reset(mMockUsageStatsManagerInternal); + // Let time passes which resets the throttle + Thread.sleep(505); + // Verify UsageStatsManagerInternal#reportShortcutUsed can be called again + setCaller(CALLING_PACKAGE_1, USER_0); + mManager.pushDynamicShortcut(makeShortcut("s10")); + setCaller(CALLING_PACKAGE_2, USER_0); + mManager.pushDynamicShortcut(makeShortcut("s10")); + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_1), any(), eq(USER_0)); + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_2), any(), eq(USER_0)); + } + public void testUnlimitedCalls() { setCaller(CALLING_PACKAGE_1, USER_0); diff --git a/services/tests/servicestests/test-apps/JobTestApp/Android.bp b/services/tests/servicestests/test-apps/JobTestApp/Android.bp deleted file mode 100644 index 6458bcd4fc8f..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/Android.bp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2017 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "JobTestApp", - - sdk_version: "current", - - srcs: ["**/*.java"], - - dex_preopt: { - enabled: false, - }, - optimize: { - enabled: false, - }, -} diff --git a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml deleted file mode 100644 index ac35805e8af6..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.servicestests.apps.jobtestapp"> - - <application> - <service android:name=".TestJobService" - android:permission="android.permission.BIND_JOB_SERVICE" /> - <activity android:name=".TestJobActivity" - android:exported="true" /> - </application> - -</manifest>
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/JobTestApp/OWNERS b/services/tests/servicestests/test-apps/JobTestApp/OWNERS deleted file mode 100644 index 6f207fb1a00e..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /apex/jobscheduler/OWNERS diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java deleted file mode 100644 index 99eb196e3298..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.servicestests.apps.jobtestapp; - -import android.app.Activity; -import android.app.job.JobInfo; -import android.app.job.JobScheduler; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; - -public class TestJobActivity extends Activity { - private static final String TAG = TestJobActivity.class.getSimpleName(); - private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp"; - - public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID"; - public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB"; - public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS"; - public static final int JOB_INITIAL_BACKOFF = 10_000; - public static final int JOB_MINIMUM_LATENCY = 5_000; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - ComponentName jobServiceComponent = new ComponentName(this, TestJobService.class); - JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); - final Intent intent = getIntent(); - switch (intent.getAction()) { - case ACTION_CANCEL_JOBS: - jobScheduler.cancelAll(); - Log.d(TAG, "Cancelled all jobs for " + getPackageName()); - break; - case ACTION_START_JOB: - final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode()); - JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent) - .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR) - .setMinimumLatency(JOB_MINIMUM_LATENCY) - .setOverrideDeadline(JOB_MINIMUM_LATENCY); - final int result = jobScheduler.schedule(jobBuilder.build()); - if (result != JobScheduler.RESULT_SUCCESS) { - Log.e(TAG, "Could not schedule job " + jobId); - } else { - Log.d(TAG, "Successfully scheduled job with id " + jobId); - } - break; - default: - Log.e(TAG, "Unknown action " + intent.getAction()); - } - finish(); - } -}
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java deleted file mode 100644 index b8585f26c185..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.servicestests.apps.jobtestapp; - -import android.annotation.TargetApi; -import android.app.job.JobParameters; -import android.app.job.JobService; -import android.content.Intent; -import android.util.Log; - -@TargetApi(24) -public class TestJobService extends JobService { - private static final String TAG = TestJobService.class.getSimpleName(); - private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp"; - public static final String ACTION_JOB_STARTED = PACKAGE_NAME + ".action.JOB_STARTED"; - public static final String ACTION_JOB_STOPPED = PACKAGE_NAME + ".action.JOB_STOPPED"; - public static final String JOB_PARAMS_EXTRA_KEY = PACKAGE_NAME + ".extra.JOB_PARAMETERS"; - - @Override - public boolean onStartJob(JobParameters params) { - Log.i(TAG, "Test job executing: " + params.getJobId()); - Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED); - reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - sendBroadcast(reportJobStartIntent); - return true; - } - - @Override - public boolean onStopJob(JobParameters params) { - Log.i(TAG, "Test job stopped executing: " + params.getJobId()); - Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED); - reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - sendBroadcast(reportJobStopIntent); - // Deadline constraint is dropped on reschedule, so it's more reliable to use a new job. - return false; - } -} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index c1f35ccb69e0..723ac15fb50f 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -13792,8 +13792,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); mBinderService.setNotificationPolicy("package", policy, false); - verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy), - eq(ZenModeConfig.UPDATE_ORIGIN_APP)); + verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy)); } @Test @@ -13859,7 +13858,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt()); } else { verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(), - eq(policy), anyInt()); + eq(policy)); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java index 3d8ec2ec9277..f604f1e77cf4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java @@ -52,7 +52,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase { .setShouldMaximizeDoze(true) .setShouldUseNightMode(false) .setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true) - .setUserModifiedFields(8) .build(); assertThat(deviceEffects.shouldDimWallpaper()).isTrue(); @@ -65,7 +64,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase { assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse(); assertThat(deviceEffects.shouldUseNightMode()).isFalse(); assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue(); - assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(8); } @Test @@ -97,7 +95,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase { .setShouldMinimizeRadioUsage(true) .setShouldUseNightMode(true) .setShouldSuppressAmbientDisplay(true) - .setUserModifiedFields(6) .build(); Parcel parcel = Parcel.obtain(); @@ -116,7 +113,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase { assertThat(copy.shouldUseNightMode()).isTrue(); assertThat(copy.shouldSuppressAmbientDisplay()).isTrue(); assertThat(copy.shouldDisplayGrayscale()).isFalse(); - assertThat(copy.getUserModifiedFields()).isEqualTo(6); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index db92719ecfd6..e523e79f6370 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -285,7 +285,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders()); assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders()); assertEquals(expected.getPriorityChannels(), actual.getPriorityChannels()); - assertEquals(expected.getUserModifiedFields(), actual.getUserModifiedFields()); } @Test @@ -342,45 +341,32 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.zenPolicy = null; rule.zenDeviceEffects = null; - assertThat(rule.canBeUpdatedByApp()).isTrue(); rule.userModifiedFields = 1; + assertThat(rule.canBeUpdatedByApp()).isFalse(); } @Test public void testCanBeUpdatedByApp_policyModified() throws Exception { - ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0); - ZenPolicy policy = policyBuilder.build(); - ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); - rule.zenPolicy = policy; - - assertThat(rule.userModifiedFields).isEqualTo(0); + rule.zenPolicy = new ZenPolicy(); assertThat(rule.canBeUpdatedByApp()).isTrue(); - policy = policyBuilder.setUserModifiedFields(1).build(); - assertThat(policy.getUserModifiedFields()).isEqualTo(1); - rule.zenPolicy = policy; + rule.zenPolicyUserModifiedFields = 1; + assertThat(rule.canBeUpdatedByApp()).isFalse(); } @Test public void testCanBeUpdatedByApp_deviceEffectsModified() throws Exception { - ZenDeviceEffects.Builder deviceEffectsBuilder = - new ZenDeviceEffects.Builder().setUserModifiedFields(0); - ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build(); - ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); - rule.zenDeviceEffects = deviceEffects; - - assertThat(rule.userModifiedFields).isEqualTo(0); + rule.zenDeviceEffects = new ZenDeviceEffects.Builder().build(); assertThat(rule.canBeUpdatedByApp()).isTrue(); - deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build(); - assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1); - rule.zenDeviceEffects = deviceEffects; + rule.zenDeviceEffectsUserModifiedFields = 1; + assertThat(rule.canBeUpdatedByApp()).isFalse(); } @@ -406,6 +392,8 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.allowManualInvocation = ALLOW_MANUAL; rule.type = TYPE; rule.userModifiedFields = 16; + rule.zenPolicyUserModifiedFields = 5; + rule.zenDeviceEffectsUserModifiedFields = 2; rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); @@ -432,6 +420,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.iconResName, parceled.iconResName); assertEquals(rule.type, parceled.type); assertEquals(rule.userModifiedFields, parceled.userModifiedFields); + assertEquals(rule.zenPolicyUserModifiedFields, parceled.zenPolicyUserModifiedFields); + assertEquals(rule.zenDeviceEffectsUserModifiedFields, + parceled.zenDeviceEffectsUserModifiedFields); assertEquals(rule.triggerDescription, parceled.triggerDescription); assertEquals(rule.zenPolicy, parceled.zenPolicy); assertEquals(rule.deletionInstant, parceled.deletionInstant); @@ -511,6 +502,8 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.allowManualInvocation = ALLOW_MANUAL; rule.type = TYPE; rule.userModifiedFields = 4; + rule.zenPolicyUserModifiedFields = 5; + rule.zenDeviceEffectsUserModifiedFields = 2; rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); @@ -541,6 +534,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.allowManualInvocation, fromXml.allowManualInvocation); assertEquals(rule.type, fromXml.type); assertEquals(rule.userModifiedFields, fromXml.userModifiedFields); + assertEquals(rule.zenPolicyUserModifiedFields, fromXml.zenPolicyUserModifiedFields); + assertEquals(rule.zenDeviceEffectsUserModifiedFields, + fromXml.zenDeviceEffectsUserModifiedFields); assertEquals(rule.triggerDescription, fromXml.triggerDescription); assertEquals(rule.iconResName, fromXml.iconResName); assertEquals(rule.deletionInstant, fromXml.deletionInstant); @@ -697,7 +693,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { .allowPriorityChannels(false) .hideAllVisualEffects() .showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true) - .setUserModifiedFields(4) .build(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -732,7 +727,6 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(policy.getVisualEffectAmbient(), fromXml.getVisualEffectAmbient()); assertEquals(policy.getVisualEffectNotificationList(), fromXml.getVisualEffectNotificationList()); - assertEquals(policy.getUserModifiedFields(), fromXml.getUserModifiedFields()); } private ZenModeConfig getMutedRingerConfig() { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java index 9d7cf53e62db..2e64645ecade 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -73,13 +73,15 @@ public class ZenModeDiffTest extends UiServiceTestCase { : Set.of("version", "manualRule", "automaticRules"); // Differences for flagged fields are only generated if the flag is enabled. - // "Metadata" fields (userModifiedFields, deletionInstant) are not compared. + // "Metadata" fields (userModifiedFields & co, deletionInstant) are not compared. private static final Set<String> ZEN_RULE_EXEMPT_FIELDS = android.app.Flags.modesApi() - ? Set.of("userModifiedFields", "deletionInstant") + ? Set.of("userModifiedFields", "zenPolicyUserModifiedFields", + "zenDeviceEffectsUserModifiedFields", "deletionInstant") : Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION, RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL, RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, "userModifiedFields", + "zenPolicyUserModifiedFields", "zenDeviceEffectsUserModifiedFields", "deletionInstant"); // allowPriorityChannels is flagged by android.app.modes_api diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 78fe41f31849..edc876aab388 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -46,6 +46,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; +import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS; import static android.provider.Settings.Global.ZEN_MODE_OFF; import static android.service.notification.Condition.SOURCE_SCHEDULE; import static android.service.notification.Condition.SOURCE_USER_ACTION; @@ -67,6 +68,7 @@ import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW; import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW; import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -295,6 +297,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL); when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt())) .thenReturn(appInfoSpy); + when(mPackageManager.getApplicationInfo(eq(mContext.getPackageName()), anyInt())) + .thenReturn(appInfoSpy); mZenModeHelper.mPm = mPackageManager; mZenModeEventLogger.reset(); @@ -2236,12 +2240,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); - // savedRule.getDeviceEffects() is equal to zde, except for the userModifiedFields. - // So we clear before comparing. - ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects()) - .setUserModifiedFields(0).build(); - - assertThat(savedEffects).isEqualTo(zde); + assertThat(savedRule.getDeviceEffects()).isEqualTo(zde); } @Test @@ -2331,12 +2330,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); - // savedRule.getDeviceEffects() is equal to updateFromUser, except for the - // userModifiedFields, so we clear before comparing. - ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects()) - .setUserModifiedFields(0).build(); - - assertThat(savedEffects).isEqualTo(updateFromUser); + assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser); } @Test @@ -3411,7 +3405,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { rule.allowManualInvocation = ALLOW_MANUAL; rule.type = TYPE; - rule.userModifiedFields = AutomaticZenRule.FIELD_NAME; rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; @@ -3426,7 +3419,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(POLICY, actual.getZenPolicy()); assertEquals(CONFIG_ACTIVITY, actual.getConfigurationActivity()); assertEquals(TYPE, actual.getType()); - assertEquals(AutomaticZenRule.FIELD_NAME, actual.getUserModifiedFields()); assertEquals(ALLOW_MANUAL, actual.isManualInvocationAllowed()); assertEquals(CREATION_TIME, actual.getCreationTime()); assertEquals(OWNER.getPackageName(), actual.getPackageName()); @@ -3453,29 +3445,31 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setManualInvocationAllowed(ALLOW_MANUAL) .build(); - ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); - - mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true); - - assertEquals(NAME, rule.name); - assertEquals(OWNER, rule.component); - assertEquals(CONDITION_ID, rule.conditionId); - assertEquals(INTERRUPTION_FILTER_ZR, rule.zenMode); - assertEquals(ENABLED, rule.enabled); - assertEquals(POLICY, rule.zenPolicy); - assertEquals(CONFIG_ACTIVITY, rule.configurationActivity); - assertEquals(TYPE, rule.type); - assertEquals(ALLOW_MANUAL, rule.allowManualInvocation); - assertEquals(OWNER.getPackageName(), rule.getPkg()); - assertEquals(ICON_RES_NAME, rule.iconResName); + String ruleId = mZenModeHelper.addAutomaticZenRule(OWNER.getPackageName(), azr, + UPDATE_ORIGIN_APP, "add", CUSTOM_PKG_UID); + + ZenModeConfig.ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + + assertThat(storedRule).isNotNull(); + assertEquals(NAME, storedRule.name); + assertEquals(OWNER, storedRule.component); + assertEquals(CONDITION_ID, storedRule.conditionId); + assertEquals(INTERRUPTION_FILTER_ZR, storedRule.zenMode); + assertEquals(ENABLED, storedRule.enabled); + assertEquals(POLICY, storedRule.zenPolicy); + assertEquals(CONFIG_ACTIVITY, storedRule.configurationActivity); + assertEquals(TYPE, storedRule.type); + assertEquals(ALLOW_MANUAL, storedRule.allowManualInvocation); + assertEquals(OWNER.getPackageName(), storedRule.getPkg()); + assertEquals(ICON_RES_NAME, storedRule.iconResName); // Because the origin of the update is the app, we don't expect the bitmask to change. - assertEquals(0, rule.userModifiedFields); - assertEquals(TRIGGER_DESC, rule.triggerDescription); + assertEquals(0, storedRule.userModifiedFields); + assertEquals(TRIGGER_DESC, storedRule.triggerDescription); } @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_updatesNameUnlessUserModified() { + public void updateAutomaticZenRule_fromApp_updatesNameUnlessUserModified() { // Add a starting rule with the name OriginalName. AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID) .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) @@ -3492,7 +3486,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { Process.SYSTEM_UID); rule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(rule.getName()).isEqualTo("NewName"); - assertThat(rule.canUpdate()).isTrue(); // The user modifies some other field in the rule, which makes the rule as a whole not // app modifiable. @@ -3501,10 +3494,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build(); mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID); - rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.getUserModifiedFields()) - .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER); - assertThat(rule.canUpdate()).isFalse(); // ...but the app can still modify the name, because the name itself hasn't been modified // by the user. @@ -3524,8 +3513,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { Process.SYSTEM_UID); rule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(rule.getName()).isEqualTo("UserProvidedName"); - assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME - | AutomaticZenRule.FIELD_INTERRUPTION_FILTER); // The app is no longer able to modify the name. azrUpdate = new AutomaticZenRule.Builder(rule) @@ -3539,7 +3526,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_updatesBitmaskAndValueForUserOrigin() { + public void updateAutomaticZenRule_fromUser_updatesBitmaskAndValue() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setZenPolicy(new ZenPolicy.Builder().build()) @@ -3571,84 +3558,21 @@ public class ZenModeHelperTest extends UiServiceTestCase { // UPDATE_ORIGIN_USER should change the bitmask and change the values. assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY); - assertThat(rule.getUserModifiedFields()) - .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER); - assertThat(rule.getZenPolicy().getUserModifiedFields()) - .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS); assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW); - assertThat(rule.getDeviceEffects().getUserModifiedFields()) - .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE); assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); - } - @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_doesNotUpdateValuesForInitUserOrigin() { - // Adds a starting rule with empty zen policies and device effects - AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) - .setInterruptionFilter(INTERRUPTION_FILTER_ALL) // Already the default, no change - .setZenPolicy(new ZenPolicy.Builder() - .allowReminders(false) - .build()) - .setDeviceEffects(new ZenDeviceEffects.Builder() - .setShouldDisplayGrayscale(false) - .build()) - .build(); - // Adds the rule using the user, to set user-modified bits. - String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID); - AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.canUpdate()).isFalse(); - assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME); - - ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy()) - .allowReminders(true) - .build(); - ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder(rule.getDeviceEffects()) - .setShouldDisplayGrayscale(true) - .build(); - AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule) - .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) - .setZenPolicy(policy) - .setDeviceEffects(deviceEffects) - .build(); - - // Attempts to update the rule with the AZR from origin init user. - mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason", - Process.SYSTEM_UID); - AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId); - - // UPDATE_ORIGIN_INIT_USER does not change the bitmask or values if rule is user modified. - // TODO: b/318506692 - Remove once we check that INIT origins can't call add/updateAZR. - assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields()); - assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL); - assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo( - rule.getZenPolicy().getUserModifiedFields()); - assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders()).isEqualTo( - ZenPolicy.STATE_DISALLOW); - assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo( - rule.getDeviceEffects().getUserModifiedFields()); - assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isFalse(); - - // Creates a new rule with the AZR from origin init user. - String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason", Process.SYSTEM_UID); - AutomaticZenRule newRule = mZenModeHelper.getAutomaticZenRule(newRuleId); - - // UPDATE_ORIGIN_INIT_USER does change the values if the rule is new, - // but does not update the bitmask. - assertThat(newRule.getUserModifiedFields()).isEqualTo(0); - assertThat(newRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY); - assertThat(newRule.getZenPolicy().getUserModifiedFields()).isEqualTo(0); - assertThat(newRule.getZenPolicy().getPriorityCategoryReminders()) - .isEqualTo(ZenPolicy.STATE_ALLOW); - assertThat(newRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0); - assertThat(newRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.userModifiedFields) + .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER); + assertThat(storedRule.zenPolicyUserModifiedFields) + .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS); + assertThat(storedRule.zenDeviceEffectsUserModifiedFields) + .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE); } @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_updatesValuesForSystemUiOrigin() { + public void updateAutomaticZenRule_fromSystemUi_updatesValues() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setInterruptionFilter(INTERRUPTION_FILTER_ALL) @@ -3684,17 +3608,19 @@ public class ZenModeHelperTest extends UiServiceTestCase { rule = mZenModeHelper.getAutomaticZenRule(ruleId); // UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask. - assertThat(rule.getUserModifiedFields()).isEqualTo(0); - assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(0); assertThat(rule.getZenPolicy().getPriorityCategoryReminders()) .isEqualTo(ZenPolicy.STATE_ALLOW); - assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0); assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); + + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.userModifiedFields).isEqualTo(0); + assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0); + assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0); } @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_updatesValuesIfRuleNotUserModified() { + public void updateAutomaticZenRule_fromApp_updatesValuesIfRuleNotUserModified() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setInterruptionFilter(INTERRUPTION_FILTER_ALL) @@ -3709,7 +3635,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.canUpdate()).isTrue(); ZenPolicy policy = new ZenPolicy.Builder() .allowReminders(true) @@ -3717,57 +3642,59 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder() .setShouldDisplayGrayscale(true) .build(); - AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule) + AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule) .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) .setZenPolicy(policy) .setDeviceEffects(deviceEffects) .build(); - // Since the rule is not already user modified, UPDATE_ORIGIN_UNKNOWN can modify the rule. + // Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule. // The bitmask is not modified. - mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_UNKNOWN, "reason", + mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); - AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields()); - assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS); - assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo( - rule.getZenPolicy().getUserModifiedFields()); - assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders()) + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.userModifiedFields).isEqualTo(0); + + assertThat(storedRule.zenMode).isEqualTo(ZEN_MODE_ALARMS); + assertThat(storedRule.zenPolicy.getPriorityCategoryReminders()) .isEqualTo(ZenPolicy.STATE_ALLOW); - assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo( - rule.getDeviceEffects().getUserModifiedFields()); - assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); + assertThat(storedRule.zenDeviceEffects.shouldDisplayGrayscale()).isTrue(); + assertThat(storedRule.userModifiedFields).isEqualTo(0); + assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0); + assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0); // Creates another rule, this time from user. This will have user modified bits set. String ruleIdUser = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID); - AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser); - assertThat(ruleUser.canUpdate()).isFalse(); + storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser); + int ruleModifiedFields = storedRule.userModifiedFields; + int rulePolicyModifiedFields = storedRule.zenPolicyUserModifiedFields; + int ruleDeviceEffectsModifiedFields = storedRule.zenDeviceEffectsUserModifiedFields; - // Zen rule update coming from unknown origin. This cannot fully update the rule, because + // Zen rule update coming from the app again. This cannot fully update the rule, because // the rule is already considered user modified. - mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_UNKNOWN, + mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); - ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser); + AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser); - // UPDATE_ORIGIN_UNKNOWN can only change the value if the rule is not already user modified, + // The app can only change the value if the rule is not already user modified, // so the rule is not changed, and neither is the bitmask. assertThat(ruleUser.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL); - // Interruption Filter All is the default value, so it's not included as a modified field. - assertThat(ruleUser.getUserModifiedFields() | AutomaticZenRule.FIELD_NAME).isGreaterThan(0); - assertThat(ruleUser.getZenPolicy().getUserModifiedFields() - | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS).isGreaterThan(0); assertThat(ruleUser.getZenPolicy().getPriorityCategoryReminders()) .isEqualTo(ZenPolicy.STATE_DISALLOW); - assertThat(ruleUser.getDeviceEffects().getUserModifiedFields() - | ZenDeviceEffects.FIELD_GRAYSCALE).isGreaterThan(0); assertThat(ruleUser.getDeviceEffects().shouldDisplayGrayscale()).isFalse(); + + storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser); + assertThat(storedRule.userModifiedFields).isEqualTo(ruleModifiedFields); + assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(rulePolicyModifiedFields); + assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo( + ruleDeviceEffectsModifiedFields); } @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_updatesValuesIfRuleNew() { + public void addAutomaticZenRule_updatesValues() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) @@ -3778,21 +3705,22 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setShouldDisplayGrayscale(true) .build()) .build(); - // Adds the rule using origin unknown, to show that a new rule is always allowed. String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - azrBase, UPDATE_ORIGIN_UNKNOWN, "reason", Process.SYSTEM_UID); + azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); // The values are modified but the bitmask is not. - assertThat(rule.canUpdate()).isTrue(); assertThat(rule.getZenPolicy().getPriorityCategoryReminders()) .isEqualTo(ZenPolicy.STATE_ALLOW); assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); + + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.canBeUpdatedByApp()).isTrue(); } @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_nullDeviceEffectsUpdate() { + public void updateAutomaticZenRule_nullDeviceEffectsUpdate() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setDeviceEffects(new ZenDeviceEffects.Builder().build()) @@ -3807,9 +3735,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setDeviceEffects(null) .build(); - // Zen rule update coming from unknown origin, but since the rule isn't already + // Zen rule update coming from app, but since the rule isn't already // user modified, it can be updated. - mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason", + mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); rule = mZenModeHelper.getAutomaticZenRule(ruleId); @@ -3819,7 +3747,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test @EnableFlags(Flags.FLAG_MODES_API) - public void automaticZenRuleToZenRule_nullPolicyUpdate() { + public void updateAutomaticZenRule_nullPolicyUpdate() { // Adds a starting rule with empty zen policies and device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setZenPolicy(new ZenPolicy.Builder().build()) @@ -3828,16 +3756,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.canUpdate()).isTrue(); AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase) // Set zen policy to null .setZenPolicy(null) .build(); - // Zen rule update coming from unknown origin, but since the rule isn't already + // Zen rule update coming from app, but since the rule isn't already // user modified, it can be updated. - mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason", + mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); rule = mZenModeHelper.getAutomaticZenRule(ruleId); @@ -3859,7 +3786,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.canUpdate()).isTrue(); // Create a fully populated ZenPolicy. ZenPolicy policy = new ZenPolicy.Builder() @@ -3894,8 +3820,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { // New ZenPolicy differs from the default config assertThat(rule.getZenPolicy()).isNotNull(); assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW); - assertThat(rule.canUpdate()).isFalse(); - assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo( + + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.canBeUpdatedByApp()).isFalse(); + assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo( ZenPolicy.FIELD_ALLOW_CHANNELS | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS | ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS @@ -3918,7 +3846,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID); AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); - assertThat(rule.canUpdate()).isTrue(); ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder() .setShouldDisplayGrayscale(true) @@ -3935,8 +3862,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { // New ZenDeviceEffects is used; all fields considered set, since previously were null. assertThat(rule.getDeviceEffects()).isNotNull(); assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); - assertThat(rule.canUpdate()).isFalse(); - assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo( + + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.canBeUpdatedByApp()).isFalse(); + assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo( ZenDeviceEffects.FIELD_GRAYSCALE); } @@ -4339,7 +4268,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule, UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID); assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000); - assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).canUpdate()).isTrue(); // User customizes it. AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule) @@ -4371,9 +4299,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS); assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo( ZenPolicy.STATE_ALLOW); - assertThat(finalRule.getUserModifiedFields()).isEqualTo( + + ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); + assertThat(storedRule.userModifiedFields).isEqualTo( AutomaticZenRule.FIELD_INTERRUPTION_FILTER); - assertThat(finalRule.getZenPolicy().getUserModifiedFields()).isEqualTo( + assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo( ZenPolicy.FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS); // Also, we discarded the "deleted rule" since we already used it for restoration. @@ -4652,7 +4582,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZEN_MODE_IMPORTANT_INTERRUPTIONS); assertThat(mZenModeHelper.mConfig.automaticRules.values()) - .comparingElementsUsing(IGNORE_TIMESTAMPS) + .comparingElementsUsing(IGNORE_METADATA) .containsExactly( expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, true)); @@ -4672,12 +4602,75 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZEN_MODE_ALARMS); assertThat(mZenModeHelper.mConfig.automaticRules.values()) - .comparingElementsUsing(IGNORE_TIMESTAMPS) + .comparingElementsUsing(IGNORE_METADATA) .containsExactly( expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, null, true)); } @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void applyGlobalZenModeAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() { + mZenModeHelper.mConfig.automaticRules.clear(); + String pkg = mContext.getPackageName(); + + // From app, call "setInterruptionFilter" and create and implicit rule. + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID, + ZEN_MODE_IMPORTANT_INTERRUPTIONS); + String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet()); + assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode) + .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS); + + // From user, update that rule's interruption filter. + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); + AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) + .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason", + Process.SYSTEM_UID); + + // From app, call "setInterruptionFilter" again. + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID, + ZEN_MODE_NO_INTERRUPTIONS); + + // The app's update was ignored, and the user's update is still current, and the current + // mode is the one they chose. + assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode) + .isEqualTo(ZEN_MODE_ALARMS); + assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void applyGlobalZenModeAsImplicitZenRule_ruleCustomizedButNotFilter_updatesRule() { + mZenModeHelper.mConfig.automaticRules.clear(); + String pkg = mContext.getPackageName(); + + // From app, call "setInterruptionFilter" and create and implicit rule. + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID, + ZEN_MODE_IMPORTANT_INTERRUPTIONS); + String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet()); + assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode) + .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS); + + // From user, update something in that rule, but not the interruption filter. + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); + AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) + .setName("Renamed") + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason", + Process.SYSTEM_UID); + + // From app, call "setInterruptionFilter" again. + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID, + ZEN_MODE_NO_INTERRUPTIONS); + + // The app's update was accepted, and the current mode is the one that they wanted. + assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode) + .isEqualTo(ZEN_MODE_NO_INTERRUPTIONS); + assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_NO_INTERRUPTIONS); + } + + @Test public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mZenModeHelper.mConfig.automaticRules.clear(); @@ -4747,8 +4740,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED, Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT); - mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy, - UPDATE_ORIGIN_APP); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy); ZenPolicy expectedZenPolicy = new ZenPolicy.Builder() .disallowAllSounds() @@ -4758,7 +4750,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .allowPriorityChannels(true) .build(); assertThat(mZenModeHelper.mConfig.automaticRules.values()) - .comparingElementsUsing(IGNORE_TIMESTAMPS) + .comparingElementsUsing(IGNORE_METADATA) .containsExactly( expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS, expectedZenPolicy, /* conditionActive= */ null)); @@ -4773,14 +4765,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED, Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT); mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, - original, UPDATE_ORIGIN_APP); + original); // Change priorityCallSenders: contacts -> starred. Policy updated = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS, PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED, Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT); - mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated, - UPDATE_ORIGIN_APP); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated); ZenPolicy expectedZenPolicy = new ZenPolicy.Builder() .disallowAllSounds() @@ -4790,20 +4781,87 @@ public class ZenModeHelperTest extends UiServiceTestCase { .allowPriorityChannels(true) .build(); assertThat(mZenModeHelper.mConfig.automaticRules.values()) - .comparingElementsUsing(IGNORE_TIMESTAMPS) + .comparingElementsUsing(IGNORE_METADATA) .containsExactly( expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS, expectedZenPolicy, /* conditionActive= */ null)); } @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void applyGlobalPolicyAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() { + mZenModeHelper.mConfig.automaticRules.clear(); + String pkg = mContext.getPackageName(); + + // From app, call "setNotificationPolicy" and create and implicit rule. + Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy); + String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet()); + + // From user, update that rule's policy. + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); + ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds() + .allowAlarms(true).build(); + AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) + .setZenPolicy(userUpdateZenPolicy) + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason", + Process.SYSTEM_UID); + + // From app, call "setNotificationPolicy" again. + Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy); + + // The app's update was ignored, and the user's update is still current. + assertThat(mZenModeHelper.mConfig.automaticRules.values()) + .comparingElementsUsing(IGNORE_METADATA) + .containsExactly( + expectedImplicitRule(pkg, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + userUpdateZenPolicy, + /* conditionActive= */ null)); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void applyGlobalPolicyAsImplicitZenRule_ruleCustomizedButNotZenPolicy_updatesRule() { + mZenModeHelper.mConfig.automaticRules.clear(); + String pkg = mContext.getPackageName(); + + // From app, call "setNotificationPolicy" and create and implicit rule. + Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy); + String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet()); + + // From user, update something in that rule, but not the ZenPolicy. + AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); + AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) + .setName("Rule renamed, not touching policy") + .build(); + mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason", + Process.SYSTEM_UID); + + // From app, call "setNotificationPolicy" again. + Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy); + + // The app's update was applied. + ZenPolicy appsSecondZenPolicy = new ZenPolicy.Builder() + .disallowAllSounds() + .allowSystem(true) + .allowPriorityChannels(true) + .build(); + assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenPolicy) + .isEqualTo(appsSecondZenPolicy); + } + + @Test public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() { mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API); mZenModeHelper.mConfig.automaticRules.clear(); withoutWtfCrash( () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, - CUSTOM_PKG_UID, new Policy(0, 0, 0), UPDATE_ORIGIN_APP)); + CUSTOM_PKG_UID, new Policy(0, 0, 0))); assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty(); } @@ -4816,7 +4874,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { Policy.getAllSuppressedVisualEffects(), STATE_FALSE, CONVERSATION_SENDERS_IMPORTANT); mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, - writtenPolicy, UPDATE_ORIGIN_APP); + writtenPolicy); Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule( CUSTOM_PKG_NAME); @@ -4856,7 +4914,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(readPolicy.allowConversations()).isFalse(); } - private static final Correspondence<ZenRule, ZenRule> IGNORE_TIMESTAMPS = + private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA = Correspondence.transforming(zr -> { Parcel p = Parcel.obtain(); try { @@ -4864,12 +4922,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { p.setDataPosition(0); ZenRule copy = new ZenRule(p); copy.creationTime = 0; + copy.userModifiedFields = 0; + copy.zenPolicyUserModifiedFields = 0; + copy.zenDeviceEffectsUserModifiedFields = 0; return copy; } finally { p.recycle(); } }, - "Ignoring timestamps"); + "Ignoring timestamp and userModifiedFields"); private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy, @Nullable Boolean conditionActive) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java index 7941eb4d2090..4ed55df7775c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java @@ -645,38 +645,12 @@ public class ZenPolicyTest extends UiServiceTestCase { } @Test - public void testFromParcel() { - ZenPolicy.Builder builder = new ZenPolicy.Builder(); - builder.setUserModifiedFields(10); - - ZenPolicy policy = builder.build(); - assertThat(policy.getUserModifiedFields()).isEqualTo(10); - - Parcel parcel = Parcel.obtain(); - policy.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - - ZenPolicy fromParcel = ZenPolicy.CREATOR.createFromParcel(parcel); - assertThat(fromParcel.getUserModifiedFields()).isEqualTo(10); - } - - @Test - public void testPolicy_userModifiedFields() { - ZenPolicy.Builder builder = new ZenPolicy.Builder(); - builder.setUserModifiedFields(10); - assertThat(builder.build().getUserModifiedFields()).isEqualTo(10); - - builder.setUserModifiedFields(0); - assertThat(builder.build().getUserModifiedFields()).isEqualTo(0); - } - - @Test public void testPolicyBuilder_constructFromPolicy() { ZenPolicy.Builder builder = new ZenPolicy.Builder(); ZenPolicy policy = builder.allowRepeatCallers(true).allowAlarms(false) .showLights(true).showBadges(false) .allowPriorityChannels(true) - .setUserModifiedFields(20).build(); + .build(); ZenPolicy newPolicy = new ZenPolicy.Builder(policy).build(); @@ -689,7 +663,6 @@ public class ZenPolicyTest extends UiServiceTestCase { assertThat(newPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET); assertThat(newPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW); - assertThat(newPolicy.getUserModifiedFields()).isEqualTo(20); } @Test diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java index 336bfdd0fb14..a8fd6f29f862 100644 --- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java +++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java @@ -35,11 +35,13 @@ import android.util.Slog; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.Keep; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.RingBuffer; import com.android.server.usage.BroadcastResponseStatsTracker.NotificationEventType; +import java.util.function.IntFunction; +import java.util.function.Supplier; + public class BroadcastResponseStatsLogger { private static final int MAX_LOG_SIZE = @@ -49,10 +51,10 @@ public class BroadcastResponseStatsLogger { @GuardedBy("mLock") private final LogBuffer mBroadcastEventsBuffer = new LogBuffer( - BroadcastEvent.class, MAX_LOG_SIZE); + BroadcastEvent::new, BroadcastEvent[]::new, MAX_LOG_SIZE); @GuardedBy("mLock") private final LogBuffer mNotificationEventsBuffer = new LogBuffer( - NotificationEvent.class, MAX_LOG_SIZE); + NotificationEvent::new, NotificationEvent[]::new, MAX_LOG_SIZE); void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage, UserHandle targetUser, long idForResponseEvent, @@ -96,8 +98,8 @@ public class BroadcastResponseStatsLogger { private static final class LogBuffer<T extends Data> extends RingBuffer<T> { - LogBuffer(Class<T> classType, int capacity) { - super(classType, capacity); + LogBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) { + super(newItem, newBacking, capacity); } void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage, @@ -179,8 +181,7 @@ public class BroadcastResponseStatsLogger { } } - @Keep - public static final class BroadcastEvent implements Data { + private static final class BroadcastEvent implements Data { public int sourceUid; public int targetUserId; public int targetUidProcessState; @@ -200,8 +201,7 @@ public class BroadcastResponseStatsLogger { } } - @Keep - public static final class NotificationEvent implements Data { + private static final class NotificationEvent implements Data { public int type; public String packageName; public int userId; @@ -218,7 +218,7 @@ public class BroadcastResponseStatsLogger { } } - public interface Data { + private interface Data { void reset(); } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java index 1df7012c44f8..49ad46131b0d 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java @@ -62,7 +62,8 @@ public class PhoneCallStateHandler { SubscriptionManager subscriptionManager, TelephonyManager telephonyManager, Callback callback) { - mSubscriptionManager = Objects.requireNonNull(subscriptionManager); + mSubscriptionManager = Objects.requireNonNull(subscriptionManager) + .createForAllUserProfiles(); mTelephonyManager = Objects.requireNonNull(telephonyManager); mCallback = Objects.requireNonNull(callback); mSubscriptionManager.addOnSubscriptionsChangedListener( diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 57b13e960093..e81f48280e46 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1049,8 +1049,17 @@ public class TelecomManager { public static final int PRESENTATION_UNAVAILABLE = 5; + /** + * Controls audio route for video calls. + * 0 - Use the default audio routing strategy. + * 1 - Disable the speaker. Route the audio to Headset or Bluetooth + * or Earpiece, based on the default audio routing strategy. + * @hide + */ + public static final String PROPERTY_VIDEOCALL_AUDIO_OUTPUT = "persist.radio.call.audio.output"; + /* - * Values for the adb property "persist.radio.videocall.audio.output" + * Values for the adb property "persist.radio.call.audio.output" */ /** @hide */ public static final int AUDIO_OUTPUT_ENABLE_SPEAKER = 0; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 73c26a3e5fc9..1badf674c8ce 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9694,6 +9694,27 @@ public class CarrierConfigManager { "remove_satellite_plmn_in_manual_network_scan_bool"; /** + * An integer key holds the time interval for refreshing or re-querying the satellite + * entitlement status from the entitlement server to ensure it is the latest. + * + * The default value is 30 days (1 month). + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = + "satellite_entitlement_status_refresh_days_int"; + + /** + * This configuration enables device to query the entitlement server to get the satellite + * configuration. + * This will need agreement the carrier before enabling this flag. + * + * The default value is false. + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = + "satellite_entitlement_supported_bool"; + + /** * Indicating whether DUN APN should be disabled when the device is roaming. In that case, * the default APN (i.e. internet) will be used for tethering. * @@ -10799,6 +10820,8 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT, CellSignalStrengthLte.USE_RSRP); sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true); + sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30); + sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false); sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, ""); sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false); diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java index 4c37f7d3184c..b84ff2977b34 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -16,6 +16,7 @@ package android.telephony.ims; +import android.annotation.FlaggedApi; import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -46,6 +47,7 @@ import android.util.SparseBooleanArray; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.util.TelephonyUtils; import java.lang.annotation.Retention; @@ -152,12 +154,36 @@ public class ImsService extends Service { public static final long CAPABILITY_TERMINAL_BASED_CALL_WAITING = 1 << 2; /** + * This ImsService supports the capability to manage calls on multiple subscriptions at the same + * time. + * <p> + * When set, this ImsService supports managing calls on multiple subscriptions at the same time + * for all WLAN network configurations. Telephony will allow new outgoing/incoming IMS calls to + * be set up on other subscriptions while there is an ongoing call. The ImsService must also + * support managing calls on WWAN + WWAN configurations whenever the modem also reports + * simultaneous calling availability, which can be listened to using the + * {@link android.telephony.TelephonyCallback.SimultaneousCellularCallingSupportListener} API. + * Telephony will only allow additional ongoing/incoming IMS calls on another subscription to be + * set up on WWAN + WWAN configurations when the modem reports that simultaneous cellular + * calling is allowed at the current time on both subscriptions where there are ongoing calls. + * <p> + * When unset (default), this ImsService can not support calls on multiple subscriptions at the + * same time for any WLAN or WWAN configurations, so pending outgoing call placed on another + * cellular subscription while there is an ongoing call will be cancelled by Telephony. + * Similarly, any incoming call notification on another cellular subscription while there is an + * ongoing call will be rejected. + * @hide TODO: move this to system API when we have a backing implementation + CTS testing + */ + @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS) + public static final long CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING = 1 << 3; + + /** * Used for internal correctness checks of capabilities set by the ImsService implementation and * tracks the index of the largest defined flag in the capabilities long. * @hide */ public static final long CAPABILITY_MAX_INDEX = - Long.numberOfTrailingZeros(CAPABILITY_TERMINAL_BASED_CALL_WAITING); + Long.numberOfTrailingZeros(CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING); /** * @hide diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 3b0397b6e480..70047a6feb9c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -928,10 +928,19 @@ public final class SatelliteManager { @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; + /** + * Satellite communication restricted by entitlement server. This can be triggered based on + * the EntitlementStatus value received from the entitlement server to enable or disable + * satellite. + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; + /** @hide */ @IntDef(prefix = "SATELLITE_COMMUNICATION_RESTRICTION_REASON_", value = { SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, - SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION + SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION, + SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteCommunicationRestrictionReason {} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java index e3988cd20199..c44d9435d203 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java @@ -68,7 +68,7 @@ public class ActivityEmbeddingSecondaryActivity extends Activity { // This triggers a recalcuation of splitatributes. ActivityEmbeddingController .getInstance(ActivityEmbeddingSecondaryActivity.this) - .invalidateTopVisibleActivityStacks(); + .invalidateVisibleActivityStacks(); } }); findViewById(R.id.secondary_enter_pip_button).setOnClickListener( diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java new file mode 100644 index 000000000000..379c4ae8a059 --- /dev/null +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.okhttp.internalandroidapi; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; + +/** + * A domain name service that resolves IP addresses for host names. + * @hide + * @hide This class is not part of the Android public SDK API + */ +public interface Dns { + /** + * Returns the IP addresses of {@code hostname}, in the order they should + * be attempted. + * + * @hide + */ + List<InetAddress> lookup(String hostname) throws UnknownHostException; +} |