diff options
567 files changed, 13203 insertions, 4737 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 44f3d706a0d0..52200bfcfdf6 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -180,6 +180,18 @@ aconfig_declarations { srcs: ["core/java/android/nfc/*.aconfig"], } +cc_aconfig_library { + name: "android_nfc_flags_aconfig_c_lib", + vendor_available: true, + aconfig_declarations: "android.nfc.flags-aconfig", + apex_available: [ + "//apex_available:platform", + "com.android.nfcservices", + "nfc_nci.st21nfc.default", + ], + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + java_aconfig_library { name: "android.nfc.flags-aconfig-java", aconfig_declarations: "android.nfc.flags-aconfig", diff --git a/Android.bp b/Android.bp index 895ef98c5537..a402c57689d6 100644 --- a/Android.bp +++ b/Android.bp @@ -249,6 +249,7 @@ java_library { "android.se.omapi-V1-java", "android.system.suspend.control.internal-java", "devicepolicyprotosnano", + "ImmutabilityAnnotation", "com.android.sysprop.init", "com.android.sysprop.localization", diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index 29a8dfa96a57..a4174ee6ae17 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -1,13 +1,3 @@ -// b/305195721 -android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:7428: lint: Unresolved link/see tag "android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Drawables DevicePolicyResources.Drawables" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyResourcesManager.java:179: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyResourcesManager [101] - // b/303477132 android/app/appsearch/AppSearchSchema.java:402: lint: Unresolved link/see tag "#getIndexableNestedProperties()" in android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder [101] android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.AppSearchSession [101] diff --git a/core/api/current.txt b/core/api/current.txt index 3f4a34b51e40..75ac8fbed995 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5316,21 +5316,23 @@ package android.app { method public android.net.Uri getConditionId(); method @Nullable public android.content.ComponentName getConfigurationActivity(); method public long getCreationTime(); + method @FlaggedApi("android.app.modes_api") @Nullable public android.service.notification.ZenDeviceEffects getDeviceEffects(); method @FlaggedApi("android.app.modes_api") @DrawableRes public int getIconResId(); method public int getInterruptionFilter(); method public String getName(); method public android.content.ComponentName getOwner(); method @FlaggedApi("android.app.modes_api") @Nullable public String getTriggerDescription(); method @FlaggedApi("android.app.modes_api") public int getType(); - method public android.service.notification.ZenPolicy getZenPolicy(); + method @Nullable public android.service.notification.ZenPolicy getZenPolicy(); method public boolean isEnabled(); method @FlaggedApi("android.app.modes_api") public boolean isManualInvocationAllowed(); method public void setConditionId(android.net.Uri); method public void setConfigurationActivity(@Nullable android.content.ComponentName); + method @FlaggedApi("android.app.modes_api") public void setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects); method public void setEnabled(boolean); method public void setInterruptionFilter(int); method public void setName(String); - method public void setZenPolicy(android.service.notification.ZenPolicy); + method public void setZenPolicy(@Nullable android.service.notification.ZenPolicy); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.AutomaticZenRule> CREATOR; field @FlaggedApi("android.app.modes_api") public static final int TYPE_BEDTIME = 3; // 0x3 @@ -5350,6 +5352,7 @@ package android.app { method @NonNull public android.app.AutomaticZenRule build(); method @NonNull public android.app.AutomaticZenRule.Builder setConditionId(@NonNull android.net.Uri); method @NonNull public android.app.AutomaticZenRule.Builder setConfigurationActivity(@Nullable android.content.ComponentName); + method @NonNull public android.app.AutomaticZenRule.Builder setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects); method @NonNull public android.app.AutomaticZenRule.Builder setEnabled(boolean); method @NonNull public android.app.AutomaticZenRule.Builder setIconResId(@DrawableRes int); method @NonNull public android.app.AutomaticZenRule.Builder setInterruptionFilter(int); @@ -7861,6 +7864,7 @@ package android.app.admin { field public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY = "persistentPreferredActivity"; field public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken"; field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled"; + field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; field public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY = "userControlDisabledPackages"; } @@ -14699,31 +14703,31 @@ package android.database.sqlite { } @FlaggedApi("android.database.sqlite.sqlite_apis_35") public final class SQLiteRawStatement implements java.io.Closeable { - method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException; - method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException; - method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException; - method public void bindInt(int, int) throws android.database.sqlite.SQLiteException; - method public void bindLong(int, long) throws android.database.sqlite.SQLiteException; - method public void bindNull(int) throws android.database.sqlite.SQLiteException; - method public void bindText(int, @NonNull String) throws android.database.sqlite.SQLiteException; + method public void bindBlob(int, @NonNull byte[]); + method public void bindBlob(int, @NonNull byte[], int, int); + method public void bindDouble(int, double); + method public void bindInt(int, int); + method public void bindLong(int, long); + method public void bindNull(int); + method public void bindText(int, @NonNull String); method public void clearBindings(); method public void close(); - method @Nullable public byte[] getColumnBlob(int) throws android.database.sqlite.SQLiteException; - method public double getColumnDouble(int) throws android.database.sqlite.SQLiteException; - method public int getColumnInt(int) throws android.database.sqlite.SQLiteException; - method public int getColumnLength(int) throws android.database.sqlite.SQLiteException; - method public long getColumnLong(int) throws android.database.sqlite.SQLiteException; - method @NonNull public String getColumnName(int) throws android.database.sqlite.SQLiteException; - method @NonNull public String getColumnText(int) throws android.database.sqlite.SQLiteException; - method public int getColumnType(int) throws android.database.sqlite.SQLiteException; + method @Nullable public byte[] getColumnBlob(int); + method public double getColumnDouble(int); + method public int getColumnInt(int); + method public int getColumnLength(int); + method public long getColumnLong(int); + method @NonNull public String getColumnName(int); + method @NonNull public String getColumnText(int); + method public int getColumnType(int); method public int getParameterCount(); method public int getParameterIndex(@NonNull String); method @Nullable public String getParameterName(int); method public int getResultColumnCount(); method public boolean isOpen(); - method public int readColumnBlob(int, @NonNull byte[], int, int, int) throws android.database.sqlite.SQLiteException; + method public int readColumnBlob(int, @NonNull byte[], int, int, int); method public void reset(); - method public boolean step() throws android.database.sqlite.SQLiteException; + method public boolean step(); field public static final int SQLITE_DATA_TYPE_BLOB = 4; // 0x4 field public static final int SQLITE_DATA_TYPE_FLOAT = 2; // 0x2 field public static final int SQLITE_DATA_TYPE_INTEGER = 1; // 0x1 @@ -22804,11 +22808,11 @@ package android.media { method public void clearOnSessionLostStateListener(); method public void close(); method public void closeSession(@NonNull byte[]); - method @android.media.MediaDrm.HdcpLevel public int getConnectedHdcpLevel(); + method public int getConnectedHdcpLevel(); method public android.media.MediaDrm.CryptoSession getCryptoSession(@NonNull byte[], @NonNull String, @NonNull String); method @NonNull public android.media.MediaDrm.KeyRequest getKeyRequest(@NonNull byte[], @Nullable byte[], @Nullable String, int, @Nullable java.util.HashMap<java.lang.String,java.lang.String>) throws android.media.NotProvisionedException; method @NonNull public java.util.List<android.media.MediaDrm.LogMessage> getLogMessages(); - method @android.media.MediaDrm.HdcpLevel public int getMaxHdcpLevel(); + method public int getMaxHdcpLevel(); method public static int getMaxSecurityLevel(); method public int getMaxSessionCount(); method public android.os.PersistableBundle getMetrics(); @@ -22822,13 +22826,13 @@ package android.media { method @Deprecated @NonNull public byte[] getSecureStop(@NonNull byte[]); method @Deprecated @NonNull public java.util.List<byte[]> getSecureStopIds(); method @Deprecated @NonNull public java.util.List<byte[]> getSecureStops(); - method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]); + method public int getSecurityLevel(@NonNull byte[]); method @NonNull public static java.util.List<java.util.UUID> getSupportedCryptoSchemes(); method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID); method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String); - method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, @android.media.MediaDrm.SecurityLevel int); + method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, int); method @NonNull public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException; - method @NonNull public byte[] openSession(@android.media.MediaDrm.SecurityLevel int) throws android.media.NotProvisionedException, android.media.ResourceBusyException; + method @NonNull public byte[] openSession(int) throws android.media.NotProvisionedException, android.media.ResourceBusyException; method @Nullable public byte[] provideKeyResponse(@NonNull byte[], @NonNull byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException; method public void provideProvisionResponse(@NonNull byte[]) throws android.media.DeniedByServerException; method @NonNull public java.util.HashMap<java.lang.String,java.lang.String> queryKeyStatus(@NonNull byte[]); @@ -22840,7 +22844,7 @@ package android.media { method public void removeOfflineLicense(@NonNull byte[]); method @Deprecated public void removeSecureStop(@NonNull byte[]); method public boolean requiresSecureDecoder(@NonNull String); - method public boolean requiresSecureDecoder(@NonNull String, @android.media.MediaDrm.SecurityLevel int); + method public boolean requiresSecureDecoder(@NonNull String, int); method public void restoreKeys(@NonNull byte[], @NonNull byte[]); method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener); method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener, @Nullable android.os.Handler); @@ -22929,9 +22933,6 @@ package android.media { field public static final int ERROR_ZERO_SUBSAMPLES = 33; // 0x21 } - @Deprecated @IntDef({android.media.MediaDrm.HDCP_LEVEL_UNKNOWN, android.media.MediaDrm.HDCP_NONE, android.media.MediaDrm.HDCP_V1, android.media.MediaDrm.HDCP_V2, android.media.MediaDrm.HDCP_V2_1, android.media.MediaDrm.HDCP_V2_2, android.media.MediaDrm.HDCP_V2_3, android.media.MediaDrm.HDCP_NO_DIGITAL_OUTPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.HdcpLevel { - } - public static final class MediaDrm.KeyRequest { method @NonNull public byte[] getData(); method @NonNull public String getDefaultUrl(); @@ -23033,9 +23034,6 @@ package android.media { method @NonNull public String getDefaultUrl(); } - @Deprecated @IntDef({android.media.MediaDrm.SECURITY_LEVEL_UNKNOWN, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.SecurityLevel { - } - public static final class MediaDrm.SessionException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable { ctor public MediaDrm.SessionException(int, @Nullable String); method @Deprecated public int getErrorCode(); @@ -28698,14 +28696,17 @@ package android.nfc { } public final class NfcAdapter { + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction(); method public void disableForegroundDispatch(android.app.Activity); method public void disableReaderMode(android.app.Activity); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction(); method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]); method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle); method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context); method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo(); method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler); method public boolean isEnabled(); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported(); method public boolean isSecureNfcEnabled(); @@ -28813,6 +28814,7 @@ package android.nfc.cardemulation { method public boolean removeAidsForService(android.content.ComponentName, String); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String); method public boolean setPreferredService(android.app.Activity, android.content.ComponentName); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean); method public boolean supportsAidPrefixRegistration(); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName); method public boolean unsetPreferredService(android.app.Activity); @@ -28832,9 +28834,20 @@ package android.nfc.cardemulation { method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onDeactivated(int); method public abstract byte[] processCommandApdu(byte[], android.os.Bundle); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>); method public final void sendResponseApdu(byte[]); field public static final int DEACTIVATION_DESELECTED = 1; // 0x1 field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0 + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U' field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE"; field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service"; } @@ -37273,6 +37286,7 @@ package android.provider { } public static final class Telephony.Carriers implements android.provider.BaseColumns { + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String ALWAYS_ON = "always_on"; field public static final String APN = "apn"; field public static final String AUTH_TYPE = "authtype"; field @Deprecated public static final String BEARER = "bearer"; @@ -37286,6 +37300,8 @@ package android.provider { field public static final String MMSPORT = "mmsport"; field public static final String MMSPROXY = "mmsproxy"; field @Deprecated public static final String MNC = "mnc"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String MTU_V4 = "mtu_v4"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String MTU_V6 = "mtu_v6"; field @Deprecated public static final String MVNO_MATCH_DATA = "mvno_match_data"; field @Deprecated public static final String MVNO_TYPE = "mvno_type"; field public static final String NAME = "name"; @@ -37301,6 +37317,8 @@ package android.provider { field public static final String SUBSCRIPTION_ID = "sub_id"; field public static final String TYPE = "type"; field public static final String USER = "user"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String USER_EDITABLE = "user_editable"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String USER_VISIBLE = "user_visible"; } public static final class Telephony.Mms implements android.provider.Telephony.BaseMmsColumns { @@ -40697,6 +40715,26 @@ package android.service.notification { field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.StatusBarNotification> CREATOR; } + @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable { + method public int describeContents(); + method public boolean shouldDimWallpaper(); + method public boolean shouldDisplayGrayscale(); + method public boolean shouldSuppressAmbientDisplay(); + method public boolean shouldUseNightMode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.ZenDeviceEffects> CREATOR; + } + + @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder { + ctor public ZenDeviceEffects.Builder(); + ctor public ZenDeviceEffects.Builder(@NonNull android.service.notification.ZenDeviceEffects); + method @NonNull public android.service.notification.ZenDeviceEffects build(); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDimWallpaper(boolean); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDisplayGrayscale(boolean); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldSuppressAmbientDisplay(boolean); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldUseNightMode(boolean); + } + public final class ZenPolicy implements android.os.Parcelable { method public int describeContents(); method public int getPriorityCallSenders(); @@ -43651,6 +43689,7 @@ package android.telephony { field public static final String KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT = "iwlan.child_sa_rekey_soft_timer_sec_int"; field public static final String KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_cbc_key_size_int_array"; field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_gcm_key_size_int_array"; field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array"; field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int"; field public static final String KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT = "iwlan.epdg_address_ip_type_preference_int"; @@ -43666,12 +43705,15 @@ package android.telephony { field public static final String KEY_IKE_REMOTE_ID_TYPE_INT = "iwlan.ike_remote_id_type_int"; field public static final String KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_cbc_key_size_int_array"; field public static final String KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_ctr_key_size_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_gcm_key_size_int_array"; field public static final String KEY_MAX_RETRIES_INT = "iwlan.max_retries_int"; field public static final String KEY_MCC_MNCS_STRING_ARRAY = "iwlan.mcc_mncs_string_array"; field public static final String KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT = "iwlan.natt_keep_alive_timer_sec_int"; field public static final String KEY_PREFIX = "iwlan."; field public static final String KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY = "iwlan.retransmit_timer_sec_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY = "iwlan.supported_child_session_aead_algorithms_int_array"; field public static final String KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_child_session_encryption_algorithms_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_aead_algorithms_int_array"; field public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_encryption_algorithms_int_array"; field public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = "iwlan.supported_integrity_algorithms_int_array"; field public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = "iwlan.supported_prf_algorithms_int_array"; @@ -45817,6 +45859,7 @@ package android.telephony.data { method public int getProxyPort(); method public int getRoamingProtocol(); method public String getUser(); + method @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public boolean isAlwaysOn(); method public boolean isEnabled(); method public boolean isPersistent(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -45856,6 +45899,7 @@ package android.telephony.data { public static class ApnSetting.Builder { ctor public ApnSetting.Builder(); method public android.telephony.data.ApnSetting build(); + method @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") @NonNull public android.telephony.data.ApnSetting.Builder setAlwaysOn(boolean); method @NonNull public android.telephony.data.ApnSetting.Builder setApnName(@Nullable String); method @NonNull public android.telephony.data.ApnSetting.Builder setApnTypeBitmask(int); method @NonNull public android.telephony.data.ApnSetting.Builder setAuthType(int); diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt index 1308b1fc578b..449249e02768 100644 --- a/core/api/lint-baseline.txt +++ b/core/api/lint-baseline.txt @@ -1,40 +1,4 @@ // Baseline format: 1.0 -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindBlob(int, byte[]): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindBlob(int, byte[], int, int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindDouble(int, double): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindInt(int, int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindLong(int, long): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindNull(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindText(int, String): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnBlob(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnDouble(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnInt(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnLength(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnLong(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnName(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnText(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnType(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#readColumnBlob(int, byte[], int, int, int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#step(): - Methods must not throw unchecked exceptions - - BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED: Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED: diff --git a/core/api/removed.txt b/core/api/removed.txt index 5a4be65ef559..989bb7756e4c 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -224,6 +224,12 @@ package android.media { ctor public AudioFormat(); } + @Deprecated @IntDef({android.media.MediaDrm.HDCP_LEVEL_UNKNOWN, android.media.MediaDrm.HDCP_NONE, android.media.MediaDrm.HDCP_V1, android.media.MediaDrm.HDCP_V2, android.media.MediaDrm.HDCP_V2_1, android.media.MediaDrm.HDCP_V2_2, android.media.MediaDrm.HDCP_V2_3, android.media.MediaDrm.HDCP_NO_DIGITAL_OUTPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.HdcpLevel { + } + + @Deprecated @IntDef({android.media.MediaDrm.SECURITY_LEVEL_UNKNOWN, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.SecurityLevel { + } + } package android.media.tv { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 06fd5591f960..fbd2142ba625 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3185,6 +3185,7 @@ package android.companion.virtual { field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2 field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1 field public static final int LAUNCH_SUCCESS = 0; // 0x0 + field @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") public static final String PERSISTENT_DEVICE_ID_DEFAULT = "default:0"; } public static interface VirtualDeviceManager.ActivityListener { @@ -4809,7 +4810,7 @@ package android.hardware.hdmi { method public void onChange(@NonNull String); } - @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult { + @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface HdmiControlManager.ControlCallbackResult { } public static interface HdmiControlManager.HotplugEventListener { @@ -11281,15 +11282,11 @@ package android.provider { field public static final String MAX_CONNECTIONS = "max_conns"; field public static final String MODEM_PERSIST = "modem_cognitive"; field @Deprecated public static final String MTU = "mtu"; - field public static final String MTU_V4 = "mtu_v4"; - field public static final String MTU_V6 = "mtu_v6"; field public static final int NO_APN_SET_ID = 0; // 0x0 field public static final String TIME_LIMIT_FOR_MAX_CONNECTIONS = "max_conns_time"; field public static final int UNEDITED = 0; // 0x0 field public static final int USER_DELETED = 2; // 0x2 - field public static final String USER_EDITABLE = "user_editable"; field public static final int USER_EDITED = 1; // 0x1 - field public static final String USER_VISIBLE = "user_visible"; field public static final String WAIT_TIME_RETRY = "wait_time"; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 8b20720418b6..75797edfb218 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -541,7 +541,6 @@ package android.app.admin { field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods"; field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended"; field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled"; - field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; } public class DevicePolicyManager { diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 3f9cc65ba1db..8ad6ea207665 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -632,6 +632,7 @@ public class AccessibilityServiceInfo implements Parcelable { InputDevice.SOURCE_JOYSTICK, InputDevice.SOURCE_SENSOR }) + @Retention(RetentionPolicy.SOURCE) public @interface MotionEventSources {} /** diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java index af00f316de7b..6ec956ee7783 100644 --- a/core/java/android/accessibilityservice/TouchInteractionController.java +++ b/core/java/android/accessibilityservice/TouchInteractionController.java @@ -24,6 +24,8 @@ import android.util.ArrayMap; import android.view.MotionEvent; import android.view.accessibility.AccessibilityInteractionClient; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Executor; @@ -92,6 +94,7 @@ public final class TouchInteractionController { STATE_DRAGGING, STATE_DELEGATING }) + @Retention(RetentionPolicy.SOURCE) private @interface State {} // The maximum number of pointers that can be touching the screen at once. (See MAX_POINTER_ID diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index be433d2ab0f2..ed18d81d7914 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -23,7 +23,9 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.inMultiWindowMode; import static android.os.Process.myUid; + import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; + import static java.lang.Character.MIN_VALUE; import android.annotation.AnimRes; @@ -1000,6 +1002,7 @@ public class Activity extends ContextThemeWrapper FULLSCREEN_MODE_REQUEST_EXIT, FULLSCREEN_MODE_REQUEST_ENTER }) + @Retention(RetentionPolicy.SOURCE) public @interface FullscreenModeRequest {} /** Request type of {@link #requestFullscreenMode(int, OutcomeReceiver)}, to request exiting the @@ -1016,6 +1019,7 @@ public class Activity extends ContextThemeWrapper OVERRIDE_TRANSITION_OPEN, OVERRIDE_TRANSITION_CLOSE }) + @Retention(RetentionPolicy.SOURCE) public @interface OverrideTransition {} /** diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 8b4ebaee04c5..854e12177fb5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4998,6 +4998,7 @@ public class ActivityManager { STOP_USER_ON_SWITCH_TRUE, STOP_USER_ON_SWITCH_FALSE }) + @Retention(RetentionPolicy.SOURCE) public @interface StopUserOnSwitch {} /** diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 919e084002ea..a7b29aab4e69 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -22,12 +22,12 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.NotificationManager.InterruptionFilter; -import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.service.notification.Condition; +import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenPolicy; import android.view.WindowInsetsController; @@ -111,6 +111,7 @@ public final class AutomaticZenRule implements Parcelable { private ComponentName configurationActivity; private long creationTime; private ZenPolicy mZenPolicy; + private ZenDeviceEffects mDeviceEffects; private boolean mModified = false; private String mPkg; private int mType = TYPE_UNKNOWN; @@ -190,6 +191,7 @@ public final class AutomaticZenRule implements Parcelable { /** * @hide */ + // TODO: b/310620812 - Remove when the flag is inlined (all system callers should use Builder). public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity, Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled, long creationTime) { @@ -209,10 +211,11 @@ public final class AutomaticZenRule implements Parcelable { configurationActivity = getTrimmedComponentName( source.readParcelable(null, android.content.ComponentName.class)); creationTime = source.readLong(); - mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); + mZenPolicy = source.readParcelable(null, ZenPolicy.class); mModified = source.readInt() == ENABLED; mPkg = source.readString(); if (Flags.modesApi()) { + mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class); mAllowManualInvocation = source.readBoolean(); mIconResId = source.readInt(); mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH); @@ -274,10 +277,18 @@ public final class AutomaticZenRule implements Parcelable { /** * Gets the zen policy. */ + @Nullable public ZenPolicy getZenPolicy() { return mZenPolicy == null ? null : this.mZenPolicy.copy(); } + /** Gets the {@link ZenDeviceEffects} of this rule. */ + @Nullable + @FlaggedApi(Flags.FLAG_MODES_API) + public ZenDeviceEffects getDeviceEffects() { + return mDeviceEffects; + } + /** * Returns the time this rule was created, represented as milliseconds since the epoch. */ @@ -325,11 +336,21 @@ public final class AutomaticZenRule implements Parcelable { /** * Sets the zen policy. */ - public void setZenPolicy(ZenPolicy zenPolicy) { + public void setZenPolicy(@Nullable ZenPolicy zenPolicy) { this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy()); } /** + * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes to + * the device behavior that should apply while the rule is active, but are not directly related + * to suppressing notifications (for example: disabling always-on display). + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public void setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) { + mDeviceEffects = deviceEffects; + } + + /** * Sets the configuration activity - an activity that handles * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information * about this rule and/or allows them to configure it. This is required to be non-null for rules @@ -451,6 +472,7 @@ public final class AutomaticZenRule implements Parcelable { dest.writeInt(mModified ? ENABLED : DISABLED); dest.writeString(mPkg); if (Flags.modesApi()) { + dest.writeParcelable(mDeviceEffects, 0); dest.writeBoolean(mAllowManualInvocation); dest.writeInt(mIconResId); dest.writeString(mTriggerDescription); @@ -472,7 +494,8 @@ public final class AutomaticZenRule implements Parcelable { .append(",mZenPolicy=").append(mZenPolicy); if (Flags.modesApi()) { - sb.append(",allowManualInvocation=").append(mAllowManualInvocation) + sb.append(",deviceEffects=").append(mDeviceEffects) + .append(",allowManualInvocation=").append(mAllowManualInvocation) .append(",iconResId=").append(mIconResId) .append(",triggerDescription=").append(mTriggerDescription) .append(",type=").append(mType); @@ -498,6 +521,7 @@ public final class AutomaticZenRule implements Parcelable { && other.creationTime == creationTime; if (Flags.modesApi()) { return finalEquals + && Objects.equals(other.mDeviceEffects, mDeviceEffects) && other.mAllowManualInvocation == mAllowManualInvocation && other.mIconResId == mIconResId && Objects.equals(other.mTriggerDescription, mTriggerDescription) @@ -510,8 +534,8 @@ public final class AutomaticZenRule implements Parcelable { public int hashCode() { if (Flags.modesApi()) { return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, - configurationActivity, mZenPolicy, mModified, creationTime, mPkg, - mAllowManualInvocation, mIconResId, mTriggerDescription, mType); + configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime, + mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType); } return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, configurationActivity, mZenPolicy, mModified, creationTime, mPkg); @@ -573,6 +597,7 @@ public final class AutomaticZenRule implements Parcelable { private boolean mEnabled; private ComponentName mConfigurationActivity = null; private ZenPolicy mPolicy = null; + private ZenDeviceEffects mDeviceEffects = null; private int mType; private String mDescription; private int mIconResId; @@ -588,6 +613,7 @@ public final class AutomaticZenRule implements Parcelable { mEnabled = rule.isEnabled(); mConfigurationActivity = rule.getConfigurationActivity(); mPolicy = rule.getZenPolicy(); + mDeviceEffects = rule.getDeviceEffects(); mType = rule.getType(); mDescription = rule.getTriggerDescription(); mIconResId = rule.getIconResId(); @@ -639,6 +665,17 @@ public final class AutomaticZenRule implements Parcelable { } /** + * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes + * to the device behavior that should apply while the rule is active, but are not directly + * related to suppressing notifications (for example: disabling always-on display). + */ + @NonNull + public Builder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) { + mDeviceEffects = deviceEffects; + return this; + } + + /** * Sets the type of the rule */ public @NonNull Builder setType(@Type int type) { @@ -687,6 +724,7 @@ public final class AutomaticZenRule implements Parcelable { public @NonNull AutomaticZenRule build() { AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity, mConditionId, mPolicy, mInterruptionFilter, mEnabled); + rule.mDeviceEffects = mDeviceEffects; rule.creationTime = mCreationTime; rule.mType = mType; rule.mTriggerDescription = mDescription; diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index 355092378279..fd13174a6a39 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -575,8 +575,9 @@ public class DownloadManager { extras.putString(Downloads.DIR_TYPE, dirType); client.call(Downloads.CALL_CREATE_EXTERNAL_PUBLIC_DIR, null, extras); } catch (RemoteException e) { - throw new IllegalStateException("Unable to create directory: " - + file.getAbsolutePath()); + throw new IllegalStateException( + "Unable to create directory: " + file.getAbsolutePath(), + e); } } else { if (file.exists()) { diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index ec5effd0963d..94385717d349 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -214,6 +214,8 @@ interface INotificationManager void setNotificationPolicyAccessGranted(String pkg, boolean granted); void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted); AutomaticZenRule getAutomaticZenRule(String id); + Map<String, AutomaticZenRule> getAutomaticZenRules(); + // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined. List<ZenModeConfig.ZenRule> getZenRules(); String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg); boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 545ba8e81f62..6aad1682466d 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -64,6 +64,8 @@ import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.PasswordValidationError; import com.android.internal.widget.VerifyCredentialResponse; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; @@ -235,6 +237,7 @@ public class KeyguardManager { PIN, PATTERN }) + @Retention(RetentionPolicy.SOURCE) @interface LockTypes {} private final IKeyguardLockedStateListener mIKeyguardLockedStateListener = diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2d80b1ffca6c..337e3f1195be 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -924,6 +924,7 @@ public class Notification implements Parcelable VISIBILITY_SECRET, NotificationManager.VISIBILITY_NO_OVERRIDE }) + @Retention(RetentionPolicy.SOURCE) public @interface NotificationVisibilityOverride{}; /** diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index b0332c32bfbc..51c937d4e94a 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1256,17 +1256,21 @@ public class NotificationManager { public Map<String, AutomaticZenRule> getAutomaticZenRules() { INotificationManager service = getService(); try { - List<ZenModeConfig.ZenRule> rules = service.getZenRules(); - Map<String, AutomaticZenRule> ruleMap = new HashMap<>(); - for (ZenModeConfig.ZenRule rule : rules) { - AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, - rule.configurationActivity, rule.conditionId, rule.zenPolicy, - zenModeToInterruptionFilter(rule.zenMode), rule.enabled, - rule.creationTime); - azr.setPackageName(rule.pkg); - ruleMap.put(rule.id, azr); + if (Flags.modesApi()) { + return service.getAutomaticZenRules(); + } else { + List<ZenModeConfig.ZenRule> rules = service.getZenRules(); + Map<String, AutomaticZenRule> ruleMap = new HashMap<>(); + for (ZenModeConfig.ZenRule rule : rules) { + AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, + rule.configurationActivity, rule.conditionId, rule.zenPolicy, + zenModeToInterruptionFilter(rule.zenMode), rule.enabled, + rule.creationTime); + azr.setPackageName(rule.pkg); + ruleMap.put(rule.id, azr); + } + return ruleMap; } - return ruleMap; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1642,6 +1646,7 @@ public class NotificationManager { * * <p> */ + // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior. public Policy getNotificationPolicy() { INotificationManager service = getService(); try { @@ -1660,6 +1665,7 @@ public class NotificationManager { * * @param policy The new desired policy. */ + // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior. public void setNotificationPolicy(@NonNull Policy policy) { checkRequired("policy", policy); INotificationManager service = getService(); @@ -2608,6 +2614,7 @@ public class NotificationManager { * Only available if policy access is granted to this package. See * {@link #isNotificationPolicyAccessGranted}. */ + // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior. public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter) { final INotificationManager service = getService(); try { diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 019a1a8cb2a6..46216343dcff 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -42,6 +42,8 @@ import android.view.Surface; import android.view.WindowManager; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -120,6 +122,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu WINDOWING_MODE_PINNED, WINDOWING_MODE_FREEFORM, }) + @Retention(RetentionPolicy.SOURCE) public @interface WindowingMode {} /** The current activity type of the configuration. */ @@ -147,6 +150,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_DREAM, }) + @Retention(RetentionPolicy.SOURCE) public @interface ActivityType {} /** The current always on top status of the configuration. */ diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index e4ee959336a5..14462b853c02 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -47,6 +47,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; @@ -175,6 +177,7 @@ public final class DeviceAdminInfo implements Parcelable { public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1; @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED}) + @Retention(RetentionPolicy.SOURCE) private @interface HeadlessDeviceOwnerMode {} /** @hide */ diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index 84b1ca5c6a61..b0bec783555b 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -164,11 +164,8 @@ public final class DevicePolicyIdentifiers { /** * String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}. - * - * @hide */ @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED) - @TestApi public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3df11f6f5691..1d036a15faa2 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3998,8 +3998,7 @@ public class DevicePolicyManager { /** * An integer array extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate which - * resource IDs (see {@link DevicePolicyResources.Drawables} and - * {@link DevicePolicyResources.Strings}) have been updated. + * resource IDs (i.e. strings and drawables) have been updated. */ public static final String EXTRA_RESOURCE_IDS = "android.app.extra.RESOURCE_IDS"; @@ -8370,9 +8369,7 @@ public class DevicePolicyManager { * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was * successfully set or not. This callback will contain: * <ul> - * <li> The policy identifier returned from - * {@link DevicePolicyIdentifiers#getIdentifierForUserRestriction(String)} with user restriction - * {@link UserManager#DISALLOW_CAMERA} + * <li> The policy identifier: userRestriction_no_camera * <li> The {@link TargetUser} that this policy relates to * <li> The {@link PolicyUpdateResult}, which will be * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the @@ -17113,19 +17110,19 @@ public class DevicePolicyManager { * Returns {@code true} if this device is marked as a financed device. * * <p>A financed device can be entered into lock task mode (see {@link #setLockTaskPackages}) - * by the holder of the role {@link android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}. + * by the holder of the role {@code android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}. * If this occurs, Device Owners and Profile Owners that have set lock task packages or * features, or that attempt to set lock task packages or features, will receive a callback * indicating that it could not be set. See {@link PolicyUpdateReceiver#onPolicyChanged} and * {@link PolicyUpdateReceiver#onPolicySetResult}. * * <p>To be informed of changes to this status you can subscribe to the broadcast - * {@link ACTION_DEVICE_FINANCING_STATE_CHANGED}. + * {@link #ACTION_DEVICE_FINANCING_STATE_CHANGED}. * * @throws SecurityException if the caller is not a device owner, profile owner of an * organization-owned managed profile, profile owner on the primary user or holder of one of the - * following roles: {@link android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT}, - * android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION. + * following roles: {@code android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT}, + * {@code android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION}. */ public boolean isDeviceFinanced() { throwIfParentInstance("isDeviceFinanced"); diff --git a/core/java/android/app/admin/DevicePolicyResourcesManager.java b/core/java/android/app/admin/DevicePolicyResourcesManager.java index 2cc189f87ced..7a7123167771 100644 --- a/core/java/android/app/admin/DevicePolicyResourcesManager.java +++ b/core/java/android/app/admin/DevicePolicyResourcesManager.java @@ -452,7 +452,7 @@ public class DevicePolicyResourcesManager { /** * Returns the appropriate updated string for the {@code stringId} (see - * {@link DevicePolicyResources.Strings}) if one was set using + * {@code DevicePolicyResources.Strings}) if one was set using * {@code setStrings}, otherwise returns the string from {@code defaultStringLoader}. * * <p>Also returns the string from {@code defaultStringLoader} if {@code stringId} is diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java index a6595feed1f7..b5c66ffa72a1 100644 --- a/core/java/android/app/ambientcontext/AmbientContextEvent.java +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java @@ -86,7 +86,9 @@ public final class AmbientContextEvent implements Parcelable { EVENT_SNORE, EVENT_BACK_DOUBLE_TAP, EVENT_VENDOR_WEARABLE_START, - }) public @interface EventCode {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EventCode {} /** The integer indicating an unknown level. */ public static final int LEVEL_UNKNOWN = 0; @@ -114,7 +116,9 @@ public final class AmbientContextEvent implements Parcelable { LEVEL_MEDIUM, LEVEL_MEDIUM_HIGH, LEVEL_HIGH - }) public @interface LevelValue {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LevelValue {} @EventCode private final int mEventType; private static int defaultEventType() { diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java index bf383f1165c4..159481f82e39 100644 --- a/core/java/android/app/ambientcontext/AmbientContextManager.java +++ b/core/java/android/app/ambientcontext/AmbientContextManager.java @@ -32,6 +32,8 @@ import android.os.RemoteException; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -106,7 +108,9 @@ public final class AmbientContextManager { STATUS_SERVICE_UNAVAILABLE, STATUS_MICROPHONE_DISABLED, STATUS_ACCESS_DENIED - }) public @interface StatusCode {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StatusCode {} /** * Allows clients to retrieve the list of {@link AmbientContextEvent}s from the intent. diff --git a/core/java/android/app/cloudsearch/SearchResponse.java b/core/java/android/app/cloudsearch/SearchResponse.java index c86142e0d22d..dab1657d60d0 100644 --- a/core/java/android/app/cloudsearch/SearchResponse.java +++ b/core/java/android/app/cloudsearch/SearchResponse.java @@ -21,6 +21,8 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -37,6 +39,7 @@ public final class SearchResponse implements Parcelable { SEARCH_STATUS_OK, SEARCH_STATUS_TIME_OUT, SEARCH_STATUS_NO_INTERNET}) + @Retention(RetentionPolicy.SOURCE) public @interface SearchStatusCode { } diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java index dd332c850d5d..bc8fac5fa0ce 100644 --- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java @@ -71,17 +71,13 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { @NonNull public static ActivityConfigurationChangeItem obtain(@NonNull IBinder activityToken, @NonNull Configuration config) { - if (config == null) { - throw new IllegalArgumentException("Config must not be null."); - } - ActivityConfigurationChangeItem instance = ObjectPool.obtain(ActivityConfigurationChangeItem.class); if (instance == null) { instance = new ActivityConfigurationChangeItem(); } instance.setActivityToken(activityToken); - instance.mConfiguration = config; + instance.mConfiguration = new Configuration(config); return instance; } @@ -89,7 +85,7 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { @Override public void recycle() { super.recycle(); - mConfiguration = Configuration.EMPTY; + mConfiguration = null; ObjectPool.recycle(this); } diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java index a5dd115d78b3..3ce094ef7467 100644 --- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java +++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java @@ -32,6 +32,7 @@ import android.util.Slog; import com.android.internal.content.ReferrerIntent; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -51,7 +52,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem { /** * A record that was properly configured for relaunch. Execution will be cancelled if not - * initialized after {@link #preExecute(ClientTransactionHandler, IBinder)}. + * initialized after {@link #preExecute(ClientTransactionHandler)}. */ private ActivityClientRecord mActivityClientRecord; @@ -99,10 +100,11 @@ public class ActivityRelaunchItem extends ActivityTransactionItem { instance = new ActivityRelaunchItem(); } instance.setActivityToken(activityToken); - instance.mPendingResults = pendingResults; - instance.mPendingNewIntents = pendingNewIntents; + instance.mPendingResults = pendingResults != null ? new ArrayList<>(pendingResults) : null; + instance.mPendingNewIntents = + pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null; instance.mConfigChanges = configChanges; - instance.mConfig = config; + instance.mConfig = new MergedConfiguration(config); instance.mPreserveWindow = preserveWindow; return instance; diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java index 24fced4981d6..51a09fb59236 100644 --- a/core/java/android/app/servertransaction/ActivityResultItem.java +++ b/core/java/android/app/servertransaction/ActivityResultItem.java @@ -35,6 +35,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Trace; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -82,7 +83,7 @@ public class ActivityResultItem extends ActivityTransactionItem { instance = new ActivityResultItem(); } instance.setActivityToken(activityToken); - instance.mResultInfoList = resultInfoList; + instance.mResultInfoList = new ArrayList<>(resultInfoList); return instance; } diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java index 2a65b3528145..b4ff476f4702 100644 --- a/core/java/android/app/servertransaction/ActivityTransactionItem.java +++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java @@ -20,6 +20,8 @@ import static android.app.servertransaction.TransactionExecutorHelper.getActivit import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; +import static java.util.Objects.requireNonNull; + import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -93,7 +95,7 @@ public abstract class ActivityTransactionItem extends ClientTransactionItem { } void setActivityToken(@NonNull IBinder activityToken) { - mActivityToken = activityToken; + mActivityToken = requireNonNull(activityToken); } // To be overridden diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index 9c0cd39e8102..7c34cdefe95f 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -54,12 +54,14 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** A list of individual callbacks to a client. */ @UnsupportedAppUsage + @Nullable private List<ClientTransactionItem> mActivityCallbacks; /** * Final lifecycle state in which the client activity should be after the transaction is * executed. */ + @Nullable private ActivityLifecycleItem mLifecycleStateRequest; /** Target client. */ @@ -123,6 +125,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { @VisibleForTesting(visibility = PACKAGE) @UnsupportedAppUsage @Deprecated + @Nullable public ActivityLifecycleItem getLifecycleStateRequest() { return mLifecycleStateRequest; } @@ -207,7 +210,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { for (int i = 0; i < size; i++) { mActivityCallbacks.get(i).recycle(); } - mActivityCallbacks.clear(); + mActivityCallbacks = null; } if (mLifecycleStateRequest != null) { mLifecycleStateRequest.recycle(); diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java index 96961aced987..0e327a7627d1 100644 --- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java @@ -64,7 +64,7 @@ public class ConfigurationChangeItem extends ClientTransactionItem { if (instance == null) { instance = new ConfigurationChangeItem(); } - instance.mConfiguration = config; + instance.mConfiguration = new Configuration(config); instance.mDeviceId = deviceId; return instance; diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java index a64c744c70ba..d2ef65aec698 100644 --- a/core/java/android/app/servertransaction/LaunchActivityItem.java +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -45,6 +45,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -135,10 +136,16 @@ public class LaunchActivityItem extends ClientTransactionItem { if (instance == null) { instance = new LaunchActivityItem(); } - setValues(instance, activityToken, intent, ident, info, curConfig, overrideConfig, deviceId, - referrer, voiceInteractor, procState, state, persistentState, pendingResults, - pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken, - activityClientController, shareableActivityToken, + setValues(instance, activityToken, new Intent(intent), ident, new ActivityInfo(info), + new Configuration(curConfig), new Configuration(overrideConfig), deviceId, + referrer, voiceInteractor, procState, + state != null ? new Bundle(state) : null, + persistentState != null ? new PersistableBundle(persistentState) : null, + pendingResults != null ? new ArrayList<>(pendingResults) : null, + pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null, + activityOptions, isForward, + profilerInfo != null ? new ProfilerInfo(profilerInfo) : null, + assistToken, activityClientController, shareableActivityToken, launchedFromBubble, taskFragmentToken); return instance; diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java index e56d3f862b1b..961da19daeed 100644 --- a/core/java/android/app/servertransaction/MoveToDisplayItem.java +++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java @@ -69,7 +69,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem { } instance.setActivityToken(activityToken); instance.mTargetDisplayId = targetDisplayId; - instance.mConfiguration = configuration; + instance.mConfiguration = new Configuration(configuration); return instance; } @@ -78,7 +78,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem { public void recycle() { super.recycle(); mTargetDisplayId = 0; - mConfiguration = Configuration.EMPTY; + mConfiguration = null; ObjectPool.recycle(this); } diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java index 8e995aa05a48..acf2ea429e82 100644 --- a/core/java/android/app/servertransaction/NewIntentItem.java +++ b/core/java/android/app/servertransaction/NewIntentItem.java @@ -32,6 +32,7 @@ import android.os.Trace; import com.android.internal.content.ReferrerIntent; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -71,7 +72,7 @@ public class NewIntentItem extends ActivityTransactionItem { instance = new NewIntentItem(); } instance.setActivityToken(activityToken); - instance.mIntents = intents; + instance.mIntents = new ArrayList<>(intents); instance.mResume = resume; return instance; diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java index 375d1bf57174..cbad92ff3f38 100644 --- a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java +++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java @@ -65,7 +65,7 @@ public class WindowContextInfoChangeItem extends ClientTransactionItem { instance = new WindowContextInfoChangeItem(); } instance.mClientToken = requireNonNull(clientToken); - instance.mInfo = new WindowContextInfo(config, displayId); + instance.mInfo = new WindowContextInfo(new Configuration(config), displayId); return instance; } diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java index 98281338872b..7d3eb8783c04 100644 --- a/core/java/android/app/servertransaction/WindowStateResizeItem.java +++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java @@ -77,10 +77,10 @@ public class WindowStateResizeItem extends ClientTransactionItem { instance = new WindowStateResizeItem(); } instance.mWindow = requireNonNull(window); - instance.mFrames = requireNonNull(frames); + instance.mFrames = new ClientWindowFrames(frames); instance.mReportDraw = reportDraw; - instance.mConfiguration = requireNonNull(configuration); - instance.mInsetsState = requireNonNull(insetsState); + instance.mConfiguration = new MergedConfiguration(configuration); + instance.mInsetsState = new InsetsState(insetsState); instance.mForceLayout = forceLayout; instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; instance.mDisplayId = displayId; diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java index d0b4fe473148..f1ca0864a6dd 100644 --- a/core/java/android/app/wearable/WearableSensingManager.java +++ b/core/java/android/app/wearable/WearableSensingManager.java @@ -35,6 +35,8 @@ import android.os.SharedMemory; import android.service.wearable.WearableSensingService; import android.system.OsConstants; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -105,7 +107,9 @@ public class WearableSensingManager { STATUS_SERVICE_UNAVAILABLE, STATUS_WEARABLE_UNAVAILABLE, STATUS_ACCESS_DENIED - }) public @interface StatusCode {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StatusCode {} private final Context mContext; private final IWearableSensingManager mService; diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 9ea3dfc61ef1..e0ce917fa3b3 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -33,6 +33,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UserHandleAware; +import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -795,9 +796,19 @@ public final class CompanionDeviceManager { @UserHandleAware @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) public @NonNull List<AssociationInfo> getAllAssociations() { + return getAllAssociations(mContext.getUserId()); + } + + /** + * Per-user version of {@link #getAllAssociations()}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) + public @NonNull List<AssociationInfo> getAllAssociations(@UserIdInt int userId) { if (!checkFeaturePresent()) return Collections.emptyList(); try { - return mService.getAllAssociationsForUser(mContext.getUserId()); + return mService.getAllAssociationsForUser(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -830,12 +841,25 @@ public final class CompanionDeviceManager { @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) public void addOnAssociationsChangedListener( @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener) { + addOnAssociationsChangedListener(executor, listener, mContext.getUserId()); + } + + /** + * Per-user version of + * {@link #addOnAssociationsChangedListener(Executor, OnAssociationsChangedListener)}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) + public void addOnAssociationsChangedListener( + @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener, + @UserIdInt int userId) { if (!checkFeaturePresent()) return; synchronized (mListeners) { final OnAssociationsChangedListenerProxy proxy = new OnAssociationsChangedListenerProxy( executor, listener); try { - mService.addOnAssociationsChangedListener(proxy, mContext.getUserId()); + mService.addOnAssociationsChangedListener(proxy, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index b3ea93bb8a85..60965a84f6d2 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -149,6 +149,19 @@ public final class VirtualDeviceManager { @SystemApi public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; + /** + * Persistent device identifier corresponding to the default device. + * + * @see Context#DEVICE_ID_DEFAULT + * @see VirtualDevice#getPersistentDeviceId() + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) + public static final String PERSISTENT_DEVICE_ID_DEFAULT = + "default:" + Context.DEVICE_ID_DEFAULT; + private final IVirtualDeviceManager mService; private final Context mContext; diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index cfab9ebec593..02066fa8a34e 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -58,6 +58,13 @@ flag { } flag { + name: "persistent_device_id_api" + namespace: "virtual_devices" + description: "Enable persistent device ID notification API" + bug: "295258915" +} + +flag { name: "express_metrics" namespace: "virtual_devices" description: "Enable express metrics in VDM" diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 62630c8909d3..d27479299efa 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -163,6 +163,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration GRAMMATICAL_GENDER_FEMININE, GRAMMATICAL_GENDER_MASCULINE, }) + @Retention(RetentionPolicy.SOURCE) public @interface GrammaticalGender {} /** @@ -698,6 +699,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration ORIENTATION_LANDSCAPE, ORIENTATION_SQUARE }) + @Retention(RetentionPolicy.SOURCE) public @interface Orientation { } diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java index 33f602bbd40e..c59d3cea0414 100644 --- a/core/java/android/database/sqlite/SQLiteRawStatement.java +++ b/core/java/android/database/sqlite/SQLiteRawStatement.java @@ -163,7 +163,7 @@ public final class SQLiteRawStatement implements Closeable { * {@link IllegalStateException} if a transaction is not in progress. Clients should call * {@link SQLiteDatabase.createRawStatement} to create a new instance. */ - SQLiteRawStatement(@NonNull SQLiteDatabase db, @NonNull String sql) throws SQLiteException { + SQLiteRawStatement(@NonNull SQLiteDatabase db, @NonNull String sql) { mThread = Thread.currentThread(); mDatabase = db; mSession = mDatabase.getThreadSession(); @@ -245,7 +245,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteDatabaseLockedException if the database is locked or busy. * @throws SQLiteException if a native error occurs. */ - public boolean step() throws SQLiteException { + public boolean step() { throwIfInvalid(); try { int err = nativeStep(mStatement, true); @@ -392,7 +392,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindBlob(int parameterIndex, @NonNull byte[] value) throws SQLiteException { + public void bindBlob(int parameterIndex, @NonNull byte[] value) { Objects.requireNonNull(value); throwIfInvalid(); try { @@ -418,8 +418,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindBlob(int parameterIndex, @NonNull byte[] value, int offset, int length) - throws SQLiteException { + public void bindBlob(int parameterIndex, @NonNull byte[] value, int offset, int length) { Objects.requireNonNull(value); throwIfInvalid(); throwIfInvalidBounds(value.length, offset, length); @@ -442,7 +441,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindDouble(int parameterIndex, double value) throws SQLiteException { + public void bindDouble(int parameterIndex, double value) { throwIfInvalid(); try { nativeBindDouble(mStatement, parameterIndex, value); @@ -462,7 +461,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindInt(int parameterIndex, int value) throws SQLiteException { + public void bindInt(int parameterIndex, int value) { throwIfInvalid(); try { nativeBindInt(mStatement, parameterIndex, value); @@ -482,7 +481,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindLong(int parameterIndex, long value) throws SQLiteException { + public void bindLong(int parameterIndex, long value) { throwIfInvalid(); try { nativeBindLong(mStatement, parameterIndex, value); @@ -502,7 +501,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindNull(int parameterIndex) throws SQLiteException { + public void bindNull(int parameterIndex) { throwIfInvalid(); try { nativeBindNull(mStatement, parameterIndex); @@ -523,7 +522,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindText(int parameterIndex, @NonNull String value) throws SQLiteException { + public void bindText(int parameterIndex, @NonNull String value) { Objects.requireNonNull(value); throwIfInvalid(); try { @@ -562,7 +561,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteException if a native error occurs. */ @SQLiteDataType - public int getColumnType(int columnIndex) throws SQLiteException { + public int getColumnType(int columnIndex) { throwIfInvalid(); try { return nativeColumnType(mStatement, columnIndex); @@ -584,7 +583,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteOutOfMemoryException if the database cannot allocate memory for the name. */ @NonNull - public String getColumnName(int columnIndex) throws SQLiteException { + public String getColumnName(int columnIndex) { throwIfInvalid(); try { return nativeColumnName(mStatement, columnIndex); @@ -609,7 +608,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range. * @throws SQLiteException if a native error occurs. */ - public int getColumnLength(int columnIndex) throws SQLiteException { + public int getColumnLength(int columnIndex) { throwIfInvalid(); try { return nativeColumnBytes(mStatement, columnIndex); @@ -635,7 +634,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteException if a native error occurs. */ @Nullable - public byte[] getColumnBlob(int columnIndex) throws SQLiteException { + public byte[] getColumnBlob(int columnIndex) { throwIfInvalid(); try { return nativeColumnBlob(mStatement, columnIndex); @@ -668,8 +667,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteException if a native error occurs. */ public int readColumnBlob(int columnIndex, @NonNull byte[] buffer, int offset, - int length, int srcOffset) - throws SQLiteException { + int length, int srcOffset) { Objects.requireNonNull(buffer); throwIfInvalid(); throwIfInvalidBounds(buffer.length, offset, length); @@ -695,7 +693,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range. * @throws SQLiteException if a native error occurs. */ - public double getColumnDouble(int columnIndex) throws SQLiteException { + public double getColumnDouble(int columnIndex) { throwIfInvalid(); try { return nativeColumnDouble(mStatement, columnIndex); @@ -719,7 +717,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range. * @throws SQLiteException if a native error occurs. */ - public int getColumnInt(int columnIndex) throws SQLiteException { + public int getColumnInt(int columnIndex) { throwIfInvalid(); try { return nativeColumnInt(mStatement, columnIndex); @@ -743,7 +741,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range. * @throws SQLiteException if a native error occurs. */ - public long getColumnLong(int columnIndex) throws SQLiteException { + public long getColumnLong(int columnIndex) { throwIfInvalid(); try { return nativeColumnLong(mStatement, columnIndex); @@ -768,7 +766,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteException if a native error occurs. */ @NonNull - public String getColumnText(int columnIndex) throws SQLiteException { + public String getColumnText(int columnIndex) { throwIfInvalid(); try { return nativeColumnText(mStatement, columnIndex); diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 90bbca8336e1..f82f79eba8c9 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -131,6 +131,7 @@ public class BiometricManager { BIOMETRIC_CONVENIENCE, DEVICE_CREDENTIAL, }) + @Retention(RetentionPolicy.SOURCE) @interface Types {} /** diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index aca6d0646a9b..5d0697806512 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -3402,8 +3402,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> new Key<Long>("android.sensor.exposureTime", long.class); /** - * <p>Duration from start of frame exposure to - * start of next frame exposure.</p> + * <p>Duration from start of frame readout to + * start of next frame readout.</p> * <p>The maximum frame rate that can be supported by a camera subsystem is * a function of many factors:</p> * <ul> @@ -3464,6 +3464,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to * OFF; otherwise the auto-exposure algorithm will override this value.</p> + * <p><em>Note:</em> Prior to Android 13, this field was described as measuring the duration from + * start of frame exposure to start of next frame exposure, which doesn't reflect the + * definition from sensor manufacturer. A mobile sensor defines the frame duration as + * intervals between sensor readouts.</p> * <p><b>Units</b>: Nanoseconds</p> * <p><b>Range of valid values:</b><br> * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }. diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 1c66f82767e6..0d204f3ececa 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -4103,8 +4103,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Long>("android.sensor.exposureTime", long.class); /** - * <p>Duration from start of frame exposure to - * start of next frame exposure.</p> + * <p>Duration from start of frame readout to + * start of next frame readout.</p> * <p>The maximum frame rate that can be supported by a camera subsystem is * a function of many factors:</p> * <ul> @@ -4165,6 +4165,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to * OFF; otherwise the auto-exposure algorithm will override this value.</p> + * <p><em>Note:</em> Prior to Android 13, this field was described as measuring the duration from + * start of frame exposure to start of next frame exposure, which doesn't reflect the + * definition from sensor manufacturer. A mobile sensor defines the frame duration as + * intervals between sensor readouts.</p> * <p><b>Units</b>: Nanoseconds</p> * <p><b>Range of valid values:</b><br> * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }. diff --git a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java index 8304796f636a..cf496d2ec88e 100644 --- a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java +++ b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java @@ -90,6 +90,22 @@ public class FrameNumberTracker { break; } } + + if (!removeError) { + // Check for the case where we might have an error after a frame number gap + // caused by other types of capture requests + int otherType1 = (requestType + 1) % CaptureRequest.REQUEST_TYPE_COUNT; + int otherType2 = (requestType + 2) % CaptureRequest.REQUEST_TYPE_COUNT; + if (mPendingFrameNumbersWithOtherType[otherType1].isEmpty() && + mPendingFrameNumbersWithOtherType[otherType2].isEmpty()) { + long errorGapNumber = Math.max(mCompletedFrameNumber[otherType1], + mCompletedFrameNumber[otherType2]) + 1; + if ((errorGapNumber > mCompletedFrameNumber[requestType] + 1) && + (errorGapNumber == errorFrameNumber)) { + removeError = true; + } + } + } } if (removeError) { mCompletedFrameNumber[requestType] = errorFrameNumber; diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index b0b7a416c019..440585c845f8 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -159,6 +159,7 @@ public final class HdmiControlManager { RESULT_INCORRECT_MODE, RESULT_COMMUNICATION_FAILED, }) + @Retention(RetentionPolicy.SOURCE) public @interface ControlCallbackResult {} /** Control operation is successfully handled by the framework. */ @@ -1135,6 +1136,7 @@ public final class HdmiControlManager { CEC_SETTING_NAME_QUERY_SAD_MAX, SETTING_NAME_EARC_ENABLED, }) + @Retention(RetentionPolicy.SOURCE) public @interface SettingName {} /** @@ -1157,6 +1159,7 @@ public final class HdmiControlManager { CEC_SETTING_NAME_QUERY_SAD_WMAPRO, CEC_SETTING_NAME_QUERY_SAD_MAX, }) + @Retention(RetentionPolicy.SOURCE) public @interface CecSettingSad {} // True if we have a logical device of type playback hosted in the system. diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 889d3df0941a..81a023451f16 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -52,6 +52,8 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -738,6 +740,7 @@ public class UsbManager { FUNCTION_NCM, FUNCTION_UVC, }) + @Retention(RetentionPolicy.SOURCE) public @interface UsbFunctionMode {} /** @hide */ @@ -748,6 +751,7 @@ public class UsbManager { GADGET_HAL_V1_2, GADGET_HAL_V2_0, }) + @Retention(RetentionPolicy.SOURCE) public @interface UsbGadgetHalVersion {} /** @hide */ @@ -759,6 +763,7 @@ public class UsbManager { USB_HAL_V1_3, USB_HAL_V2_0, }) + @Retention(RetentionPolicy.SOURCE) public @interface UsbHalVersion {} /** diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 0c95c2ec7a7a..f6beec179d57 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -84,4 +84,6 @@ interface INfcAdapter boolean isReaderOptionSupported(); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") boolean enableReaderOption(boolean enable); + boolean isObserveModeSupported(); + boolean setObserveMode(boolean enabled); } diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl index c7b3b2c03f65..191385a3c13d 100644 --- a/core/java/android/nfc/INfcCardEmulation.aidl +++ b/core/java/android/nfc/INfcCardEmulation.aidl @@ -30,6 +30,7 @@ interface INfcCardEmulation boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid); boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category); boolean setDefaultForNextTap(int userHandle, in ComponentName service); + boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable); boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup); boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement); boolean unsetOffHostForService(int userHandle, in ComponentName service); diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index c89759553810..98a980f5e7f8 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -1081,6 +1081,61 @@ public final class NfcAdapter { } } + + /** + * Returns whether the device supports observer mode or not. When observe + * mode is enabled, the NFC hardware will listen for NFC readers, but not + * respond to them. When observe mode is disabled, the NFC hardware will + * resoond to the reader and proceed with the transaction. + * @return true if the mode is supported, false otherwise. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean isObserveModeSupported() { + try { + return sService.isObserveModeSupported(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + + /** + * Disables observe mode to allow the transaction to proceed. See + * {@link #isObserveModeSupported()} for a description of observe mode and + * use {@link #disallowTransaction()} to enable observe mode and block + * transactions again. + * + * @return boolean indicating success or failure. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean allowTransaction() { + try { + return sService.setObserveMode(false); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + + /** + * Signals that the transaction has completed and observe mode may be + * reenabled. See {@link #isObserveModeSupported()} for a description of + * observe mode and use {@link #allowTransaction()} to disable observe + * mode and allow transactions to proceed. + * + * @return boolean indicating success or failure. + */ + + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean disallowTransaction() { + try { + return sService.setObserveMode(true); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + /** * Resumes default polling for the current device state if polling is paused. Calling * this while polling is not paused is a no-op. diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index d048b595ad1e..58b6179691e9 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -328,6 +328,24 @@ public final class CardEmulation { return SELECTION_MODE_ASK_IF_CONFLICT; } } + /** + * Sets whether the system should default to observe mode or not when + * the service is in the foreground or the default payment service. + * + * @param service The component name of the service + * @param enable Whether the servic should default to observe mode or not + * @return whether the change was successful. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean setServiceObserveModeDefault(@NonNull ComponentName service, boolean enable) { + try { + return sService.setServiceObserveModeDefault(mContext.getUser().getIdentifier(), + service, enable); + } catch (RemoteException e) { + Log.e(TAG, "Failed to reach CardEmulationService."); + } + return false; + } /** * Registers a list of AIDs for a specific category for the diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java index 55d0e73780a2..7cd2533a7dbf 100644 --- a/core/java/android/nfc/cardemulation/HostApduService.java +++ b/core/java/android/nfc/cardemulation/HostApduService.java @@ -16,11 +16,14 @@ package android.nfc.cardemulation; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; +import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -29,6 +32,9 @@ import android.os.Messenger; import android.os.RemoteException; import android.util.Log; +import java.util.ArrayList; +import java.util.List; + /** * <p>HostApduService is a convenience {@link Service} class that can be * extended to emulate an NFC card inside an Android @@ -230,9 +236,99 @@ public abstract class HostApduService extends Service { /** * @hide */ + public static final int MSG_POLLING_LOOP = 4; + + /** + * @hide + */ public static final String KEY_DATA = "data"; /** + * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of + * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE"; + + /** + * POLLING_LOOP_TYPE_A is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-A. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_A = 'A'; + + /** + * POLLING_LOOP_TYPE_B is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-B. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_B = 'B'; + + /** + * POLLING_LOOP_TYPE_F is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-F. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_F = 'F'; + + /** + * POLLING_LOOP_TYPE_ON is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop turns on. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_ON = 'O'; + + /** + * POLLING_LOOP_TYPE_OFF is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop turns off. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_OFF = 'X'; + + /** + * POLLING_LOOP_TYPE_UNKNOWN is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop frame isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U'; + + /** + * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA"; + + /** + * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN"; + + /** + * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP"; + + /** + * @hide + */ + public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY = + "android.nfc.cardemulation.POLLING_FRAMES"; + + /** * Messenger interface to NfcService for sending responses. * Only accessed on main thread by the message handler. * @@ -255,6 +351,7 @@ public abstract class HostApduService extends Service { byte[] apdu = dataBundle.getByteArray(KEY_DATA); if (apdu != null) { + HostApduService has = HostApduService.this; byte[] responseApdu = processCommandApdu(apdu, null); if (responseApdu != null) { if (mNfcService == null) { @@ -306,6 +403,12 @@ public abstract class HostApduService extends Service { Log.e(TAG, "RemoteException calling into NfcService."); } break; + case MSG_POLLING_LOOP: + ArrayList<Bundle> frames = + msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY, + Bundle.class); + processPollingFrames(frames); + break; default: super.handleMessage(msg); } @@ -366,6 +469,21 @@ public abstract class HostApduService extends Service { } } + /** + * This method is called when a polling frame has been received from a + * remote device. If the device is in observe mode, the service should + * call {@link NfcAdapter#allowTransaction()} once it is ready to proceed + * with the transaction. If the device is not in observe mode, the service + * can use this polling frame information to determine how to proceed if it + * subsequently has {@link #processCommandApdu(byte[], Bundle)} called. The + * service must override this method inorder to receive polling frames, + * otherwise the base implementation drops the frame. + * + * @param frame A description of the polling frame. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public void processPollingFrames(@NonNull List<Bundle> frame) { + } /** * <p>This method will be called when a command APDU has been received diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig index cd50ace036de..17e042761dbe 100644 --- a/core/java/android/nfc/flags.aconfig +++ b/core/java/android/nfc/flags.aconfig @@ -20,3 +20,31 @@ flag { description: "Flag for NFC user restriction" bug: "291187960" } + +flag { + name: "nfc_observe_mode" + namespace: "nfc" + description: "Enable NFC Observe Mode" + bug: "294217286" +} + +flag { + name: "nfc_read_polling_loop" + namespace: "nfc" + description: "Enable NFC Polling Loop Notifications" + bug: "294217286" +} + +flag { + name: "nfc_observe_mode_st_shim" + namespace: "nfc" + description: "Enable NFC Observe Mode ST shim" + bug: "294217286" +} + +flag { + name: "nfc_read_polling_loop_st_shim" + namespace: "nfc" + description: "Enable NFC Polling Loop Notifications ST shim" + bug: "294217286" +} diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 995622004fa6..0ccc485a34f4 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -30,9 +30,11 @@ import com.android.internal.os.BinderCallHeavyHitterWatcher; import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener; import com.android.internal.os.BinderInternal; import com.android.internal.os.BinderInternal.CallSession; +import com.android.internal.os.SomeArgs; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.FunctionalUtils.ThrowingSupplier; +import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.CriticalNative; @@ -46,6 +48,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Modifier; import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.function.Supplier; /** * Base class for a remotable object, the core part of a lightweight @@ -289,6 +292,33 @@ public class Binder implements IBinder { sWarnOnBlockingOnCurrentThread.set(sWarnOnBlocking); } + private static ThreadLocal<SomeArgs> sIdentity$ravenwood; + + @android.ravenwood.annotation.RavenwoodKeepWholeClass + private static class IdentitySupplier implements Supplier<SomeArgs> { + @Override + public SomeArgs get() { + final SomeArgs args = SomeArgs.obtain(); + // Match IPCThreadState behavior + args.arg1 = Boolean.FALSE; + args.argi1 = android.os.Process.myUid(); + args.argi2 = android.os.Process.myPid(); + return args; + } + } + + /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep + public static void init$ravenwood() { + sIdentity$ravenwood = ThreadLocal.withInitial(new IdentitySupplier()); + } + + /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep + public static void reset$ravenwood() { + sIdentity$ravenwood = null; + } + /** * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null. */ @@ -312,8 +342,14 @@ public class Binder implements IBinder { * Warning: oneway transactions do not receive PID. */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static final native int getCallingPid(); + /** @hide */ + public static final int getCallingPid$ravenwood() { + return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2; + } + /** * Return the Linux UID assigned to the process that sent you the * current transaction that is being processed. This UID can be used with @@ -322,8 +358,14 @@ public class Binder implements IBinder { * incoming transaction, then its own UID is returned. */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static final native int getCallingUid(); + /** @hide */ + public static final int getCallingUid$ravenwood() { + return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1; + } + /** * Returns {@code true} if the current thread is currently executing an * incoming transaction. @@ -331,6 +373,7 @@ public class Binder implements IBinder { * @hide */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static final native boolean isDirectlyHandlingTransactionNative(); /** @hide */ @@ -344,6 +387,7 @@ public class Binder implements IBinder { /** * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static final boolean isDirectlyHandlingTransaction() { return sIsHandlingBinderTransaction || isDirectlyHandlingTransactionNative(); } @@ -363,8 +407,15 @@ public class Binder implements IBinder { * @hide */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace private static native boolean hasExplicitIdentity(); + /** @hide */ + private static boolean hasExplicitIdentity$ravenwood() { + return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().arg1 + == Boolean.TRUE; + } + /** * Return the Linux UID assigned to the process that sent the transaction * currently being processed. @@ -373,6 +424,7 @@ public class Binder implements IBinder { * executing an incoming transaction and the calling identity has not been * explicitly set with {@link #clearCallingIdentity()} */ + @android.ravenwood.annotation.RavenwoodKeep public static final int getCallingUidOrThrow() { if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) { throw new IllegalStateException( @@ -434,8 +486,26 @@ public class Binder implements IBinder { * @see #restoreCallingIdentity(long) */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static final native long clearCallingIdentity(); + /** @hide */ + public static final long clearCallingIdentity$ravenwood() { + final SomeArgs args = Preconditions.requireNonNullViaRavenwoodRule( + sIdentity$ravenwood).get(); + long res = ((long) args.argi1 << 32) | args.argi2; + if (args.arg1 == Boolean.TRUE) { + res |= (0x1 << 30); + } else { + res &= ~(0x1 << 30); + } + // Match IPCThreadState behavior + args.arg1 = Boolean.TRUE; + args.argi1 = android.os.Process.myUid(); + args.argi2 = android.os.Process.myPid(); + return res; + } + /** * Restore the identity of the incoming IPC on the current thread * back to a previously identity that was returned by {@link @@ -447,8 +517,18 @@ public class Binder implements IBinder { * @see #clearCallingIdentity */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static final native void restoreCallingIdentity(long token); + /** @hide */ + public static final void restoreCallingIdentity$ravenwood(long token) { + final SomeArgs args = Preconditions.requireNonNullViaRavenwoodRule( + sIdentity$ravenwood).get(); + args.arg1 = ((token & (0x1 << 30)) != 0) ? Boolean.TRUE : Boolean.FALSE; + args.argi1 = (int) (token >> 32); + args.argi2 = (int) (token & ~(0x1 << 30)); + } + /** * Convenience method for running the provided action enclosed in * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}. @@ -644,8 +724,14 @@ public class Binder implements IBinder { * in order to prevent the process from holding on to objects longer than * it needs to. */ + @android.ravenwood.annotation.RavenwoodReplace public static final native void flushPendingCommands(); + /** @hide */ + public static final void flushPendingCommands$ravenwood() { + // Ravenwood doesn't support IPC; ignored + } + /** * Add the calling thread to the IPC thread pool. This function does * not return until the current process is exiting. @@ -703,6 +789,7 @@ public class Binder implements IBinder { * <p>If you're creating a Binder token (a Binder object without an attached interface), * you should use {@link #Binder(String)} instead. */ + @android.ravenwood.annotation.RavenwoodKeep public Binder() { this(null); } @@ -719,6 +806,7 @@ public class Binder implements IBinder { * Instead of creating multiple tokens with the same descriptor, consider adding a suffix to * help identify them. */ + @android.ravenwood.annotation.RavenwoodKeep public Binder(@Nullable String descriptor) { mObject = getNativeBBinderHolder(); if (mObject != 0L) { @@ -742,6 +830,7 @@ public class Binder implements IBinder { * will be implemented for you to return the given owner IInterface when * the corresponding descriptor is requested. */ + @android.ravenwood.annotation.RavenwoodKeep public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) { mOwner = owner; mDescriptor = descriptor; @@ -750,6 +839,7 @@ public class Binder implements IBinder { /** * Default implementation returns an empty interface name. */ + @android.ravenwood.annotation.RavenwoodKeep public @Nullable String getInterfaceDescriptor() { return mDescriptor; } @@ -758,6 +848,7 @@ public class Binder implements IBinder { * Default implementation always returns true -- if you got here, * the object is alive. */ + @android.ravenwood.annotation.RavenwoodKeep public boolean pingBinder() { return true; } @@ -768,6 +859,7 @@ public class Binder implements IBinder { * Note that if you're calling on a local binder, this always returns true * because your process is alive if you're calling it. */ + @android.ravenwood.annotation.RavenwoodKeep public boolean isBinderAlive() { return true; } @@ -777,6 +869,7 @@ public class Binder implements IBinder { * to return the associated {@link IInterface} if it matches the requested * descriptor. */ + @android.ravenwood.annotation.RavenwoodKeep public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) { if (mDescriptor != null && mDescriptor.equals(descriptor)) { return mOwner; @@ -1250,12 +1343,14 @@ public class Binder implements IBinder { /** * Local implementation is a no-op. */ + @android.ravenwood.annotation.RavenwoodKeep public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { } /** * Local implementation is a no-op. */ + @android.ravenwood.annotation.RavenwoodKeep public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) { return true; } @@ -1283,6 +1378,7 @@ public class Binder implements IBinder { } } + @android.ravenwood.annotation.RavenwoodReplace private static native long getNativeBBinderHolder(); private static long getNativeBBinderHolder$ravenwood() { diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index 90e4b17250d8..91c2965c2505 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -194,6 +194,7 @@ public interface IBinder { * Limit that should be placed on IPC sizes, in bytes, to keep them safely under the transaction * buffer limit. */ + @android.ravenwood.annotation.RavenwoodKeep static int getSuggestedMaxIpcSizeBytes() { return MAX_IPC_SIZE; } diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl index f30dd20d7087..0f2756914fa6 100644 --- a/core/java/android/os/IVibratorManagerService.aidl +++ b/core/java/android/os/IVibratorManagerService.aidl @@ -33,13 +33,13 @@ interface IVibratorManagerService { boolean unregisterVibratorStateListener(int vibratorId, in IVibratorStateListener listener); boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in CombinedVibration vibration, in VibrationAttributes attributes); - void vibrate(int uid, int displayId, String opPkg, in CombinedVibration vibration, + void vibrate(int uid, int deviceId, String opPkg, in CombinedVibration vibration, in VibrationAttributes attributes, String reason, IBinder token); void cancelVibrate(int usageFilter, IBinder token); // Async oneway APIs. // There is no order guarantee with respect to the two-way APIs above like // vibrate/isVibrating/cancel. - oneway void performHapticFeedback(int uid, int displayId, String opPkg, int constant, + oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant, boolean always, String reason, IBinder token); } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 677143afd4fb..daec1721977b 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -34,6 +34,9 @@ import android.system.StructPollfd; import android.util.Pair; import android.webkit.WebViewZygote; +import com.android.internal.os.SomeArgs; +import com.android.internal.util.Preconditions; + import dalvik.system.VMRuntime; import libcore.io.IoUtils; @@ -833,14 +836,37 @@ public class Process { return VMRuntime.getRuntime().is64Bit(); } + private static SomeArgs sIdentity$ravenwood; + + /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep + public static void init$ravenwood(int uid, int pid) { + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = uid; + args.argi2 = pid; + sIdentity$ravenwood = args; + } + + /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep + public static void reset$ravenwood() { + sIdentity$ravenwood = null; + } + /** * Returns the identifier of this process, which can be used with * {@link #killProcess} and {@link #sendSignal}. */ + @android.ravenwood.annotation.RavenwoodReplace public static final int myPid() { return Os.getpid(); } + /** @hide */ + public static final int myPid$ravenwood() { + return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).argi2; + } + /** * Returns the identifier of this process' parent. * @hide @@ -864,10 +890,16 @@ public class Process { * app-specific sandbox. It is different from {@link #myUserHandle} in that * a uid identifies a specific app sandbox in a specific user. */ + @android.ravenwood.annotation.RavenwoodReplace public static final int myUid() { return Os.getuid(); } + /** @hide */ + public static final int myUid$ravenwood() { + return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).argi1; + } + /** * Returns this process's user handle. This is the * user the process is running under. It is distinct from diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index d4688f8794a4..f71c2695c677 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -57,6 +57,8 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.security.GeneralSecurityException; import java.security.PublicKey; import java.security.SignatureException; @@ -166,6 +168,7 @@ public class RecoverySystem { RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED, RESUME_ON_REBOOT_REBOOT_ERROR_SLOT_MISMATCH, RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE}) + @Retention(RetentionPolicy.SOURCE) public @interface ResumeOnRebootRebootErrorCode {} /** diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index 831ca86504af..49a0bd3289aa 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -24,6 +24,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.location.ILocationManager; import android.location.LocationTime; +import android.text.format.DateUtils; import android.util.Slog; import dalvik.annotation.optimization.CriticalNative; @@ -125,6 +126,7 @@ public final class SystemClock { * * @param ms to sleep before returning, in milliseconds of uptime. */ + @android.ravenwood.annotation.RavenwoodKeep public static void sleep(long ms) { long start = uptimeMillis(); @@ -186,8 +188,16 @@ public final class SystemClock { * @return milliseconds of non-sleep uptime since boot. */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace native public static long uptimeMillis(); + /** @hide */ + public static long uptimeMillis$ravenwood() { + // Ravenwood booted in Jan 2023, and has been in deep sleep for one week + return System.currentTimeMillis() - (1672556400L * 1_000) + - (DateUtils.WEEK_IN_MILLIS * 1_000); + } + /** * Returns nanoseconds since boot, not counting time spent in deep sleep. * @@ -195,8 +205,16 @@ public final class SystemClock { * @hide */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static native long uptimeNanos(); + /** @hide */ + public static long uptimeNanos$ravenwood() { + // Ravenwood booted in Jan 2023, and has been in deep sleep for one week + return System.nanoTime() - (1672556400L * 1_000_000_000) + - (DateUtils.WEEK_IN_MILLIS * 1_000_000_000); + } + /** * Return {@link Clock} that starts at system boot, not counting time spent * in deep sleep. @@ -218,8 +236,15 @@ public final class SystemClock { * @return elapsed milliseconds since boot. */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace native public static long elapsedRealtime(); + /** @hide */ + public static long elapsedRealtime$ravenwood() { + // Ravenwood booted in Jan 2023, and has been in deep sleep for one week + return System.currentTimeMillis() - (1672556400L * 1_000); + } + /** * Return {@link Clock} that starts at system boot, including time spent in * sleep. @@ -241,8 +266,15 @@ public final class SystemClock { * @return elapsed nanoseconds since boot. */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static native long elapsedRealtimeNanos(); + /** @hide */ + public static long elapsedRealtimeNanos$ravenwood() { + // Ravenwood booted in Jan 2023, and has been in deep sleep for one week + return System.nanoTime() - (1672556400L * 1_000_000_000); + } + /** * Returns milliseconds running in the current thread. * @@ -271,8 +303,15 @@ public final class SystemClock { */ @UnsupportedAppUsage @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static native long currentTimeMicro(); + /** @hide */ + public static long currentTimeMicro$ravenwood() { + // Ravenwood booted in Jan 2023, and has been in deep sleep for one week + return System.nanoTime() / 1000L; + } + /** * Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized * using a remote network source outside the device. diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java index ee90834c15ef..bc85412e851b 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -137,8 +137,8 @@ public class SystemVibratorManager extends VibratorManager { return; } try { - mService.vibrate(uid, mContext.getAssociatedDisplayId(), opPkg, effect, attributes, - reason, mToken); + mService.vibrate(uid, mContext.getDeviceId(), opPkg, effect, attributes, reason, + mToken); } catch (RemoteException e) { Log.w(TAG, "Failed to vibrate.", e); } @@ -152,8 +152,8 @@ public class SystemVibratorManager extends VibratorManager { } try { mService.performHapticFeedback( - Process.myUid(), mContext.getAssociatedDisplayId(), mPackageName, constant, - always, reason, mToken); + Process.myUid(), mContext.getDeviceId(), mPackageName, constant, always, reason, + mToken); } catch (RemoteException e) { Log.w(TAG, "Failed to perform haptic feedback.", e); } diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java index b7e3068a437c..0a8f62fd56d8 100644 --- a/core/java/android/os/UpdateEngine.java +++ b/core/java/android/os/UpdateEngine.java @@ -25,6 +25,9 @@ import android.os.IUpdateEngine; import android.os.IUpdateEngineCallback; import android.os.RemoteException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * UpdateEngine handles calls to the update engine which takes care of A/B OTA * updates. It wraps up the update engine Binder APIs and exposes them as @@ -178,6 +181,7 @@ public class UpdateEngine { ErrorCodeConstants.NOT_ENOUGH_SPACE, ErrorCodeConstants.DEVICE_CORRUPTED, }) + @Retention(RetentionPolicy.SOURCE) public @interface ErrorCode {} /** diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 2d1802ae85e5..6853892348d9 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2133,6 +2133,7 @@ public class StorageManager { MOUNT_MODE_EXTERNAL_PASS_THROUGH, MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE }) + @Retention(RetentionPolicy.SOURCE) /** @hide */ public @interface MountMode {} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 7369740bc43c..fb2ac7112b0a 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -44,6 +44,13 @@ flag { } flag { + name: "enhanced_confirmation_mode_apis" + namespace: "permissions" + description: "enable enhanced confirmation mode apis" + bug: "310220212" +} + +flag { name: "op_enable_mobile_data_by_user" namespace: "permissions" description: "enables logging of the OP_ENABLE_MOBILE_DATA_BY_USER" diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c012ff34bfab..4e7734c5471d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -19039,6 +19039,14 @@ public final class Settings { public static final int BATTERY_SAVER_MODE_CUSTOM = 4; /** + Whether 1P apps vote for enabling data during different modes, + i.e. BTM, BBSM + * @hide + */ + @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final String CONNECTIVITY_KEEP_DATA_ON = "wear_connectivity_keep_data_on"; + + /** * The maximum ambient mode duration when an activity is allowed to auto resume. * @hide */ diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index bcda25a1bf3b..72c436ee6d41 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -17,6 +17,7 @@ package android.provider; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -49,6 +50,7 @@ import android.text.TextUtils; import android.util.Patterns; import com.android.internal.telephony.SmsApplication; +import com.android.internal.telephony.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -3191,8 +3193,8 @@ public final class Telephony { * Sets whether the PDU session brought up by this APN should always be on. * See 3GPP TS 23.501 section 5.6.13 * <P>Type: INTEGER</P> - * @hide */ + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String ALWAYS_ON = "always_on"; /** @@ -3302,18 +3304,16 @@ public final class Telephony { * The MTU (maximum transmit unit) size of the mobile interface for IPv4 to which the APN is * connected, in bytes. * <p>Type: INTEGER </p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String MTU_V4 = "mtu_v4"; /** * The MTU (maximum transmit unit) size of the mobile interface for IPv6 to which the APN is * connected, in bytes. * <p>Type: INTEGER </p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String MTU_V6 = "mtu_v6"; /** @@ -3335,17 +3335,15 @@ public final class Telephony { /** * {@code true} if this APN visible to the user, {@code false} otherwise. * <p>Type: INTEGER (boolean)</p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String USER_VISIBLE = "user_visible"; /** * {@code true} if the user allowed to edit this APN, {@code false} otherwise. * <p>Type: INTEGER (boolean)</p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String USER_EDITABLE = "user_editable"; /** diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 759953e4aca4..92c516c38dcc 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -309,6 +309,7 @@ public abstract class NotificationListenerService extends Service { REASON_ASSISTANT_CANCEL, REASON_LOCKDOWN, }) + @Retention(RetentionPolicy.SOURCE) public @interface NotificationCancelReason{}; /** @@ -320,6 +321,7 @@ public abstract class NotificationListenerService extends Service { FLAG_FILTER_TYPE_SILENT, FLAG_FILTER_TYPE_ONGOING }) + @Retention(RetentionPolicy.SOURCE) public @interface NotificationFilterTypes {} /** * A flag value indicating that this notification listener can see conversation type diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java new file mode 100644 index 000000000000..5b096c641f78 --- /dev/null +++ b/core/java/android/service/notification/ZenDeviceEffects.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.app.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Objects; + +/** + * Represents the set of device effects (affecting display and device behavior in general) that + * are applied whenever an {@link android.app.AutomaticZenRule} is active. + */ +@FlaggedApi(Flags.FLAG_MODES_API) +public final class ZenDeviceEffects implements Parcelable { + + private final boolean mGrayscale; + private final boolean mSuppressAmbientDisplay; + private final boolean mDimWallpaper; + private final boolean mNightMode; + + private final boolean mDisableAutoBrightness; + private final boolean mDisableTapToWake; + private final boolean mDisableTiltToWake; + private final boolean mDisableTouch; + private final boolean mMinimizeRadioUsage; + private final boolean mMaximizeDoze; + + private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay, + boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness, + boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch, + boolean minimizeRadioUsage, boolean maximizeDoze) { + mGrayscale = grayscale; + mSuppressAmbientDisplay = suppressAmbientDisplay; + mDimWallpaper = dimWallpaper; + mNightMode = nightMode; + mDisableAutoBrightness = disableAutoBrightness; + mDisableTapToWake = disableTapToWake; + mDisableTiltToWake = disableTiltToWake; + mDisableTouch = disableTouch; + mMinimizeRadioUsage = minimizeRadioUsage; + mMaximizeDoze = maximizeDoze; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof final ZenDeviceEffects that)) return false; + if (obj == this) return true; + + return this.mGrayscale == that.mGrayscale + && this.mSuppressAmbientDisplay == that.mSuppressAmbientDisplay + && this.mDimWallpaper == that.mDimWallpaper + && this.mNightMode == that.mNightMode + && this.mDisableAutoBrightness == that.mDisableAutoBrightness + && this.mDisableTapToWake == that.mDisableTapToWake + && this.mDisableTiltToWake == that.mDisableTiltToWake + && this.mDisableTouch == that.mDisableTouch + && this.mMinimizeRadioUsage == that.mMinimizeRadioUsage + && this.mMaximizeDoze == that.mMaximizeDoze; + } + + @Override + public int hashCode() { + return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode, + mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch, + mMinimizeRadioUsage, mMaximizeDoze); + } + + @Override + public String toString() { + ArrayList<String> effects = new ArrayList<>(10); + if (mGrayscale) effects.add("grayscale"); + if (mSuppressAmbientDisplay) effects.add("suppressAmbientDisplay"); + if (mDimWallpaper) effects.add("dimWallpaper"); + if (mNightMode) effects.add("nightMode"); + if (mDisableAutoBrightness) effects.add("disableAutoBrightness"); + if (mDisableTapToWake) effects.add("disableTapToWake"); + if (mDisableTiltToWake) effects.add("disableTiltToWake"); + if (mDisableTouch) effects.add("disableTouch"); + if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage"); + if (mMaximizeDoze) effects.add("maximizeDoze"); + return "[" + String.join(", ", effects) + "]"; + } + + /** + * Whether the level of color saturation of the display should be set to minimum, effectively + * switching it to grayscale, while the rule is active. + */ + public boolean shouldDisplayGrayscale() { + return mGrayscale; + } + + /** + * Whether the ambient (always-on) display feature should be disabled while the rule is active. + * This will have no effect if the device doesn't support always-on display or if it's not + * generally enabled. + */ + public boolean shouldSuppressAmbientDisplay() { + return mSuppressAmbientDisplay; + } + + /** Whether the wallpaper should be dimmed while the rule is active. */ + public boolean shouldDimWallpaper() { + return mDimWallpaper; + } + + /** Whether night mode (aka dark theme) should be applied while the rule is active. */ + public boolean shouldUseNightMode() { + return mNightMode; + } + + /** + * Whether the display's automatic brightness adjustment should be disabled while the rule is + * active. + * @hide + */ + public boolean shouldDisableAutoBrightness() { + return mDisableAutoBrightness; + } + + /** + * Whether "tap to wake" should be disabled while the rule is active. + * @hide + */ + public boolean shouldDisableTapToWake() { + return mDisableTapToWake; + } + + /** + * Whether "tilt to wake" should be disabled while the rule is active. + * @hide + */ + public boolean shouldDisableTiltToWake() { + return mDisableTiltToWake; + } + + /** + * Whether touch interactions should be disabled while the rule is active. + * @hide + */ + public boolean shouldDisableTouch() { + return mDisableTouch; + } + + /** + * Whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption, should be + * minimized while the rule is active. + * @hide + */ + public boolean shouldMinimizeRadioUsage() { + return mMinimizeRadioUsage; + } + + /** + * Whether Doze should be enhanced (e.g. with more aggresive activation, or less frequent + * maintenance windows) while the rule is active. + * @hide + */ + public boolean shouldMaximizeDoze() { + return mMaximizeDoze; + } + + /** {@link Parcelable.Creator} that instantiates {@link ZenDeviceEffects} objects. */ + @NonNull + public static final Creator<ZenDeviceEffects> CREATOR = new Creator<ZenDeviceEffects>() { + @Override + public ZenDeviceEffects createFromParcel(Parcel in) { + return new ZenDeviceEffects(in.readBoolean(), in.readBoolean(), in.readBoolean(), + in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), + in.readBoolean(), in.readBoolean(), in.readBoolean()); + } + + @Override + public ZenDeviceEffects[] newArray(int size) { + return new ZenDeviceEffects[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBoolean(mGrayscale); + dest.writeBoolean(mSuppressAmbientDisplay); + dest.writeBoolean(mDimWallpaper); + dest.writeBoolean(mNightMode); + dest.writeBoolean(mDisableAutoBrightness); + dest.writeBoolean(mDisableTapToWake); + dest.writeBoolean(mDisableTiltToWake); + dest.writeBoolean(mDisableTouch); + dest.writeBoolean(mMinimizeRadioUsage); + dest.writeBoolean(mMaximizeDoze); + } + + /** Builder class for {@link ZenDeviceEffects} objects. */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final class Builder { + + private boolean mGrayscale; + private boolean mSuppressAmbientDisplay; + private boolean mDimWallpaper; + private boolean mNightMode; + private boolean mDisableAutoBrightness; + private boolean mDisableTapToWake; + private boolean mDisableTiltToWake; + private boolean mDisableTouch; + private boolean mMinimizeRadioUsage; + private boolean mMaximizeDoze; + + /** + * Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled). + */ + public Builder() { + } + + /** + * Instantiates a new {@link ZenPolicy.Builder} with all effects set to their corresponding + * values in the supplied {@link ZenDeviceEffects}. + */ + public Builder(@NonNull ZenDeviceEffects zenDeviceEffects) { + mGrayscale = zenDeviceEffects.shouldDisplayGrayscale(); + mSuppressAmbientDisplay = zenDeviceEffects.shouldSuppressAmbientDisplay(); + mDimWallpaper = zenDeviceEffects.shouldDimWallpaper(); + mNightMode = zenDeviceEffects.shouldUseNightMode(); + mDisableAutoBrightness = zenDeviceEffects.shouldDisableAutoBrightness(); + mDisableTapToWake = zenDeviceEffects.shouldDisableTapToWake(); + mDisableTiltToWake = zenDeviceEffects.shouldDisableTiltToWake(); + mDisableTouch = zenDeviceEffects.shouldDisableTouch(); + mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage(); + mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze(); + } + + /** + * Sets whether the level of color saturation of the display should be set to minimum, + * effectively switching it to grayscale, while the rule is active. + */ + @NonNull + public Builder setShouldDisplayGrayscale(boolean grayscale) { + mGrayscale = grayscale; + return this; + } + + /** + * Sets whether the ambient (always-on) display feature should be disabled while the rule + * is active. This will have no effect if the device doesn't support always-on display or if + * it's not generally enabled. + */ + @NonNull + public Builder setShouldSuppressAmbientDisplay(boolean suppressAmbientDisplay) { + mSuppressAmbientDisplay = suppressAmbientDisplay; + return this; + } + + /** Sets whether the wallpaper should be dimmed while the rule is active. */ + @NonNull + public Builder setShouldDimWallpaper(boolean dimWallpaper) { + mDimWallpaper = dimWallpaper; + return this; + } + + /** Sets whether night mode (aka dark theme) should be applied while the rule is active. */ + @NonNull + public Builder setShouldUseNightMode(boolean nightMode) { + mNightMode = nightMode; + return this; + } + + /** + * Sets whether the display's automatic brightness adjustment should be disabled while the + * rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableAutoBrightness(boolean disableAutoBrightness) { + mDisableAutoBrightness = disableAutoBrightness; + return this; + } + + /** + * Sets whether "tap to wake" should be disabled while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableTapToWake(boolean disableTapToWake) { + mDisableTapToWake = disableTapToWake; + return this; + } + + /** + * Sets whether "tilt to wake" should be disabled while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableTiltToWake(boolean disableTiltToWake) { + mDisableTiltToWake = disableTiltToWake; + return this; + } + + /** + * Sets whether touch interactions should be disabled while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableTouch(boolean disableTouch) { + mDisableTouch = disableTouch; + return this; + } + + /** + * Sets whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption, + * should be minimized while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldMinimizeRadioUsage(boolean minimizeRadioUsage) { + mMinimizeRadioUsage = minimizeRadioUsage; + return this; + } + + /** + * Sets whether Doze should be enhanced (e.g. with more aggresive activation, or less + * frequent maintenance windows) while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldMaximizeDoze(boolean maximizeDoze) { + mMaximizeDoze = maximizeDoze; + 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); + } + } +} diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index ff4dfc7cc079..305b751f0cef 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -1004,6 +1004,8 @@ public class ZenModeConfig implements Parcelable { priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS; conversationSenders = getConversationSendersWithDefault( zenPolicy.getPriorityConversationSenders(), conversationSenders); + } else { + conversationSenders = CONVERSATION_SENDERS_NONE; } if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS, @@ -1102,7 +1104,7 @@ public class ZenModeConfig implements Parcelable { return (policy.suppressedVisualEffects & visualEffect) == 0; } - private int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders, + private static int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders, int defaultPolicySender) { switch (senders) { case ZenPolicy.PEOPLE_TYPE_ANYONE: @@ -1116,7 +1118,7 @@ public class ZenModeConfig implements Parcelable { } } - private int getConversationSendersWithDefault(@ZenPolicy.ConversationSenders int senders, + private static int getConversationSendersWithDefault(@ZenPolicy.ConversationSenders int senders, int defaultPolicySender) { switch (senders) { case ZenPolicy.CONVERSATION_SENDERS_ANYONE: diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java index ff6dffdaaf5d..1e08fd8061e0 100644 --- a/core/java/android/service/voice/HotwordDetectedResult.java +++ b/core/java/android/service/voice/HotwordDetectedResult.java @@ -85,6 +85,7 @@ public final class HotwordDetectedResult implements Parcelable { CONFIDENCE_LEVEL_HIGH, CONFIDENCE_LEVEL_VERY_HIGH }) + @Retention(RetentionPolicy.SOURCE) @interface HotwordConfidenceLevelValue { } diff --git a/core/java/android/service/voice/HotwordRejectedResult.java b/core/java/android/service/voice/HotwordRejectedResult.java index 7b3f47d04e48..26c1ca428c3b 100644 --- a/core/java/android/service/voice/HotwordRejectedResult.java +++ b/core/java/android/service/voice/HotwordRejectedResult.java @@ -22,6 +22,9 @@ import android.os.Parcelable; import com.android.internal.util.DataClass; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Represents a result supporting the rejected hotword trigger. * @@ -57,6 +60,7 @@ public final class HotwordRejectedResult implements Parcelable { CONFIDENCE_LEVEL_MEDIUM, CONFIDENCE_LEVEL_HIGH }) + @Retention(RetentionPolicy.SOURCE) @interface HotwordConfidenceLevelValue { } diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 35834fd3886f..f1c1420de9c7 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -38,6 +38,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; +import android.util.CloseGuard; import android.util.Log; import android.util.Slog; @@ -46,6 +47,7 @@ import com.android.internal.R; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.Reference; import java.util.List; import java.util.Objects; import java.util.Queue; @@ -59,6 +61,9 @@ import java.util.concurrent.LinkedBlockingQueue; * {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)}. This class's methods must be * invoked only from the main application thread. * + * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a + * SpeechRecognizer object when it is no longer needed. + * * <p>The implementation of this API is likely to stream audio to remote servers to perform speech * recognition. As such this API is not intended to be used for continuous recognition, which would * consume a significant amount of battery and bandwidth. @@ -301,6 +306,8 @@ public class SpeechRecognizer { /** The actual RecognitionService endpoint */ private IRecognitionService mService; + private final CloseGuard mCloseGuard = new CloseGuard(); + /** Context with which the manager was created */ private final Context mContext; @@ -366,9 +373,7 @@ public class SpeechRecognizer { * {@link #createSpeechRecognizer} static factory method */ private SpeechRecognizer(final Context context, final ComponentName serviceComponent) { - mContext = context; - mServiceComponent = serviceComponent; - mOnDevice = false; + this(context, serviceComponent, false); } /** @@ -376,9 +381,17 @@ public class SpeechRecognizer { * {@link #createOnDeviceSpeechRecognizer} static factory method */ private SpeechRecognizer(final Context context, boolean onDevice) { + this(context, null, onDevice); + } + + private SpeechRecognizer( + final Context context, + final ComponentName serviceComponent, + final boolean onDevice) { mContext = context; - mServiceComponent = null; + mServiceComponent = serviceComponent; mOnDevice = onDevice; + mCloseGuard.open("SpeechRecognizer#destroy()"); } /** @@ -418,6 +431,9 @@ public class SpeechRecognizer { * command to the created {@code SpeechRecognizer}, otherwise no notifications will be * received. * + * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a + * SpeechRecognizer object when it is no longer needed. + * * <p>For apps targeting Android 11 (API level 30) interaction with a speech recognition * service requires <queries> element to be added to the manifest file: * <pre>{@code @@ -445,6 +461,9 @@ public class SpeechRecognizer { * Use this version of the method to specify a specific service to direct this * {@link SpeechRecognizer} to. * + * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a + * SpeechRecognizer object when it is no longer needed. + * * <p><strong>Important</strong>: before calling this method, please check via * {@link android.content.pm.PackageManager#queryIntentServices(Intent, int)} that {@code * serviceComponent} actually exists and provides @@ -486,6 +505,9 @@ public class SpeechRecognizer { * before dispatching any command to the created {@code SpeechRecognizer}, otherwise no * notifications will be received. * + * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a + * SpeechRecognizer object when it is no longer needed. + * * @param context in which to create {@code SpeechRecognizer} * @return a new on-device {@code SpeechRecognizer}. * @throws UnsupportedOperationException iff {@link #isOnDeviceRecognitionAvailable(Context)} @@ -886,17 +908,32 @@ public class SpeechRecognizer { /** Destroys the {@code SpeechRecognizer} object. */ public void destroy() { - if (mService != null) { - try { - mService.cancel(mListener, /*isShutdown*/ true); - } catch (final Exception e) { - // Not important + try { + if (mService != null) { + try { + mService.cancel(mListener, /*isShutdown*/ true); + } catch (final Exception e) { + // Not important + } } + + mService = null; + mPendingTasks.clear(); + mListener.mInternalListener = null; + mCloseGuard.close(); + } finally { + Reference.reachabilityFence(this); } + } - mService = null; - mPendingTasks.clear(); - mListener.mInternalListener = null; + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + destroy(); + } finally { + super.finalize(); + } } /** Establishes a connection to system server proxy and initializes the session. */ diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 766e924c994e..b91a8789a181 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -163,12 +163,6 @@ public class FeatureFlagUtils { public static final String SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS = "settings_remoteauth_enrollment"; - /** Flag to enable/disable entire page in Accessibility -> Hearing aids - * @hide - */ - public static final String SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE = - "settings_accessibility_hearing_aid_page"; - /** * Flag to enable/disable preferring the AccessibilityMenu service in the system. * @hide @@ -244,7 +238,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false"); DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false"); - DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, "true"); DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false"); DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false"); DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true"); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index c73514250e3b..d4bd9e5bab92 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -651,6 +651,7 @@ public interface WindowManager extends ViewManager { REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY, REMOVE_CONTENT_MODE_DESTROY, }) + @Retention(RetentionPolicy.SOURCE) @interface RemoveContentMode {} /** @@ -685,6 +686,7 @@ public interface WindowManager extends ViewManager { DISPLAY_IME_POLICY_FALLBACK_DISPLAY, DISPLAY_IME_POLICY_HIDE, }) + @Retention(RetentionPolicy.SOURCE) @interface DisplayImePolicy {} /** @@ -3349,6 +3351,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP, PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY, }) + @Retention(RetentionPolicy.SOURCE) public @interface PrivateFlags {} /** diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java index c4d43bcfdb24..eb2a101e8ee6 100644 --- a/core/java/android/view/inputmethod/HandwritingGesture.java +++ b/core/java/android/view/inputmethod/HandwritingGesture.java @@ -86,6 +86,7 @@ public abstract class HandwritingGesture { * Granular level on which text should be operated. */ @IntDef({GRANULARITY_CHARACTER, GRANULARITY_WORD}) + @Retention(RetentionPolicy.SOURCE) @interface Granularity {} /** diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index eeab005771f5..589b7a3eeda4 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1176,24 +1176,33 @@ public final class InputMethodManager { mActive = interactive; mFullscreenMode = fullscreen; if (interactive) { + // Find the next view focus to start the input connection when the + // device was interactive. final View rootView = mCurRootView != null ? mCurRootView.getView() : null; if (rootView == null) { + // No window focused or view was removed, ignore request. return; } - // Find the next view focus to start the input connection when the - // device was interactive. final ViewRootImpl currentViewRootImpl = mCurRootView; + // Post this on UI thread as required for view focus code. rootView.post(() -> { synchronized (mH) { if (mCurRootView != currentViewRootImpl) { + // Focused window changed since posting, ignore request. return; } } - final View focusedView = currentViewRootImpl.getView().findFocus(); + final View curRootView = currentViewRootImpl.getView(); + if (curRootView == null) { + // View was removed, ignore request. + return; + } + final View focusedView = curRootView.findFocus(); onViewFocusChangedInternal(focusedView, focusedView != null); }); } else { + // Finish input connection when device becomes non-interactive. finishInputLocked(); if (isImeSessionAvailableLocked()) { mCurBindState.mImeSession.finishInput(); diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java index e44f43609256..4816f35e6a07 100644 --- a/core/java/android/window/BackNavigationInfo.java +++ b/core/java/android/window/BackNavigationInfo.java @@ -27,6 +27,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteCallback; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Information to be sent to SysUI about a back event. * @@ -85,6 +88,7 @@ public final class BackNavigationInfo implements Parcelable { TYPE_CROSS_TASK, TYPE_CALLBACK }) + @Retention(RetentionPolicy.SOURCE) public @interface BackTargetType { } diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java index 0ce076b6eb96..1bd921b339f6 100644 --- a/core/java/android/window/ClientWindowFrames.java +++ b/core/java/android/window/ClientWindowFrames.java @@ -22,6 +22,8 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * The window frame container class used by client side for layout. * @hide @@ -101,6 +103,29 @@ public class ClientWindowFrames implements Parcelable { } @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ClientWindowFrames other = (ClientWindowFrames) o; + return frame.equals(other.frame) + && displayFrame.equals(other.displayFrame) + && parentFrame.equals(other.parentFrame) + && Objects.equals(attachedFrame, other.attachedFrame) + && isParentFrameClippedByDisplayCutout == other.isParentFrameClippedByDisplayCutout + && compatScale == other.compatScale; + } + + @Override + public int hashCode() { + return Objects.hash(frame, displayFrame, parentFrame, attachedFrame, + isParentFrameClippedByDisplayCutout, compatScale); + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index f1c0d8dee525..b6c04d94b476 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -33,6 +33,8 @@ import android.util.Log; import android.util.Singleton; import android.util.Slog; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; /** @@ -65,6 +67,7 @@ public interface SplashScreen { SPLASH_SCREEN_STYLE_SOLID_COLOR, SPLASH_SCREEN_STYLE_ICON }) + @Retention(RetentionPolicy.SOURCE) @interface SplashScreenStyle {} /** diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig new file mode 100644 index 000000000000..9fe30df13036 --- /dev/null +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -0,0 +1,17 @@ +package: "com.android.window.flags" + +flag { + name: "allows_screen_size_decoupled_from_status_bar_and_cutout" + namespace: "large_screen_experiences_app_compat" + description: "When necessary, configuration decoupled from status bar and display cutout" + bug: "291870756" + is_fixed_read_only: true +} + +flag { + name: "movable_cutout_configuration" + namespace: "large_screen_experiences_app_compat" + description: "Make it possible to move cutout across edges through device config" + bug: "302387383" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 0ad6c99422b8..b600b22751ff 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -35,4 +35,11 @@ flag { name: "fullscreen_dim_flag" description: "Whether to allow showing fullscreen dim on ActivityEmbedding split" bug: "253533308" +} + +flag { + namespace: "windowing_sdk" + name: "activity_embedding_interactive_divider_flag" + description: "Whether the interactive divider feature is enabled" + bug: "293654166" }
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java index 1826f7a38e26..b0f35784fbbd 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,19 +26,6 @@ import java.util.Set; //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public interface ParsedActivity extends ParsedMainComponent { - /** - * Generate activity object that forwards user to App Details page automatically. - * This activity should be invisible to user and user should not know or see it. - * @hide - */ - @NonNull - static ParsedActivity makeAppDetailsActivity(String packageName, String processName, - int uiOptions, String taskAffinity, boolean hardwareAccelerated) { - // Proxy method since ParsedActivityImpl is supposed to be package visibility - return ParsedActivityImpl.makeAppDetailsActivity(packageName, processName, uiOptions, - taskAffinity, hardwareAccelerated); - } - int getColorMode(); int getConfigChanges(); diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemService.java index cf478b1da2e4..adb5f4f6d2a2 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttribution.java index 1a5d110ca8ff..5b623cdbaebb 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttribution.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.StringRes; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java b/core/java/com/android/internal/pm/pkg/component/ParsedComponent.java index 5b6ecbac1359..319ed7f23d47 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedComponent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentation.java index c325d8dab296..76c500827a32 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentation.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfo.java index a7f7b0086c00..fee0017c4a31 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfo.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java index b926d5345149..291ed0c44ddd 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermission.java index dc5347a41fcf..813d14dc61b3 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroup.java index 64f4fbd440a2..a5ba51307f01 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroup.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; /** @hide */ //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java index 608d08eeb984..e5247f99f398 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.SuppressLint; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java b/core/java/com/android/internal/pm/pkg/component/ParsedProvider.java index c66a5c1b6c92..ba5470f12017 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java b/core/java/com/android/internal/pm/pkg/component/ParsedService.java index 5fc251ccab47..e3611021442e 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermission.java index e17d1c4f5184..984d50ddfc70 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.IntDef; import android.annotation.NonNull; diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 686e1fc2c34e..9d0be4bf8ee6 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -40,6 +40,7 @@ import java.util.function.IntFunction; /** * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ArrayUtils { private static final int CACHE_SIZE = 73; private static Object[] sCache = new Object[CACHE_SIZE]; @@ -48,35 +49,43 @@ public class ArrayUtils { private ArrayUtils() { /* cannot be instantiated */ } + @android.ravenwood.annotation.RavenwoodReplace public static byte[] newUnpaddedByteArray(int minLen) { return (byte[])VMRuntime.getRuntime().newUnpaddedArray(byte.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace public static char[] newUnpaddedCharArray(int minLen) { return (char[])VMRuntime.getRuntime().newUnpaddedArray(char.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static int[] newUnpaddedIntArray(int minLen) { return (int[])VMRuntime.getRuntime().newUnpaddedArray(int.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace public static boolean[] newUnpaddedBooleanArray(int minLen) { return (boolean[])VMRuntime.getRuntime().newUnpaddedArray(boolean.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace public static long[] newUnpaddedLongArray(int minLen) { return (long[])VMRuntime.getRuntime().newUnpaddedArray(long.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace public static float[] newUnpaddedFloatArray(int minLen) { return (float[])VMRuntime.getRuntime().newUnpaddedArray(float.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace public static Object[] newUnpaddedObjectArray(int minLen) { return (Object[])VMRuntime.getRuntime().newUnpaddedArray(Object.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @SuppressWarnings("unchecked") public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) { diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 3496994fe173..698c5bad3801 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4303,6 +4303,9 @@ <!-- Whether the device must be screen on before routing data to this service. The default is true.--> <attr name="requireDeviceScreenOn" format="boolean"/> + <!-- Whether the device should default to observe mode when this service is + default or in the foreground. --> + <attr name="defaultToObserveMode" format="boolean"/> </declare-styleable> <!-- Use <code>offhost-apdu-service</code> as the root tag of the XML resource that @@ -4327,6 +4330,9 @@ <!-- Whether the device must be screen on before routing data to this service. The default is false.--> <attr name="requireDeviceScreenOn"/> + <!-- Whether the device should default to observe mode when this service is + default or in the foreground. --> + <attr name="defaultToObserveMode"/> </declare-styleable> <!-- Specify one or more <code>aid-group</code> elements inside a diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 764279490643..39d958cdf5ee 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -205,6 +205,13 @@ <string name="config_satellite_emergency_handover_intent_action" translatable="false"></string> <java-symbol type="string" name="config_satellite_emergency_handover_intent_action" /> + <!-- Whether outgoing satellite datagrams should be sent to modem in demo mode. When satellite + is enabled for demo mode, if this config is enabled, outgoing datagrams will be sent to + modem; otherwise, success results will be returned. If demo mode is disabled, outgoing + datagrams are always sent to modem. --> + <bool name="config_send_satellite_datagram_to_modem_in_demo_mode">false</bool> + <java-symbol type="bool" name="config_send_satellite_datagram_to_modem_in_demo_mode" /> + <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks will not perform handover if the target transport is out of service, or VoPS not supported. The network will be torn down on the source transport, and will be diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 15c188d2140b..cec83dee9c3a 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5245,6 +5245,11 @@ <!-- Zen mode - name of default automatic calendar time-based rule that is triggered every night (when sleeping). [CHAR LIMIT=40] --> <string name="zen_mode_default_every_night_name">Sleeping</string> + <!-- Zen mode - Condition summary when a rule is activated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] --> + <string name="zen_mode_implicit_activated">On</string> + <!-- Zen mode - Condition summary when a rule is deactivated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] --> + <string name="zen_mode_implicit_deactivated">Off</string> + <!-- Indication that the current volume and other effects (vibration) are being suppressed by a third party, such as a notification listener. [CHAR LIMIT=30] --> <string name="muted_by"><xliff:g id="third_party">%1$s</xliff:g> is muting some sounds</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e19d5486768a..16ad5c909575 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2574,6 +2574,8 @@ <java-symbol type="string" name="zen_mode_default_weekends_name" /> <java-symbol type="string" name="zen_mode_default_events_name" /> <java-symbol type="string" name="zen_mode_default_every_night_name" /> + <java-symbol type="string" name="zen_mode_implicit_activated" /> + <java-symbol type="string" name="zen_mode_implicit_deactivated" /> <java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" /> <java-symbol type="string" name="display_rotation_camera_compat_toast_in_multi_window" /> <java-symbol type="array" name="config_system_condition_providers" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 709646b00e5c..3a2e50aa06e8 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -34,7 +34,7 @@ http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ --> <!-- Arab Emirates --> - <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214|6253" /> + <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" /> <!-- Albania: 5 digits, known short codes listed --> <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" /> @@ -155,7 +155,7 @@ <shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" /> <!-- Israel: 4 digits, known premium codes listed --> - <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477" /> + <shortcode country="il" pattern="\\d{4}" premium="4422|4545" /> <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU: https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf --> @@ -198,9 +198,6 @@ <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf --> <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" /> - <!-- Namibia: 5 digits --> - <shortcode country="na" pattern="\\d{1,5}" free="40005" /> - <!-- The Netherlands, 4 digits, known premium codes listed, plus EU --> <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" /> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 36e122301ba2..4b02257978d2 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -227,8 +227,7 @@ public class ActivityThreadTest { try { // Send process level config change. ClientTransaction transaction = newTransaction(activityThread); - transaction.addCallback(ConfigurationChangeItem.obtain( - new Configuration(newConfig), DEVICE_ID_INVALID)); + transaction.addCallback(ConfigurationChangeItem.obtain(newConfig, DEVICE_ID_INVALID)); appThread.scheduleTransaction(transaction); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -245,7 +244,7 @@ public class ActivityThreadTest { newConfig.smallestScreenWidthDp++; transaction = newTransaction(activityThread); transaction.addCallback(ActivityConfigurationChangeItem.obtain( - activity.getActivityToken(), new Configuration(newConfig))); + activity.getActivityToken(), newConfig)); appThread.scheduleTransaction(transaction); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 4bbde0cd6366..723c0812468c 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; +import android.annotation.NonNull; import android.app.ActivityOptions; import android.app.IApplicationThread; import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder; @@ -80,74 +81,29 @@ public class ObjectPoolTests { @Test public void testRecycleActivityConfigurationChangeItem() { - ActivityConfigurationChangeItem emptyItem = ActivityConfigurationChangeItem.obtain( - null /* activityToken */, Configuration.EMPTY); - ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain( - mActivityToken, config()); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - ActivityConfigurationChangeItem item2 = ActivityConfigurationChangeItem.obtain( - mActivityToken, config()); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> ActivityConfigurationChangeItem.obtain(mActivityToken, config())); } @Test public void testRecycleActivityResultItem() { - ActivityResultItem emptyItem = ActivityResultItem.obtain( - null /* activityToken */, null /* resultInfoList */); - ActivityResultItem item = ActivityResultItem.obtain(mActivityToken, resultInfoList()); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - ActivityResultItem item2 = ActivityResultItem.obtain(mActivityToken, resultInfoList()); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> ActivityResultItem.obtain(mActivityToken, resultInfoList())); } @Test public void testRecycleConfigurationChangeItem() { - ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null, 0); - ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config(), 1); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> ConfigurationChangeItem.obtain(config(), 1)); } @Test public void testRecycleDestroyActivityItem() { - DestroyActivityItem emptyItem = DestroyActivityItem.obtain( - null /* activityToken */, false, 0); - DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true, 117); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - DestroyActivityItem item2 = DestroyActivityItem.obtain(mActivityToken, true, 14); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true, 117)); } @Test public void testRecycleLaunchActivityItem() { final IBinder activityToken = new Binder(); final Intent intent = new Intent("action"); - int ident = 57; + final int ident = 57; final ActivityInfo activityInfo = new ActivityInfo(); activityInfo.flags = 42; activityInfo.setMaxAspectRatio(2.4f); @@ -158,20 +114,19 @@ public class ObjectPoolTests { final Configuration overrideConfig = new Configuration(); overrideConfig.assetsSeq = 5; final String referrer = "referrer"; - int procState = 4; + final int procState = 4; final Bundle bundle = new Bundle(); bundle.putString("key", "value"); final PersistableBundle persistableBundle = new PersistableBundle(); persistableBundle.putInt("k", 4); final IBinder assistToken = new Binder(); final IBinder shareableActivityToken = new Binder(); - int deviceId = 3; + final int deviceId = 3; + final IBinder taskFragmentToken = new Binder(); - final Supplier<LaunchActivityItem> itemSupplier = () -> new LaunchActivityItemBuilder() - .setActivityToken(activityToken) - .setIntent(intent) + testRecycle(() -> new LaunchActivityItemBuilder( + activityToken, intent, activityInfo) .setIdent(ident) - .setInfo(activityInfo) .setCurConfig(config()) .setOverrideConfig(overrideConfig) .setReferrer(referrer) @@ -183,154 +138,74 @@ public class ObjectPoolTests { .setIsForward(true) .setAssistToken(assistToken) .setShareableActivityToken(shareableActivityToken) - .setTaskFragmentToken(new Binder()) + .setTaskFragmentToken(taskFragmentToken) .setDeviceId(deviceId) - .build(); - - LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build(); - LaunchActivityItem item = itemSupplier.get(); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - LaunchActivityItem item2 = itemSupplier.get(); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + .build()); } @Test public void testRecycleActivityRelaunchItem() { - ActivityRelaunchItem emptyItem = ActivityRelaunchItem.obtain( - null /* activityToken */, null, null, 0, null, false); - Configuration overrideConfig = new Configuration(); - overrideConfig.assetsSeq = 5; - ActivityRelaunchItem item = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(), - referrerIntentList(), 42, mergedConfig(), true); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - ActivityRelaunchItem item2 = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(), - referrerIntentList(), 42, mergedConfig(), true); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> ActivityRelaunchItem.obtain(mActivityToken, + resultInfoList(), referrerIntentList(), 42, mergedConfig(), true)); } @Test public void testRecycleMoveToDisplayItem() { - MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain( - null /* activityToken */, 0, Configuration.EMPTY); - MoveToDisplayItem item = MoveToDisplayItem.obtain(mActivityToken, 4, config()); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - MoveToDisplayItem item2 = MoveToDisplayItem.obtain(mActivityToken, 3, config()); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> MoveToDisplayItem.obtain(mActivityToken, 4, config())); } @Test public void testRecycleNewIntentItem() { - NewIntentItem emptyItem = NewIntentItem.obtain( - null /* activityToken */, null /* intents */, false /* resume */); - NewIntentItem item = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - NewIntentItem item2 = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> NewIntentItem.obtain(mActivityToken, referrerIntentList(), false)); } @Test public void testRecyclePauseActivityItemItem() { - PauseActivityItem emptyItem = PauseActivityItem.obtain( - null /* activityToken */, false, false, 0, false, false); - PauseActivityItem item = PauseActivityItem.obtain( - mActivityToken, true, true, 5, true, true); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - PauseActivityItem item2 = PauseActivityItem.obtain( - mActivityToken, true, false, 5, true, true); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, 5, true, true)); } @Test public void testRecycleResumeActivityItem() { - ResumeActivityItem emptyItem = ResumeActivityItem.obtain( - null /* activityToken */, false, false); - ResumeActivityItem item = ResumeActivityItem.obtain(mActivityToken, 3, true, false); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - ResumeActivityItem item2 = ResumeActivityItem.obtain(mActivityToken, 2, true, false); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> ResumeActivityItem.obtain(mActivityToken, 3, true, false)); } @Test public void testRecycleStartActivityItem() { - StartActivityItem emptyItem = StartActivityItem.obtain( - null /* activityToken */, null /* activityOptions */); - StartActivityItem item = StartActivityItem.obtain(mActivityToken, - ActivityOptions.makeBasic()); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - StartActivityItem item2 = StartActivityItem.obtain(mActivityToken, - ActivityOptions.makeBasic().setLaunchDisplayId(10)); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> StartActivityItem.obtain(mActivityToken, ActivityOptions.makeBasic())); } @Test public void testRecycleStopItem() { - StopActivityItem emptyItem = StopActivityItem.obtain(null /* activityToken */, 0); - StopActivityItem item = StopActivityItem.obtain(mActivityToken, 4); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - StopActivityItem item2 = StopActivityItem.obtain(mActivityToken, 3); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> StopActivityItem.obtain(mActivityToken, 4)); } @Test public void testRecycleClientTransaction() { - ClientTransaction emptyItem = ClientTransaction.obtain(null); - ClientTransaction item = ClientTransaction.obtain(mApplicationThread); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); + testRecycle(() -> ClientTransaction.obtain(mApplicationThread)); + } + private void testRecycle(@NonNull Supplier<? extends ObjectPoolItem> obtain) { + // Reuse the same object after recycle. + final ObjectPoolItem item = obtain.get(); item.recycle(); - assertEquals(item, emptyItem); + final ObjectPoolItem item2 = obtain.get(); - ClientTransaction item2 = ClientTransaction.obtain(mApplicationThread); assertSame(item, item2); - assertNotEquals(item2, emptyItem); + + // Create new object when the pool is empty. + final ObjectPoolItem item3 = obtain.get(); + + assertNotSame(item, item3); + assertEquals(item, item3); + + // Reset fields after recycle. + item.recycle(); + + assertNotEquals(item, item3); + + // Recycled objects are equal. + item3.recycle(); + + assertEquals(item, item3); } } diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java index 5a88bad37d5f..c0e2a4993e1c 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java +++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java @@ -18,6 +18,8 @@ package android.app.servertransaction; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static java.util.Objects.requireNonNull; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; @@ -92,17 +94,18 @@ class TestUtils { } static class LaunchActivityItemBuilder { - @Nullable - private IBinder mActivityToken; - @Nullable - private Intent mIntent; + @NonNull + private final IBinder mActivityToken; + @NonNull + private final Intent mIntent; + @NonNull + private final ActivityInfo mInfo; + @NonNull + private final Configuration mCurConfig = new Configuration(); + @NonNull + private final Configuration mOverrideConfig = new Configuration(); + private int mIdent; - @Nullable - private ActivityInfo mInfo; - @Nullable - private Configuration mCurConfig; - @Nullable - private Configuration mOverrideConfig; private int mDeviceId; @Nullable private String mReferrer; @@ -130,16 +133,11 @@ class TestUtils { @Nullable private IBinder mTaskFragmentToken; - @NonNull - LaunchActivityItemBuilder setActivityToken(@Nullable IBinder activityToken) { - mActivityToken = activityToken; - return this; - } - - @NonNull - LaunchActivityItemBuilder setIntent(@Nullable Intent intent) { - mIntent = intent; - return this; + LaunchActivityItemBuilder(@NonNull IBinder activityToken, @NonNull Intent intent, + @NonNull ActivityInfo info) { + mActivityToken = requireNonNull(activityToken); + mIntent = requireNonNull(intent); + mInfo = requireNonNull(info); } @NonNull @@ -149,20 +147,14 @@ class TestUtils { } @NonNull - LaunchActivityItemBuilder setInfo(@Nullable ActivityInfo info) { - mInfo = info; - return this; - } - - @NonNull - LaunchActivityItemBuilder setCurConfig(@Nullable Configuration curConfig) { - mCurConfig = curConfig; + LaunchActivityItemBuilder setCurConfig(@NonNull Configuration curConfig) { + mCurConfig.setTo(curConfig); return this; } @NonNull - LaunchActivityItemBuilder setOverrideConfig(@Nullable Configuration overrideConfig) { - mOverrideConfig = overrideConfig; + LaunchActivityItemBuilder setOverrideConfig(@NonNull Configuration overrideConfig) { + mOverrideConfig.setTo(overrideConfig); return this; } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java index f2b0f2e622b8..443dcb4e51c5 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java @@ -44,6 +44,8 @@ import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; import android.app.servertransaction.ActivityLifecycleItem.LifecycleState; import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder; +import android.content.Intent; +import android.content.pm.ActivityInfo; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -290,7 +292,7 @@ public class TransactionExecutorTests { // A previous queued launch transaction runs on main thread (execute). final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */); final LaunchActivityItem launchItem = - spy(new LaunchActivityItemBuilder().setActivityToken(token).build()); + spy(new LaunchActivityItemBuilder(token, new Intent(), new ActivityInfo()).build()); launchTransaction.addCallback(launchItem); mExecutor.execute(launchTransaction); @@ -322,7 +324,7 @@ public class TransactionExecutorTests { // A previous queued launch transaction runs on main thread (execute). final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */); final LaunchActivityItem launchItem = - spy(new LaunchActivityItemBuilder().setActivityToken(token).build()); + spy(new LaunchActivityItemBuilder(token, new Intent(), new ActivityInfo()).build()); launchTransaction.addTransactionItem(launchItem); mExecutor.execute(launchTransaction); diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 4aa62c503a41..07921bfc34f5 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -173,11 +173,9 @@ public class TransactionParcelTests { final PersistableBundle persistableBundle = new PersistableBundle(); persistableBundle.putInt("k", 4); - final LaunchActivityItem item = new LaunchActivityItemBuilder() - .setActivityToken(activityToken) - .setIntent(intent) + final LaunchActivityItem item = new LaunchActivityItemBuilder( + activityToken, intent, activityInfo) .setIdent(ident) - .setInfo(activityInfo) .setCurConfig(config()) .setOverrideConfig(overrideConfig) .setReferrer(referrer) diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java index c00eb91d752a..4d45daf3570c 100644 --- a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java @@ -52,16 +52,18 @@ public class WindowStateResizeItemTest { private PendingTransactionActions mPendingActions; @Mock private IWindow mWindow; - @Mock + + private InsetsState mInsetsState; private ClientWindowFrames mFrames; - @Mock private MergedConfiguration mConfiguration; - @Mock - private InsetsState mInsetsState; @Before public void setup() { MockitoAnnotations.initMocks(this); + + mInsetsState = new InsetsState(); + mFrames = new ClientWindowFrames(); + mConfiguration = new MergedConfiguration(); } @Test diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java index 3e3c77b7b21c..03c38cc137ad 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java @@ -25,9 +25,11 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import android.annotation.EnforcePermission; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; import android.os.IBinder; import android.os.RemoteException; +import android.os.test.FakePermissionEnforcer; import androidx.test.filters.SmallTest; @@ -57,7 +59,8 @@ public final class DeviceStateManagerGlobalTest { @Before public void setUp() { - mService = new TestDeviceStateManagerService(); + FakePermissionEnforcer permissionEnforcer = new FakePermissionEnforcer(); + mService = new TestDeviceStateManagerService(permissionEnforcer); mDeviceStateManagerGlobal = new DeviceStateManagerGlobal(mService); assertFalse(mService.mCallbacks.isEmpty()); } @@ -261,6 +264,10 @@ public final class DeviceStateManagerGlobalTest { private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>(); + TestDeviceStateManagerService(FakePermissionEnforcer enforcer) { + super(enforcer); + } + private DeviceStateInfo getInfo() { final int mergedBaseState = mBaseStateRequest == null ? mBaseState : mBaseStateRequest.state; @@ -380,7 +387,10 @@ public final class DeviceStateManagerGlobalTest { // No-op in the test since DeviceStateManagerGlobal just calls into the system server with // no business logic around it. @Override - public void onStateRequestOverlayDismissed(boolean shouldCancelMode) {} + @EnforcePermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + public void onStateRequestOverlayDismissed(boolean shouldCancelMode) { + onStateRequestOverlayDismissed_enforcePermission(); + } public void setSupportedStates(int[] states) { mSupportedStates = states; diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index 66fabec91924..a4bce9eb5e88 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -23,6 +23,8 @@ import android.annotation.NonNull; import libcore.util.NativeAllocationRegistry; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.Buffer; import java.nio.ShortBuffer; @@ -43,6 +45,7 @@ public class Mesh { * Determines how the mesh is represented and will be drawn. */ @IntDef({TRIANGLES, TRIANGLE_STRIP}) + @Retention(RetentionPolicy.SOURCE) private @interface Mode {} /** diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java index 5509f000aca5..45e29a88c7db 100644 --- a/graphics/java/android/graphics/drawable/Icon.java +++ b/graphics/java/android/graphics/drawable/Icon.java @@ -62,6 +62,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Objects; @@ -116,6 +118,7 @@ public final class Icon implements Parcelable { */ @IntDef({TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP, TYPE_URI_ADAPTIVE_BITMAP}) + @Retention(RetentionPolicy.SOURCE) public @interface IconType { } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index d5b29e384c09..d5fab441cd46 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -822,14 +822,23 @@ public class PipTransition extends PipTransitionController { + "participant"); } - // Make sure other open changes are visible as entering PIP. Some may be hidden in - // Transitions#setupStartState because the transition type is OPEN (such as auto-enter). + // Make sure other non-pip changes are handled correctly. for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change == enterPip) continue; if (TransitionUtil.isOpeningType(change.getMode())) { + // For other open changes that are visible when entering PIP, some may be hidden in + // Transitions#setupStartState because the transition type is OPEN (such as + // auto-enter). final SurfaceControl leash = change.getLeash(); startTransaction.show(leash).setAlpha(leash, 1.f); + } else if (TransitionUtil.isClosingType(change.getMode())) { + // For other close changes that are invisible as entering PIP, hide them immediately + // to avoid showing a freezing surface. + // Ideally, we should let other handler to handle them (likely RemoteHandler by + // Launcher). + final SurfaceControl leash = change.getLeash(); + startTransaction.hide(leash); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index b26d0613f3b6..07c54293111c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -16,6 +16,7 @@ package com.android.wm.shell.unfold; +import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.TRANSIT_CHANGE; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; @@ -188,23 +189,27 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback) { - if (info.getType() == TRANSIT_CHANGE) { - // TODO (b/286928742) unfold transition handler should be part of mixed handler to - // handle merges better. - for (int i = 0; i < info.getChanges().size(); ++i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo != null - && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { - // Tasks that are always on top (e.g. bubbles), will handle their own transition - // as they are on top of everything else. So skip merging transitions here. - return; - } + if (info.getType() != TRANSIT_CHANGE) { + return; + } + if ((info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) { + return; + } + // TODO (b/286928742) unfold transition handler should be part of mixed handler to + // handle merges better. + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo != null + && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { + // Tasks that are always on top (e.g. bubbles), will handle their own transition + // as they are on top of everything else. So skip merging transitions here. + return; } - // Apply changes happening during the unfold animation immediately - t.apply(); - finishCallback.onTransitionFinished(null); } + // Apply changes happening during the unfold animation immediately + t.apply(); + finishCallback.onTransitionFinished(null); } /** Whether `request` contains an unfold action. */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java index 7917ba6eeb41..6d73c12dc304 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java @@ -17,6 +17,7 @@ package com.android.wm.shell.unfold; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_NONE; import static com.google.common.truth.Truth.assertThat; @@ -46,6 +47,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.transition.TransitionInfoBuilder; import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; @@ -265,6 +267,42 @@ public class UnfoldTransitionHandlerTest { verify(finishCallback).onTransitionFinished(any()); } + @Test + public void mergeAnimation_eatsDisplayOnlyTransitions() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + TransitionFinishCallback mergeCallback = mock(TransitionFinishCallback.class); + + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback); + + // Offer a keyguard unlock transition - this should NOT merge + mUnfoldTransitionHandler.mergeAnimation( + new Binder(), + new TransitionInfoBuilder(TRANSIT_CHANGE, TRANSIT_FLAG_KEYGUARD_GOING_AWAY).build(), + mock(SurfaceControl.Transaction.class), + mTransition, + mergeCallback); + verify(finishCallback, never()).onTransitionFinished(any()); + + // Offer a CHANGE-only transition - this SHOULD merge (b/278064943) + mUnfoldTransitionHandler.mergeAnimation( + new Binder(), + new TransitionInfoBuilder(TRANSIT_CHANGE).build(), + mock(SurfaceControl.Transaction.class), + mTransition, + mergeCallback); + verify(mergeCallback).onTransitionFinished(any()); + + // We should never have finished the original transition. + verify(finishCallback, never()).onTransitionFinished(any()); + } + private TransitionRequestInfo createUnfoldTransitionRequestInfo() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( @@ -372,4 +410,4 @@ public class UnfoldTransitionHandlerTest { private TransitionInfo createNonUnfoldTransitionInfo() { return new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0); } -}
\ No newline at end of file +} diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index a1b05c186ec0..a7d64231da80 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -597,7 +597,13 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, SkIRect clipBounds; if (enableClip) { uirenderer::Rect initialClipBounds; - props.getClippingRectForFlags(props.getClippingFlags(), &initialClipBounds); + const auto clipFlags = props.getClippingFlags(); + if (clipFlags) { + props.getClippingRectForFlags(clipFlags, &initialClipBounds); + } else { + // Works for RenderNode::damageSelf() + initialClipBounds.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); + } clipBounds = info.damageAccumulator ->computeClipAndTransform(initialClipBounds.toSkRect(), &transform) diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index e706eb083ab6..d55d28d469d0 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -527,52 +527,65 @@ Frame VulkanManager::dequeueNextBuffer(VulkanSurface* surface) { return Frame(surface->logicalWidth(), surface->logicalHeight(), bufferAge); } -struct DestroySemaphoreInfo { +class SharedSemaphoreInfo : public LightRefBase<SharedSemaphoreInfo> { PFN_vkDestroySemaphore mDestroyFunction; VkDevice mDevice; VkSemaphore mSemaphore; + GrBackendSemaphore mGrBackendSemaphore; - DestroySemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device, - VkSemaphore semaphore) - : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {} + SharedSemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device, + VkSemaphore semaphore) + : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) { + mGrBackendSemaphore.initVulkan(semaphore); + } + + ~SharedSemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); } + + friend class LightRefBase<SharedSemaphoreInfo>; + friend class sp<SharedSemaphoreInfo>; + +public: + VkSemaphore semaphore() const { return mSemaphore; } - ~DestroySemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); } + GrBackendSemaphore* grBackendSemaphore() { return &mGrBackendSemaphore; } }; static void destroy_semaphore(void* context) { - DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(context); - delete info; + SharedSemaphoreInfo* info = reinterpret_cast<SharedSemaphoreInfo*>(context); + info->decStrong(0); } VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) { ATRACE_NAME("Vulkan finish frame"); - VkExportSemaphoreCreateInfo exportInfo; - exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; - exportInfo.pNext = nullptr; - exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; - - VkSemaphoreCreateInfo semaphoreInfo; - semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - semaphoreInfo.pNext = &exportInfo; - semaphoreInfo.flags = 0; - VkSemaphore semaphore; - VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore); - ALOGE_IF(VK_SUCCESS != err, "VulkanManager::makeSwapSemaphore(): Failed to create semaphore"); - - GrBackendSemaphore backendSemaphore; - backendSemaphore.initVulkan(semaphore); - + sp<SharedSemaphoreInfo> sharedSemaphore; GrFlushInfo flushInfo; - if (err == VK_SUCCESS) { - flushInfo.fNumSemaphores = 1; - flushInfo.fSignalSemaphores = &backendSemaphore; - flushInfo.fFinishedProc = destroy_semaphore; - flushInfo.fFinishedContext = - new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore); - } else { - semaphore = VK_NULL_HANDLE; + + { + VkExportSemaphoreCreateInfo exportInfo; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.pNext = nullptr; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + + VkSemaphoreCreateInfo semaphoreInfo; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphoreInfo.pNext = &exportInfo; + semaphoreInfo.flags = 0; + VkSemaphore semaphore; + VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore); + ALOGE_IF(VK_SUCCESS != err, + "VulkanManager::makeSwapSemaphore(): Failed to create semaphore"); + + if (err == VK_SUCCESS) { + sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore); + flushInfo.fNumSemaphores = 1; + flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore(); + flushInfo.fFinishedProc = destroy_semaphore; + sharedSemaphore->incStrong(0); + flushInfo.fFinishedContext = sharedSemaphore.get(); + } } + GrDirectContext* context = GrAsDirectContext(surface->recordingContext()); ALOGE_IF(!context, "Surface is not backed by gpu"); GrSemaphoresSubmitted submitted = context->flush( @@ -581,37 +594,34 @@ VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) { VkDrawResult drawResult{ .submissionTime = systemTime(), }; - if (semaphore != VK_NULL_HANDLE) { - if (submitted == GrSemaphoresSubmitted::kYes) { - if (mFrameBoundaryANDROID) { - // retrieve VkImage used as render target - VkImage image = VK_NULL_HANDLE; - GrBackendRenderTarget backendRenderTarget = - SkSurfaces::GetBackendRenderTarget( - surface, SkSurfaces::BackendHandleAccess::kFlushRead); - if (backendRenderTarget.isValid()) { - GrVkImageInfo info; - if (GrBackendRenderTargets::GetVkImageInfo(backendRenderTarget, &info)) { - image = info.fImage; - } else { - ALOGE("Frame boundary: backend is not vulkan"); - } + if (sharedSemaphore) { + if (submitted == GrSemaphoresSubmitted::kYes && mFrameBoundaryANDROID) { + // retrieve VkImage used as render target + VkImage image = VK_NULL_HANDLE; + GrBackendRenderTarget backendRenderTarget = SkSurfaces::GetBackendRenderTarget( + surface, SkSurfaces::BackendHandleAccess::kFlushRead); + if (backendRenderTarget.isValid()) { + GrVkImageInfo info; + if (GrBackendRenderTargets::GetVkImageInfo(backendRenderTarget, &info)) { + image = info.fImage; } else { - ALOGE("Frame boundary: invalid backend render target"); + ALOGE("Frame boundary: backend is not vulkan"); } - // frameBoundaryANDROID needs to know about mSwapSemaphore, but - // it won't wait on it. - mFrameBoundaryANDROID(mDevice, semaphore, image); + } else { + ALOGE("Frame boundary: invalid backend render target"); } + // frameBoundaryANDROID needs to know about mSwapSemaphore, but + // it won't wait on it. + mFrameBoundaryANDROID(mDevice, sharedSemaphore->semaphore(), image); } VkSemaphoreGetFdInfoKHR getFdInfo; getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR; getFdInfo.pNext = nullptr; - getFdInfo.semaphore = semaphore; + getFdInfo.semaphore = sharedSemaphore->semaphore(); getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; int fenceFd = -1; - err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd); + VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd); ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd"); drawResult.presentFence.reset(fenceFd); } else { @@ -732,15 +742,15 @@ status_t VulkanManager::createReleaseFence(int* nativeFence, GrDirectContext* gr return INVALID_OPERATION; } - GrBackendSemaphore backendSemaphore; - backendSemaphore.initVulkan(semaphore); + auto sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore); // Even if Skia fails to submit the semaphore, it will still call the destroy_semaphore callback GrFlushInfo flushInfo; flushInfo.fNumSemaphores = 1; - flushInfo.fSignalSemaphores = &backendSemaphore; + flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore(); flushInfo.fFinishedProc = destroy_semaphore; - flushInfo.fFinishedContext = new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore); + sharedSemaphore->incStrong(0); + flushInfo.fFinishedContext = sharedSemaphore.get(); GrSemaphoresSubmitted submitted = grContext->flush(flushInfo); grContext->submit(); diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index 5a274353f68e..2b349d498d59 100644 --- a/media/java/android/media/AudioDeviceAttributes.java +++ b/media/java/android/media/AudioDeviceAttributes.java @@ -325,7 +325,7 @@ public final class AudioDeviceAttributes implements Parcelable { + " role:" + roleToString(mRole) + " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName(mNativeType) : AudioSystem.getInputDeviceName(mNativeType)) - + " addr:" + mAddress + + " addr:" + Utils.anonymizeBluetoothAddress(mNativeType, mAddress) + " name:" + mName + " profiles:" + mAudioProfiles.toString() + " descriptors:" + mAudioDescriptors.toString()); diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java index 9211c53a17bc..73bc6f96bf8b 100644 --- a/media/java/android/media/AudioDevicePort.java +++ b/media/java/android/media/AudioDevicePort.java @@ -156,7 +156,7 @@ public class AudioDevicePort extends AudioPort { AudioSystem.getOutputDeviceName(mType)); return "{" + super.toString() + ", mType: " + type - + ", mAddress: " + mAddress + + ", mAddress: " + Utils.anonymizeBluetoothAddress(mType, mAddress) + "}"; } } diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java index 985a7584ffe2..0f48abeb6882 100644 --- a/media/java/android/media/AudioHalVersionInfo.java +++ b/media/java/android/media/AudioHalVersionInfo.java @@ -22,6 +22,8 @@ import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -54,6 +56,7 @@ public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHa flag = false, prefix = "AUDIO_HAL_TYPE_", value = {AUDIO_HAL_TYPE_HIDL, AUDIO_HAL_TYPE_AIDL}) + @Retention(RetentionPolicy.SOURCE) public @interface AudioHalType {} /** AudioHalVersionInfo object of all valid Audio HAL versions. */ diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index faa7f7fa3aa1..5331046dd0e3 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -2018,6 +2018,8 @@ public final class MediaDrm implements AutoCloseable { * {@link #HDCP_V2_1}, * {@link #HDCP_V2_2}, * {@link #HDCP_V2_3} + * + * @removed mistakenly exposed previously */ @Deprecated @Retention(RetentionPolicy.SOURCE) @@ -2121,6 +2123,8 @@ public final class MediaDrm implements AutoCloseable { * {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO}, * {@link #SECURITY_LEVEL_HW_SECURE_DECODE}, * {@link #SECURITY_LEVEL_HW_SECURE_ALL} + * + * @removed mistakenly exposed previously */ @Deprecated @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java index 74ca4b858ff6..99fcaf29688c 100644 --- a/media/java/android/media/Spatializer.java +++ b/media/java/android/media/Spatializer.java @@ -267,20 +267,26 @@ public class Spatializer { HEAD_TRACKING_MODE_DISABLED, HEAD_TRACKING_MODE_RELATIVE_WORLD, HEAD_TRACKING_MODE_RELATIVE_DEVICE, - }) public @interface HeadTrackingMode {}; + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HeadTrackingMode {}; /** @hide */ @IntDef(flag = false, value = { HEAD_TRACKING_MODE_DISABLED, HEAD_TRACKING_MODE_RELATIVE_WORLD, HEAD_TRACKING_MODE_RELATIVE_DEVICE, - }) public @interface HeadTrackingModeSet {}; + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HeadTrackingModeSet {}; /** @hide */ @IntDef(flag = false, value = { HEAD_TRACKING_MODE_RELATIVE_WORLD, HEAD_TRACKING_MODE_RELATIVE_DEVICE, - }) public @interface HeadTrackingModeSupported {}; + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HeadTrackingModeSupported {}; /** * @hide diff --git a/media/java/android/media/Utils.java b/media/java/android/media/Utils.java index ecb6b3d495ad..d07f6118f6f4 100644 --- a/media/java/android/media/Utils.java +++ b/media/java/android/media/Utils.java @@ -657,4 +657,35 @@ public class Utils { // on the fly. private final boolean mForceRemoveConsistency; // default false } + + /** + * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app + * Must match the implementation of BluetoothUtils.toAnonymizedAddress() + * @param address MAC address to be anonymized + * @return anonymized MAC address + */ + public static @Nullable String anonymizeBluetoothAddress(@Nullable String address) { + if (address == null) { + return null; + } + if (address.length() != "AA:BB:CC:DD:EE:FF".length()) { + return address; + } + return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length()); + } + + /** + * Convert a Bluetooth MAC address to an anonymized one if the internal device type corresponds + * to a Bluetooth. + * @param deviceType the internal type of the audio device + * @param address MAC address to be anonymized + * @return anonymized MAC address + */ + public static @Nullable String anonymizeBluetoothAddress( + int deviceType, @Nullable String address) { + if (!AudioSystem.isBluetoothDevice(deviceType)) { + return address; + } + return anonymizeBluetoothAddress(address); + } } diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl new file mode 100644 index 000000000000..92cc923dc9ab --- /dev/null +++ b/media/java/android/media/tv/ad/ITvAdManager.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.ad; + +/** + * Interface to the TV AD service. + * @hide + */ +interface ITvAdManager { + void startAdService(in IBinder sessionToken, int userId); +} diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl new file mode 100644 index 000000000000..b834f1b9fb92 --- /dev/null +++ b/media/java/android/media/tv/ad/ITvAdSession.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.ad; + +/** + * Sub-interface of ITvAdService which is created per session and has its own context. + * @hide + */ +oneway interface ITvAdSession { + void startAdService(); +} diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java new file mode 100644 index 000000000000..aa5a290346b0 --- /dev/null +++ b/media/java/android/media/tv/ad/TvAdManager.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.ad; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +/** + * Central system API to the overall client-side TV AD architecture, which arbitrates interaction + * between applications and AD services. + * @hide + */ +public class TvAdManager { + private static final String TAG = "TvAdManager"; + + private final ITvAdManager mService; + private final int mUserId; + + public TvAdManager(ITvAdManager service, int userId) { + mService = service; + mUserId = userId; + } + + /** + * The Session provides the per-session functionality of AD service. + */ + public static final class Session { + private final IBinder mToken; + private final ITvAdManager mService; + private final int mUserId; + + private Session(IBinder token, ITvAdManager service, int userId) { + mToken = token; + mService = service; + mUserId = userId; + } + + void startAdService() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.startAdService(mToken, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } +} diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java new file mode 100644 index 000000000000..61101f07923c --- /dev/null +++ b/media/java/android/media/tv/ad/TvAdService.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.ad; + +import android.app.Service; +import android.view.KeyEvent; + +/** + * The TvAdService class represents a TV client-side advertisement service. + * @hide + */ +public abstract class TvAdService extends Service { + private static final boolean DEBUG = false; + private static final String TAG = "TvAdService"; + + /** + * Base class for derived classes to implement to provide a TV AD session. + */ + public abstract static class Session implements KeyEvent.Callback { + /** + * Starts TvAdService session. + */ + public void onStartAdService() { + } + + void startAdService() { + onStartAdService(); + } + } + + /** + * Implements the internal ITvAdService interface. + */ + public static class ITvAdSessionWrapper extends ITvAdSession.Stub { + private final Session mSessionImpl; + + public ITvAdSessionWrapper(Session mSessionImpl) { + this.mSessionImpl = mSessionImpl; + } + + @Override + public void startAdService() { + mSessionImpl.startAdService(); + } + } +} diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java new file mode 100644 index 000000000000..1a3771a9f24c --- /dev/null +++ b/media/java/android/media/tv/ad/TvAdView.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv.ad; + +import android.content.Context; +import android.util.Log; +import android.view.ViewGroup; + +/** + * Displays contents of TV AD services. + * @hide + */ +public class TvAdView extends ViewGroup { + private static final String TAG = "TvAdView"; + private static final boolean DEBUG = false; + + // TODO: create session + private TvAdManager.Session mSession; + + public TvAdView(Context context) { + super(context, /* attrs = */null, /* defStyleAttr = */0); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + if (DEBUG) { + Log.d(TAG, + "onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)"); + } + } + + /** + * Starts the AD service. + */ + public void startAdService() { + if (DEBUG) { + Log.d(TAG, "start"); + } + if (mSession != null) { + mSession.startAdService(); + } + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt new file mode 100644 index 000000000000..d0d2dc0083a6 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.framework.common + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn + +/** + * A [BroadcastReceiver] flow for the given [intentFilter]. + */ +fun Context.broadcastReceiverFlow(intentFilter: IntentFilter): Flow<Intent> = callbackFlow { + val broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + trySend(intent) + } + } + registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED) + + awaitClose { unregisterReceiver(broadcastReceiver) } +}.conflate().flowOn(Dispatchers.Default) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt index 1c92696efd29..223e99e61204 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * 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. @@ -57,19 +57,24 @@ fun UserProfilePager(content: @Composable (userGroup: UserGroup) -> Unit) { private fun UserManager.getUserGroups(): List<UserGroup> { val userGroupList = mutableListOf<UserGroup>() - val profileToShowInSettings = getProfiles(UserHandle.myUserId()) - .map { userInfo -> userInfo to getUserProperties(userInfo.userHandle) } + val showInSettingsMap = getProfiles(UserHandle.myUserId()).groupBy { showInSettings(it) } - profileToShowInSettings - .filter { it.second.showInSettings == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT } - .takeIf { it.isNotEmpty() } - ?.map { it.first } - ?.let { userInfos -> userGroupList += UserGroup(userInfos) } + showInSettingsMap[UserProperties.SHOW_IN_SETTINGS_WITH_PARENT]?.let { + userGroupList += UserGroup(it) + } - profileToShowInSettings - .filter { it.second.showInSettings == UserProperties.SHOW_IN_SETTINGS_SEPARATE && - (!it.second.hideInSettingsInQuietMode || !it.first.isQuietModeEnabled) } - .forEach { userGroupList += UserGroup(userInfos = listOf(it.first)) } + showInSettingsMap[UserProperties.SHOW_IN_SETTINGS_SEPARATE]?.forEach { + userGroupList += UserGroup(listOf(it)) + } return userGroupList } + +private fun UserManager.showInSettings(userInfo: UserInfo): Int { + val userProperties = getUserProperties(userInfo.userHandle) + return if (userInfo.isQuietModeEnabled && userProperties.hideInSettingsInQuietMode) { + UserProperties.SHOW_IN_SETTINGS_NO + } else { + userProperties.showInSettings + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt new file mode 100644 index 000000000000..dfaf3c66ff8d --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt @@ -0,0 +1,80 @@ +/* + * 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.settingslib.spaprivileged.framework.common + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +class BroadcastReceiverFlowTest { + + private var registeredBroadcastReceiver: BroadcastReceiver? = null + + private val context = mock<Context> { + on { + registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_NOT_EXPORTED)) + } doAnswer { + registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver + null + } + } + + @Test + fun broadcastReceiverFlow_registered() = runBlocking { + val flow = context.broadcastReceiverFlow(INTENT_FILTER) + + flow.firstWithTimeoutOrNull() + + assertThat(registeredBroadcastReceiver).isNotNull() + } + + @Test + fun broadcastReceiverFlow_isCalledOnReceive() = runBlocking { + var onReceiveIsCalled = false + launch { + context.broadcastReceiverFlow(INTENT_FILTER).first { + onReceiveIsCalled = true + true + } + } + + delay(100) + registeredBroadcastReceiver!!.onReceive(context, Intent()) + delay(100) + + assertThat(onReceiveIsCalled).isTrue() + } + + private companion object { + val INTENT_FILTER = IntentFilter() + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt new file mode 100644 index 000000000000..e450364a9ab2 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt @@ -0,0 +1,73 @@ +/* + * 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.settingslib.spaprivileged.template.common + +import android.content.Context +import android.content.pm.UserInfo +import android.content.pm.UserProperties +import android.os.UserManager +import androidx.compose.material3.Text +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spaprivileged.framework.common.userManager +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy + +@RunWith(AndroidJUnit4::class) +class UserProfilePagerTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val mockUserManager = mock<UserManager> { + on { getProfiles(any()) } doReturn listOf(USER_0) + on { getUserProperties(USER_0.userHandle) } doReturn + UserProperties.Builder() + .setShowInSettings(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT) + .build() + } + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { userManager } doReturn mockUserManager + } + + @Test + fun userProfilePager() { + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + UserProfilePager { userGroup -> + Text(text = userGroup.userInfos.joinToString { it.id.toString() }) + } + } + } + + composeTestRule.onNodeWithText(USER_0.id.toString()).assertIsDisplayed() + } + + private companion object { + val USER_0 = UserInfo(0, "", 0) + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index e9f4b5c8efc6..245fe6edd601 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1310,7 +1310,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> // Set default string with battery level in device connected situation. if (isTwsBatteryAvailable(leftBattery, rightBattery)) { stringRes = R.string.bluetooth_battery_level_untethered; - } else if (batteryLevelPercentageString != null) { + } else if (batteryLevelPercentageString != null && !shortSummary) { stringRes = R.string.bluetooth_battery_level; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 7e8fe7e09d74..d9fe7335dbcb 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -445,5 +445,6 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_SUPPORTED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 2e174e267bde..c0d83c4c08d5 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -686,7 +686,8 @@ public class SettingsBackupTest { Settings.Global.Wearable.PHONE_SWITCHING_SUPPORTED, Settings.Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE, Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE, - Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED); + Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, + Settings.Global.Wearable.CONNECTIVITY_KEEP_DATA_ON); private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS = newHashSet( diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 0e9f8b153fd4..80fd51643b98 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -294,8 +294,6 @@ filegroup { "tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt", "tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt", "tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt", - "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt", - "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt", "tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt", "tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt", // Keyguard helper diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index aebdaaba3ce1..e340209afbb8 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -107,8 +107,16 @@ flag { } flag { - name: "qs_new_pipeline" - namespace: "systemui" - description: "Use the new pipeline for Quick Settings. Should have no behavior changes." - bug: "241772429" + name: "qs_new_pipeline" + namespace: "systemui" + description: "Use the new pipeline for Quick Settings. Should have no behavior changes." + bug: "241772429" } + +flag { + name: "coroutine_tracing" + namespace: "systemui" + description: "Adds thread-local data to System UI's global coroutine scopes to " + "allow for tracing of coroutine continuations using System UI's tracinglib" + bug: "289353932" +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index 33c084e424f6..7eb7dacda255 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -78,7 +78,10 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.times import com.android.compose.PlatformButton import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.SceneKey as SceneTransitionLayoutSceneKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.thenIf import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel @@ -90,6 +93,8 @@ import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.common.ui.compose.Icon import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.fold.ui.composable.FoldPosture +import com.android.systemui.fold.ui.composable.foldPosture import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey @@ -159,28 +164,27 @@ private fun SceneScope.BouncerScene( when (layout) { Layout.STANDARD -> - Bouncer( + StandardLayout( viewModel = viewModel, dialogFactory = dialogFactory, - userInputAreaVisibility = UserInputAreaVisibility.FULL, modifier = childModifier, ) Layout.SIDE_BY_SIDE -> - SideBySide( + SideBySideLayout( viewModel = viewModel, dialogFactory = dialogFactory, isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, modifier = childModifier, ) Layout.STACKED -> - Stacked( + StackedLayout( viewModel = viewModel, dialogFactory = dialogFactory, isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, modifier = childModifier, ) Layout.SPLIT -> - Split( + SplitLayout( viewModel = viewModel, dialogFactory = dialogFactory, modifier = childModifier, @@ -194,59 +198,146 @@ private fun SceneScope.BouncerScene( * authentication attempt, including all messaging UI (directives, reasoning, errors, etc.). */ @Composable -private fun Bouncer( +private fun StandardLayout( viewModel: BouncerViewModel, dialogFactory: BouncerSceneDialogFactory, - userInputAreaVisibility: UserInputAreaVisibility, modifier: Modifier = Modifier, + outputOnly: Boolean = false, ) { - val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() - val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() - var dialog: Dialog? by remember { mutableStateOf(null) } - val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 0.dp) + val foldPosture: FoldPosture by foldPosture() + val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState() + val isSplitAroundTheFold = + foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired + val currentSceneKey = + if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey + + SceneTransitionLayout( + currentScene = currentSceneKey, + onChangeScene = {}, + transitions = SceneTransitions, + modifier = modifier, ) { - Crossfade( - targetState = message, - label = "Bouncer message", - animationSpec = if (message.isUpdateAnimated) tween() else snap(), - ) { message -> - Text( - text = message.text, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, + scene(SceneKeys.ContiguousSceneKey) { + FoldSplittable( + viewModel = viewModel, + dialogFactory = dialogFactory, + outputOnly = outputOnly, + isSplit = false, ) } - Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) - - Box(Modifier.weight(1f)) { - UserInputArea( + scene(SceneKeys.SplitSceneKey) { + FoldSplittable( viewModel = viewModel, - visibility = userInputAreaVisibility, - modifier = Modifier.align(Alignment.Center), + dialogFactory = dialogFactory, + outputOnly = outputOnly, + isSplit = true, ) } + } +} + +/** + * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user + * switcher UI) and laid out vertically, centered horizontally. + * + * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't + * render across the location of the fold hardware when the device is fully or part-way unfolded + * with the fold hinge in a horizontal position. + * + * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN + * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter + * their PIN or pattern. + */ +@Composable +private fun SceneScope.FoldSplittable( + viewModel: BouncerViewModel, + dialogFactory: BouncerSceneDialogFactory, + outputOnly: Boolean, + isSplit: Boolean, + modifier: Modifier = Modifier, +) { + val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() + val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() + var dialog: Dialog? by remember { mutableStateOf(null) } + val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() + val splitRatio = + LocalContext.current.resources.getFloat( + R.dimen.motion_layout_half_fold_bouncer_height_ratio + ) - Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) + Column(modifier = modifier.padding(horizontal = 32.dp)) { + // Content above the fold, when split on a foldable device in a "table top" posture: + Box( + modifier = + Modifier.element(SceneElements.AboveFold).fillMaxWidth().thenIf(isSplit) { + Modifier.weight(splitRatio) + }, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth().padding(top = 92.dp), + ) { + Crossfade( + targetState = message, + label = "Bouncer message", + animationSpec = if (message.isUpdateAnimated) tween() else snap(), + ) { message -> + Text( + text = message.text, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + ) + } - val actionButtonModifier = Modifier.height(56.dp) + Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) - actionButton.let { actionButtonViewModel -> - if (actionButtonViewModel != null) { - BouncerActionButton( - viewModel = actionButtonViewModel, - modifier = actionButtonModifier, + UserInputArea( + viewModel = viewModel, + visibility = UserInputAreaVisibility.OUTPUT_ONLY, ) - } else { - Spacer(modifier = actionButtonModifier) } } - Spacer(Modifier.height(48.dp)) + // Content below the fold, when split on a foldable device in a "table top" posture: + Box( + modifier = + Modifier.element(SceneElements.BelowFold).fillMaxWidth().thenIf(isSplit) { + Modifier.weight(1 - splitRatio) + }, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + if (!outputOnly) { + Box(Modifier.weight(1f)) { + UserInputArea( + viewModel = viewModel, + visibility = UserInputAreaVisibility.INPUT_ONLY, + modifier = Modifier.align(Alignment.Center), + ) + } + } + + Spacer(Modifier.heightIn(min = 21.dp, max = 48.dp)) + + val actionButtonModifier = Modifier.height(56.dp) + + actionButton.let { actionButtonViewModel -> + if (actionButtonViewModel != null) { + BouncerActionButton( + viewModel = actionButtonViewModel, + modifier = actionButtonModifier, + ) + } else { + Spacer(modifier = actionButtonModifier) + } + } + + Spacer(Modifier.height(48.dp)) + } + } if (dialogMessage != null) { if (dialog == null) { @@ -288,8 +379,8 @@ private fun UserInputArea( when (val nonNullViewModel = authMethodViewModel) { is PinBouncerViewModel -> when (visibility) { - UserInputAreaVisibility.FULL -> - PinBouncer( + UserInputAreaVisibility.OUTPUT_ONLY -> + PinInputDisplay( viewModel = nonNullViewModel, modifier = modifier, ) @@ -298,34 +389,20 @@ private fun UserInputArea( viewModel = nonNullViewModel, modifier = modifier, ) - UserInputAreaVisibility.OUTPUT_ONLY -> - PinInputDisplay( - viewModel = nonNullViewModel, - modifier = modifier, - ) - UserInputAreaVisibility.NONE -> {} } is PasswordBouncerViewModel -> - when (visibility) { - UserInputAreaVisibility.FULL, - UserInputAreaVisibility.INPUT_ONLY -> - PasswordBouncer( - viewModel = nonNullViewModel, - modifier = modifier, - ) - else -> {} + if (visibility == UserInputAreaVisibility.INPUT_ONLY) { + PasswordBouncer( + viewModel = nonNullViewModel, + modifier = modifier, + ) } is PatternBouncerViewModel -> - when (visibility) { - UserInputAreaVisibility.FULL, - UserInputAreaVisibility.INPUT_ONLY -> - PatternBouncer( - viewModel = nonNullViewModel, - modifier = - Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false) - .then(modifier) - ) - else -> {} + if (visibility == UserInputAreaVisibility.INPUT_ONLY) { + PatternBouncer( + viewModel = nonNullViewModel, + modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false) + ) } else -> Unit } @@ -492,17 +569,17 @@ private fun UserSwitcherDropdownMenu( * by double-tapping on the side. */ @Composable -private fun Split( +private fun SplitLayout( viewModel: BouncerViewModel, dialogFactory: BouncerSceneDialogFactory, modifier: Modifier = Modifier, ) { SwappableLayout( startContent = { startContentModifier -> - Bouncer( + StandardLayout( viewModel = viewModel, dialogFactory = dialogFactory, - userInputAreaVisibility = UserInputAreaVisibility.OUTPUT_ONLY, + outputOnly = true, modifier = startContentModifier, ) }, @@ -595,7 +672,7 @@ private fun SwappableLayout( * rendering of the bouncer will be used instead of the side-by-side layout. */ @Composable -private fun SideBySide( +private fun SideBySideLayout( viewModel: BouncerViewModel, dialogFactory: BouncerSceneDialogFactory, isUserSwitcherVisible: Boolean, @@ -615,10 +692,9 @@ private fun SideBySide( } }, endContent = { endContentModifier -> - Bouncer( + StandardLayout( viewModel = viewModel, dialogFactory = dialogFactory, - userInputAreaVisibility = UserInputAreaVisibility.FULL, modifier = endContentModifier, ) }, @@ -628,7 +704,7 @@ private fun SideBySide( /** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */ @Composable -private fun Stacked( +private fun StackedLayout( viewModel: BouncerViewModel, dialogFactory: BouncerSceneDialogFactory, isUserSwitcherVisible: Boolean, @@ -644,10 +720,9 @@ private fun Stacked( ) } - Bouncer( + StandardLayout( viewModel = viewModel, dialogFactory = dialogFactory, - userInputAreaVisibility = UserInputAreaVisibility.FULL, modifier = Modifier.fillMaxWidth().weight(1f), ) } @@ -708,11 +783,6 @@ private enum class Layout { /** Enumerates all supported user-input area visibilities. */ private enum class UserInputAreaVisibility { /** - * The entire user input area is shown, including where the user enters input and where it's - * reflected to the user. - */ - FULL, - /** * Only the area where the user enters the input is shown; the area where the input is reflected * back to the user is not shown. */ @@ -722,8 +792,6 @@ private enum class UserInputAreaVisibility { * input is entered by the user is not shown. */ OUTPUT_ONLY, - /** The entire user input area is hidden. */ - NONE, } /** @@ -758,3 +826,17 @@ private fun animatedAlpha( private val SelectedUserImageSize = 190.dp private val UserSwitcherDropdownWidth = SelectedUserImageSize + 2 * 29.dp private val UserSwitcherDropdownHeight = 60.dp + +private object SceneKeys { + val ContiguousSceneKey = SceneTransitionLayoutSceneKey("default") + val SplitSceneKey = SceneTransitionLayoutSceneKey("split") +} + +private object SceneElements { + val AboveFold = ElementKey("above_fold") + val BelowFold = ElementKey("below_fold") +} + +private val SceneTransitions = transitions { + from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 5b9ad4de8c84..243751fafe5d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -24,14 +24,10 @@ import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween -import androidx.compose.foundation.gestures.awaitEachGesture -import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.sizeIn import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -69,38 +65,19 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Composable -internal fun PinBouncer( +fun PinPad( viewModel: PinBouncerViewModel, modifier: Modifier = Modifier, ) { // Report that the UI is shown to let the view-model run some logic. LaunchedEffect(Unit) { viewModel.onShown() } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = - modifier.pointerInput(Unit) { - awaitEachGesture { - awaitFirstDown() - viewModel.onDown() - } - } - ) { - PinInputDisplay(viewModel) - Spacer(Modifier.heightIn(min = 34.dp, max = 48.dp)) - PinPad(viewModel) - } -} - -@Composable -fun PinPad( - viewModel: PinBouncerViewModel, - modifier: Modifier = Modifier, -) { val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState() val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState() val animateFailure: Boolean by viewModel.animateFailure.collectAsState() + val isDigitButtonAnimationEnabled: Boolean by + viewModel.isDigitButtonAnimationEnabled.collectAsState() val buttonScaleAnimatables = remember { List(12) { Animatable(1f) } } LaunchedEffect(animateFailure) { @@ -119,10 +96,11 @@ fun PinPad( ) { repeat(9) { index -> DigitButton( - index + 1, - isInputEnabled, - viewModel::onPinButtonClicked, - buttonScaleAnimatables[index]::value, + digit = index + 1, + isInputEnabled = isInputEnabled, + onClicked = viewModel::onPinButtonClicked, + scaling = buttonScaleAnimatables[index]::value, + isAnimationEnabled = isDigitButtonAnimationEnabled, ) } @@ -141,10 +119,11 @@ fun PinPad( ) DigitButton( - 0, - isInputEnabled, - viewModel::onPinButtonClicked, - buttonScaleAnimatables[10]::value, + digit = 0, + isInputEnabled = isInputEnabled, + onClicked = viewModel::onPinButtonClicked, + scaling = buttonScaleAnimatables[10]::value, + isAnimationEnabled = isDigitButtonAnimationEnabled, ) ActionButton( @@ -168,15 +147,17 @@ private fun DigitButton( isInputEnabled: Boolean, onClicked: (Int) -> Unit, scaling: () -> Float, + isAnimationEnabled: Boolean, ) { PinPadButton( onClicked = { onClicked(digit) }, isEnabled = isInputEnabled, backgroundColor = MaterialTheme.colorScheme.surfaceVariant, foregroundColor = MaterialTheme.colorScheme.onSurfaceVariant, + isAnimationEnabled = isAnimationEnabled, modifier = Modifier.graphicsLayer { - val scale = scaling() + val scale = if (isAnimationEnabled) scaling() else 1f scaleX = scale scaleY = scale } @@ -220,6 +201,7 @@ private fun ActionButton( isEnabled = isInputEnabled && !isHidden, backgroundColor = backgroundColor, foregroundColor = foregroundColor, + isAnimationEnabled = true, modifier = Modifier.graphicsLayer { alpha = hiddenAlpha @@ -241,6 +223,7 @@ private fun PinPadButton( isEnabled: Boolean, backgroundColor: Color, foregroundColor: Color, + isAnimationEnabled: Boolean, modifier: Modifier = Modifier, onLongPressed: (() -> Unit)? = null, content: @Composable (contentColor: () -> Color) -> Unit, @@ -268,7 +251,7 @@ private fun PinPadButton( val cornerRadius: Dp by animateDpAsState( - if (isPressed) 24.dp else pinButtonSize / 2, + if (isAnimationEnabled && isPressed) 24.dp else pinButtonSize / 2, label = "PinButton round corners", animationSpec = tween(animDurationMillis, easing = animEasing) ) @@ -276,7 +259,7 @@ private fun PinPadButton( val containerColor: Color by animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.primary + isAnimationEnabled && isPressed -> MaterialTheme.colorScheme.primary else -> backgroundColor }, label = "Pin button container color", @@ -285,7 +268,7 @@ private fun PinPadButton( val contentColor = animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.onPrimary + isAnimationEnabled && isPressed -> MaterialTheme.colorScheme.onPrimary else -> foregroundColor }, label = "Pin button container color", @@ -298,7 +281,8 @@ private fun PinPadButton( contentAlignment = Alignment.Center, modifier = modifier - .size(pinButtonSize) + .sizeIn(maxWidth = pinButtonSize, maxHeight = pinButtonSize) + .aspectRatio(1f) .drawBehind { drawRoundRect( color = containerColor, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 37804682ed98..09706bed1921 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -12,7 +12,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -25,10 +24,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.FixedSizeEdgeDetector import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.transitions import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.ui.viewmodel.CommunalViewModel @@ -76,17 +77,24 @@ fun CommunalContainer( currentScene = currentScene, onChangeScene = { sceneKey -> viewModel.onSceneChanged(sceneKey.toCommunalSceneKey()) }, transitions = sceneTransitions, + edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize) ) { scene( TransitionSceneKey.Blank, - userActions = mapOf(Swipe.Left to TransitionSceneKey.Communal) + userActions = + mapOf( + Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to TransitionSceneKey.Communal + ) ) { BlankScene { showSceneTransitionLayout = false } } scene( TransitionSceneKey.Communal, - userActions = mapOf(Swipe.Right to TransitionSceneKey.Blank), + userActions = + mapOf( + Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TransitionSceneKey.Blank + ), ) { CommunalScene(viewModel, modifier = modifier) } @@ -105,14 +113,12 @@ private fun BlankScene( Box(modifier.fillMaxSize()) { Column( Modifier.fillMaxHeight() - .width(100.dp) + .width(ContainerDimensions.EdgeSwipeSize) .align(Alignment.CenterEnd) .background(Color(0x55e9f2eb)), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("Default scene") - IconButton(onClick = hideSceneTransitionLayout) { Icon(Icons.Filled.Close, contentDescription = "Close button") } @@ -142,3 +148,7 @@ fun CommunalSceneKey.toTransitionSceneKey(): SceneKey { fun SceneKey.toCommunalSceneKey(): CommunalSceneKey { return this.identity as CommunalSceneKey } + +object ContainerDimensions { + val EdgeSwipeSize = 40.dp +} 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 142978284cfb..6e18cb9cd46b 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 @@ -22,10 +22,11 @@ import android.widget.FrameLayout import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan @@ -42,6 +43,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -65,6 +67,7 @@ fun CommunalHub( LazyHorizontalGrid( modifier = modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart), rows = GridCells.Fixed(CommunalContentSize.FULL.span), + contentPadding = PaddingValues(horizontal = Dimensions.Spacing), horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing), verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing), ) { @@ -92,6 +95,16 @@ fun CommunalHub( LocalContext.current.getString(R.string.button_to_open_widget_picker) ) } + + // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving + // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge + // swipe back to the blank scene. + Spacer( + Modifier.height(Dimensions.GridHeight) + .align(Alignment.CenterStart) + .width(Dimensions.Spacing) + .pointerInput(Unit) {} + ) } } @@ -164,11 +177,7 @@ private fun TutorialContent(modifier: Modifier = Modifier) { @Composable private fun Umo(viewModel: CommunalViewModel, modifier: Modifier = Modifier) { AndroidView( - modifier = - modifier - .width(Dimensions.CardWidth) - .height(Dimensions.CardHeightThird) - .padding(Dimensions.Spacing), + modifier = modifier, factory = { viewModel.mediaHost.expansion = MediaHostState.EXPANDED viewModel.mediaHost.showsOnlyActiveMedia = false diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt new file mode 100644 index 000000000000..1c993cf6206c --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt @@ -0,0 +1,79 @@ +/* + * 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.fold.ui.composable + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.window.layout.FoldingFeature +import androidx.window.layout.WindowInfoTracker + +sealed interface FoldPosture { + /** A foldable device that's fully closed/folded or a device that doesn't support folding. */ + data object Folded : FoldPosture + /** A foldable that's halfway open with the hinge held vertically. */ + data object Book : FoldPosture + /** A foldable that's halfway open with the hinge held horizontally. */ + data object Tabletop : FoldPosture + /** A foldable that's fully unfolded / flat. */ + data object FullyUnfolded : FoldPosture +} + +/** Returns the [FoldPosture] of the device currently. */ +@Composable +fun foldPosture(): State<FoldPosture> { + val context = LocalContext.current + val infoTracker = remember(context) { WindowInfoTracker.getOrCreate(context) } + val layoutInfo by infoTracker.windowLayoutInfo(context).collectAsState(initial = null) + + return produceState<FoldPosture>( + initialValue = FoldPosture.Folded, + key1 = layoutInfo, + ) { + value = + layoutInfo + ?.displayFeatures + ?.firstNotNullOfOrNull { it as? FoldingFeature } + .let { foldingFeature -> + when (foldingFeature?.state) { + null -> FoldPosture.Folded + FoldingFeature.State.HALF_OPENED -> + foldingFeature.orientation.toHalfwayPosture() + FoldingFeature.State.FLAT -> + if (foldingFeature.isSeparating) { + // Dual screen device. + foldingFeature.orientation.toHalfwayPosture() + } else { + FoldPosture.FullyUnfolded + } + else -> error("Unsupported state \"${foldingFeature.state}\"") + } + } + } +} + +private fun FoldingFeature.Orientation.toHalfwayPosture(): FoldPosture { + return when (this) { + FoldingFeature.Orientation.HORIZONTAL -> FoldPosture.Tabletop + FoldingFeature.Orientation.VERTICAL -> FoldPosture.Book + else -> error("Unsupported orientation \"$this\"") + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index aae61bd0f554..6153e199c24e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -18,7 +18,6 @@ package com.android.compose.animation.scene import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.movableContentOf @@ -36,7 +35,6 @@ import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.geometry.lerp import androidx.compose.ui.graphics.drawscope.scale -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.IntermediateMeasureScope import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.Placeable @@ -47,7 +45,6 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.round import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.SharedElementTransformation -import com.android.compose.modifiers.thenIf import com.android.compose.ui.util.lerp /** An element on screen, that can be composed in one or more scenes. */ @@ -146,8 +143,6 @@ internal fun Modifier.element( element } - val lastSharedValues = element.lastSharedValues - val lastSceneValues = sceneValues.lastValues DisposableEffect(scene, sceneValues, element) { onDispose { @@ -160,18 +155,6 @@ internal fun Modifier.element( } } - val alpha = - remember(layoutImpl, element, scene, sceneValues) { - derivedStateOf { elementAlpha(layoutImpl, element, scene, sceneValues) } - } - val isOpaque by remember(alpha) { derivedStateOf { alpha.value == 1f } } - SideEffect { - if (isOpaque) { - lastSharedValues.alpha = 1f - lastSceneValues.alpha = 1f - } - } - val drawScale by remember(layoutImpl, element, scene, sceneValues) { derivedStateOf { getDrawScale(layoutImpl, element, scene, sceneValues) } @@ -200,14 +183,6 @@ internal fun Modifier.element( place(layoutImpl, scene, element, sceneValues, placeable, placementScope = this) } } - .thenIf(!isOpaque) { - Modifier.graphicsLayer { - val alpha = alpha.value - this.alpha = alpha - lastSharedValues.alpha = alpha - lastSceneValues.alpha = alpha - } - } .testTag(key.testTag) } @@ -325,6 +300,61 @@ private fun Modifier.modifierTransformations( } } +/** + * Whether the element is opaque or not. + * + * Important: The logic here should closely match the logic in [elementAlpha]. Note that we don't + * reuse [elementAlpha] and simply check if alpha == 1f because [isElementOpaque] is checked during + * placement and we don't want to read the transition progress in that phase. + */ +private fun isElementOpaque( + layoutImpl: SceneTransitionLayoutImpl, + element: Element, + scene: Scene, + sceneValues: Element.TargetValues, +): Boolean { + val state = layoutImpl.state.transitionState + + if (state !is TransitionState.Transition || state.fromScene == state.toScene) { + return true + } + + if (!layoutImpl.isTransitionReady(state)) { + val lastValue = + sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified } + ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f + + return lastValue == 1f + } + + val fromScene = state.fromScene + val toScene = state.toScene + val fromValues = element.sceneValues[fromScene] + val toValues = element.sceneValues[toScene] + + if (fromValues == null && toValues == null) { + error("This should not happen, element $element is neither in $fromScene or $toScene") + } + + val isSharedElement = fromValues != null && toValues != null + if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { + return true + } + + return layoutImpl.transitions + .transitionSpec(fromScene, toScene) + .transformations(element.key, scene.key) + .alpha == null +} + +/** + * Whether the element is opaque or not. + * + * Important: The logic here should closely match the logic in [isElementOpaque]. Note that we don't + * reuse [elementAlpha] in [isElementOpaque] and simply check if alpha == 1f because + * [isElementOpaque] is checked during placement and we don't want to read the transition progress + * in that phase. + */ private fun elementAlpha( layoutImpl: SceneTransitionLayoutImpl, element: Element, @@ -446,6 +476,8 @@ private fun IntermediateMeasureScope.place( } val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero) + val lastSharedValues = element.lastSharedValues + val lastValues = sceneValues.lastValues val targetOffset = computeValue( layoutImpl, @@ -456,16 +488,31 @@ private fun IntermediateMeasureScope.place( idleValue = targetOffsetInScene, currentValue = { currentOffset }, lastValue = { - sceneValues.lastValues.offset.takeIf { it.isSpecified } - ?: element.lastSharedValues.offset.takeIf { it.isSpecified } - ?: currentOffset + lastValues.offset.takeIf { it.isSpecified } + ?: lastSharedValues.offset.takeIf { it.isSpecified } ?: currentOffset }, ::lerp, ) - element.lastSharedValues.offset = targetOffset - sceneValues.lastValues.offset = targetOffset - placeable.place((targetOffset - currentOffset).round()) + lastSharedValues.offset = targetOffset + lastValues.offset = targetOffset + + val offset = (targetOffset - currentOffset).round() + if (isElementOpaque(layoutImpl, element, scene, sceneValues)) { + // TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is not + // animated once b/305195729 is fixed. Test that drawing is not invalidated in that + // case. + placeable.place(offset) + lastSharedValues.alpha = 1f + lastValues.alpha = 1f + } else { + placeable.placeWithLayer(offset) { + val alpha = elementAlpha(layoutImpl, element, scene, sceneValues) + this.alpha = alpha + lastSharedValues.alpha = alpha + lastValues.alpha = alpha + } + } } } @@ -506,7 +553,10 @@ private inline fun <T> computeValue( // There is no ongoing transition. if (state !is TransitionState.Transition || state.fromScene == state.toScene) { - return idleValue + // Even if this element SceneTransitionLayout is not animated, the layout itself might be + // animated (e.g. by another parent SceneTransitionLayout), in which case this element still + // need to participate in the layout phase. + return currentValue() } // A transition was started but it's not ready yet (not all elements have been composed/laid @@ -524,21 +574,17 @@ private inline fun <T> computeValue( error("This should not happen, element $element is neither in $fromScene or $toScene") } - // TODO(b/291053278): Handle overscroll correctly. We should probably coerce between [0f, 1f] - // here and consume overflows at drawing time, somehow reusing Compose OverflowEffect or some - // similar mechanism. - val transitionProgress = state.progress - // The element is shared: interpolate between the value in fromScene and the value in toScene. // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared // elements follow the finger direction. val isSharedElement = fromValues != null && toValues != null if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { - return lerp( - sceneValue(fromValues!!), - sceneValue(toValues!!), - transitionProgress, - ) + val start = sceneValue(fromValues!!) + val end = sceneValue(toValues!!) + + // Make sure we don't read progress if values are the same and we don't need to interpolate, + // so we don't invalidate the phase where this is read. + return if (start == end) start else lerp(start, end, state.progress) } val transformation = @@ -573,8 +619,15 @@ private inline fun <T> computeValue( idleValue, ) + // Make sure we don't read progress if values are the same and we don't need to interpolate, so + // we don't invalidate the phase where this is read. + if (targetValue == idleValue) { + return targetValue + } + + val progress = state.progress // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range. - val rangeProgress = transformation.range?.progress(transitionProgress) ?: transitionProgress + val rangeProgress = transformation.range?.progress(progress) ?: progress // Interpolate between the value at rest and the value before entering/after leaving. val isEntering = scene.key == toScene diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt index 216608aff0cb..5d8eaf7f3d15 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt @@ -2,9 +2,10 @@ package com.android.compose.animation.scene import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.unit.IntSize interface DraggableHandler { - fun onDragStarted(startedPosition: Offset, pointersDown: Int = 1) + fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int = 1) fun onDelta(pixels: Float) fun onDragStopped(velocity: Float) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index d0a5f5bfebc0..d48781a4529b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.input.pointer.util.addPointerInputChange import androidx.compose.ui.platform.LocalViewConfiguration +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.Velocity import androidx.compose.ui.util.fastForEach @@ -60,7 +61,7 @@ internal fun Modifier.multiPointerDraggable( orientation: Orientation, enabled: Boolean, startDragImmediately: Boolean, - onDragStarted: (startedPosition: Offset, pointersDown: Int) -> Unit, + onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit, onDragDelta: (Float) -> Unit, onDragStopped: (velocity: Float) -> Unit, ): Modifier = composed { @@ -83,7 +84,7 @@ internal fun Modifier.multiPointerDraggable( val onDragStart: (Offset, Int) -> Unit = { startedPosition, pointersDown -> velocityTracker.resetTracking() - onDragStarted(startedPosition, pointersDown) + onDragStarted(size, startedPosition, pointersDown) } val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index eb5168bdd3cb..1a79522da05d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -25,8 +25,9 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.onPlaced +import androidx.compose.ui.layout.intermediateLayout import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.IntSize import androidx.compose.ui.zIndex @@ -44,14 +45,24 @@ internal class Scene( var content by mutableStateOf(content) var userActions by mutableStateOf(actions) var zIndex by mutableFloatStateOf(zIndex) - var size by mutableStateOf(IntSize.Zero) + var targetSize by mutableStateOf(IntSize.Zero) /** The shared values in this scene that are not tied to a specific element. */ val sharedValues = SnapshotStateMap<ValueKey, Element.SharedValue<*>>() @Composable + @OptIn(ExperimentalComposeUiApi::class) fun Content(modifier: Modifier = Modifier) { - Box(modifier.zIndex(zIndex).onPlaced { size = it.size }.testTag(key.testTag)) { + Box( + modifier + .zIndex(zIndex) + .intermediateLayout { measurable, constraints -> + targetSize = lookaheadSize + val placeable = measurable.measure(constraints) + layout(placeable.width, placeable.height) { placeable.place(0, 0) } + } + .testTag(key.testTag) + ) { scope.content() } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt index 9a3a0aef30cb..838cb3bd5fba 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt @@ -27,6 +27,7 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round @@ -38,13 +39,13 @@ import kotlinx.coroutines.launch @VisibleForTesting class SceneGestureHandler( - private val layoutImpl: SceneTransitionLayoutImpl, + internal val layoutImpl: SceneTransitionLayoutImpl, internal val orientation: Orientation, private val coroutineScope: CoroutineScope, ) { val draggable: DraggableHandler = SceneDraggableHandler(this) - private var transitionState + internal var transitionState get() = layoutImpl.state.transitionState set(value) { layoutImpl.state.transitionState = value @@ -57,7 +58,7 @@ class SceneGestureHandler( * Note: the initialScene here does not matter, it's only used for initializing the transition * and will be replaced when a drag event starts. */ - private val swipeTransition = SwipeTransition(initialScene = currentScene) + internal val swipeTransition = SwipeTransition(initialScene = currentScene) internal val currentScene: Scene get() = layoutImpl.scene(transitionState.currentScene) @@ -90,7 +91,7 @@ class SceneGestureHandler( internal var gestureWithPriority: Any? = null - internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?) { + internal fun onDragStarted(pointersDown: Int, layoutSize: IntSize, startedPosition: Offset?) { if (isDrivingTransition) { // This [transition] was already driving the animation: simply take over it. // Stop animating and start from where the current offset. @@ -126,14 +127,14 @@ class SceneGestureHandler( // we will also have to make sure that we correctly handle overscroll. swipeTransition.absoluteDistance = when (orientation) { - Orientation.Horizontal -> layoutImpl.size.width - Orientation.Vertical -> layoutImpl.size.height + Orientation.Horizontal -> layoutSize.width + Orientation.Vertical -> layoutSize.height }.toFloat() val fromEdge = startedPosition?.let { position -> layoutImpl.edgeDetector.edge( - layoutImpl.size, + layoutSize, position.round(), layoutImpl.density, orientation, @@ -414,7 +415,7 @@ class SceneGestureHandler( } } - private class SwipeTransition(initialScene: Scene) : TransitionState.Transition { + internal class SwipeTransition(initialScene: Scene) : TransitionState.Transition { var _currentScene by mutableStateOf(initialScene) override val currentScene: SceneKey get() = _currentScene.key @@ -513,9 +514,9 @@ class SceneGestureHandler( private class SceneDraggableHandler( private val gestureHandler: SceneGestureHandler, ) : DraggableHandler { - override fun onDragStarted(startedPosition: Offset, pointersDown: Int) { + override fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) { gestureHandler.gestureWithPriority = this - gestureHandler.onDragStarted(pointersDown, startedPosition) + gestureHandler.onDragStarted(pointersDown, layoutSize, startedPosition) } override fun onDelta(pixels: Float) { @@ -597,9 +598,29 @@ class SceneNestedScrollHandler( return PriorityNestedScrollConnection( canStartPreScroll = { offsetAvailable, offsetBeforeStart -> canChangeScene = offsetBeforeStart == Offset.Zero - gestureHandler.isDrivingTransition && + + val canInterceptSwipeTransition = canChangeScene && - offsetAvailable.toAmount() != 0f + gestureHandler.isDrivingTransition && + offsetAvailable.toAmount() != 0f + if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false + + val progress = gestureHandler.swipeTransition.progress + val threshold = gestureHandler.layoutImpl.transitionInterceptionThreshold + fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold + + // The transition is always between 0 and 1. If it is close to either of these + // intervals, we want to go directly to the TransitionState.Idle. + // The progress value can go beyond this range in the case of overscroll. + val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f) + if (shouldSnapToIdle) { + gestureHandler.swipeTransition.stopOffsetAnimation() + gestureHandler.transitionState = + TransitionState.Idle(gestureHandler.swipeTransition.currentScene) + } + + // Start only if we cannot consume this event + !shouldSnapToIdle }, canStartPostScroll = { offsetAvailable, offsetBeforeStart -> val amount = offsetAvailable.toAmount() @@ -647,7 +668,11 @@ class SceneNestedScrollHandler( canContinueScroll = { true }, onStart = { gestureHandler.gestureWithPriority = this - gestureHandler.onDragStarted(pointersDown = 1, startedPosition = null) + gestureHandler.onDragStarted( + pointersDown = 1, + layoutSize = gestureHandler.currentScene.targetSize, + startedPosition = null, + ) }, onScroll = { offsetAvailable -> if (gestureHandler.gestureWithPriority != this) { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index efdfe7a7921e..9c31445e1b96 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -16,6 +16,7 @@ package com.android.compose.animation.scene +import androidx.annotation.FloatRange import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable import androidx.compose.runtime.State @@ -41,6 +42,8 @@ import androidx.compose.ui.platform.LocalDensity * @param transitions the definition of the transitions used to animate a change of scene. * @param state the observable state of this layout. * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any. + * @param transitionInterceptionThreshold used during a scene transition. For the scene to be + * intercepted, the progress value must be above the threshold, and below (1 - threshold). * @param scenes the configuration of the different scenes of this layout. */ @Composable @@ -51,6 +54,7 @@ fun SceneTransitionLayout( modifier: Modifier = Modifier, state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) }, edgeDetector: EdgeDetector = DefaultEdgeDetector, + @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f, scenes: SceneTransitionLayoutScope.() -> Unit, ) { val density = LocalDensity.current @@ -63,6 +67,7 @@ fun SceneTransitionLayout( state = state, density = density, edgeDetector = edgeDetector, + transitionInterceptionThreshold = transitionInterceptionThreshold, coroutineScope = coroutineScope, ) } @@ -71,6 +76,7 @@ fun SceneTransitionLayout( layoutImpl.transitions = transitions layoutImpl.density = density layoutImpl.edgeDetector = edgeDetector + layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold layoutImpl.setScenes(scenes) layoutImpl.setCurrentScene(currentScene) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 6edd1b6b923d..94f2737039f4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -30,13 +30,15 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.layout.LookaheadScope -import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.layout.intermediateLayout import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize import androidx.compose.ui.util.fastForEach +import com.android.compose.ui.util.lerp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel @@ -48,6 +50,7 @@ class SceneTransitionLayoutImpl( internal val state: SceneTransitionLayoutState, density: Density, edgeDetector: EdgeDetector, + transitionInterceptionThreshold: Float, coroutineScope: CoroutineScope, ) { internal val scenes = SnapshotStateMap<SceneKey, Scene>() @@ -60,16 +63,11 @@ class SceneTransitionLayoutImpl( internal var transitions by mutableStateOf(transitions) internal var density: Density by mutableStateOf(density) internal var edgeDetector by mutableStateOf(edgeDetector) + internal var transitionInterceptionThreshold by mutableStateOf(transitionInterceptionThreshold) private val horizontalGestureHandler: SceneGestureHandler private val verticalGestureHandler: SceneGestureHandler - /** - * The size of this layout. Note that this could be [IntSize.Zero] if this layour does not have - * any scene configured or right before the first measure pass of the layout. - */ - @VisibleForTesting var size by mutableStateOf(IntSize.Zero) - init { setScenes(builder) @@ -157,15 +155,46 @@ class SceneTransitionLayoutImpl( } @Composable + @OptIn(ExperimentalComposeUiApi::class) internal fun Content(modifier: Modifier) { Box( modifier // Handle horizontal and vertical swipes on this layout. // Note: order here is important and will give a slight priority to the vertical // swipes. - .swipeToScene(gestureHandler(Orientation.Horizontal)) - .swipeToScene(gestureHandler(Orientation.Vertical)) - .onSizeChanged { size = it } + .swipeToScene(horizontalGestureHandler) + .swipeToScene(verticalGestureHandler) + // Animate the size of this layout. + .intermediateLayout { measurable, constraints -> + // Measure content normally. + val placeable = measurable.measure(constraints) + + val width: Int + val height: Int + val state = state.transitionState + if (state !is TransitionState.Transition || state.fromScene == state.toScene) { + width = placeable.width + height = placeable.height + } else { + // Interpolate the size. + val fromSize = scene(state.fromScene).targetSize + val toSize = scene(state.toScene).targetSize + + // Optimization: make sure we don't read state.progress if fromSize == + // toSize to avoid running this code every frame when the layout size does + // not change. + if (fromSize == toSize) { + width = fromSize.width + height = fromSize.height + } else { + val size = lerp(fromSize, toSize, state.progress) + width = size.width.coerceAtLeast(0) + height = size.height.coerceAtLeast(0) + } + } + + layout(width, height) { placeable.place(0, 0) } + } ) { LookaheadScope { val scenesToCompose = @@ -230,4 +259,9 @@ class SceneTransitionLayoutImpl( } internal fun isSceneReady(scene: SceneKey): Boolean = readyScenes.containsKey(scene) + + @VisibleForTesting + fun setScenesTargetSizeForTest(size: IntSize) { + scenes.values.forEach { it.targetSize = size } + } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt index 840800d838db..70534dde4f6f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt @@ -38,7 +38,7 @@ internal class EdgeTranslate( transition: TransitionState.Transition, value: Offset ): Offset { - val sceneSize = scene.size + val sceneSize = scene.targetSize val elementSize = sceneValues.targetSize if (elementSize == Element.SizeUnspecified) { return value diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt new file mode 100644 index 000000000000..6401bb3d994a --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene + +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.intermediateLayout +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ElementTest { + @get:Rule val rule = createComposeRule() + + @Composable + @OptIn(ExperimentalComposeUiApi::class) + private fun SceneScope.Element( + key: ElementKey, + size: Dp, + offset: Dp, + modifier: Modifier = Modifier, + onLayout: () -> Unit = {}, + onPlacement: () -> Unit = {}, + ) { + Box( + modifier + .offset(offset) + .element(key) + .intermediateLayout { measurable, constraints -> + onLayout() + val placement = measurable.measure(constraints) + layout(placement.width, placement.height) { + onPlacement() + placement.place(0, 0) + } + } + .size(size) + ) + } + + @Test + fun staticElements_noLayout_noPlacement() { + val nFrames = 20 + val layoutSize = 100.dp + val elementSize = 50.dp + val elementOffset = 20.dp + + var fooLayouts = 0 + var fooPlacements = 0 + var barLayouts = 0 + var barPlacements = 0 + + rule.testTransition( + fromSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element( + TestElements.Foo, + elementSize, + elementOffset, + onLayout = { fooLayouts++ }, + onPlacement = { fooPlacements++ }, + ) + + // Transformed element + Element( + TestElements.Bar, + elementSize, + elementOffset, + onLayout = { barLayouts++ }, + onPlacement = { barPlacements++ }, + ) + } + }, + toSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element(TestElements.Foo, elementSize, elementOffset) + } + }, + transition = { + spec = tween(nFrames * 16) + + // no-op transformations. + translate(TestElements.Bar, x = 0.dp, y = 0.dp) + scaleSize(TestElements.Bar, width = 1f, height = 1f) + }, + ) { + var numberOfLayoutsAfterOneAnimationFrame = 0 + var numberOfPlacementsAfterOneAnimationFrame = 0 + + fun assertNumberOfLayoutsAndPlacements() { + assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + assertThat(fooPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame) + assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + assertThat(barPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame) + } + + at(16) { + // Capture the number of layouts and placements that happened after 1 animation + // frame. + numberOfLayoutsAfterOneAnimationFrame = fooLayouts + numberOfPlacementsAfterOneAnimationFrame = fooPlacements + } + repeat(nFrames - 2) { i -> + // Ensure that all animation frames (except the final one) don't relayout or replace + // static (shared or transformed) elements. + at(32L + i * 16) { assertNumberOfLayoutsAndPlacements() } + } + } + } + + @Test + fun onlyMovingElements_noLayout_onlyPlacement() { + val nFrames = 20 + val layoutSize = 100.dp + val elementSize = 50.dp + + var fooLayouts = 0 + var fooPlacements = 0 + var barLayouts = 0 + var barPlacements = 0 + + rule.testTransition( + fromSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element( + TestElements.Foo, + elementSize, + offset = 0.dp, + onLayout = { fooLayouts++ }, + onPlacement = { fooPlacements++ }, + ) + + // Transformed element + Element( + TestElements.Bar, + elementSize, + offset = 0.dp, + onLayout = { barLayouts++ }, + onPlacement = { barPlacements++ }, + ) + } + }, + toSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element(TestElements.Foo, elementSize, offset = 20.dp) + } + }, + transition = { + spec = tween(nFrames * 16) + + // Only translate Bar. + translate(TestElements.Bar, x = 20.dp, y = 20.dp) + scaleSize(TestElements.Bar, width = 1f, height = 1f) + }, + ) { + var numberOfLayoutsAfterOneAnimationFrame = 0 + var lastNumberOfPlacements = 0 + + fun assertNumberOfLayoutsAndPlacements() { + // The number of layouts have not changed. + assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + + // The number of placements have increased. + assertThat(fooPlacements).isGreaterThan(lastNumberOfPlacements) + assertThat(barPlacements).isGreaterThan(lastNumberOfPlacements) + lastNumberOfPlacements = fooPlacements + } + + at(16) { + // Capture the number of layouts and placements that happened after 1 animation + // frame. + numberOfLayoutsAfterOneAnimationFrame = fooLayouts + lastNumberOfPlacements = fooPlacements + } + repeat(nFrames - 2) { i -> + // Ensure that all animation frames (except the final one) only replaced the + // elements. + at(32L + i * 16) { assertNumberOfLayoutsAndPlacements() } + } + } + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt index 1e3d01108103..49ef31b16d73 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt @@ -46,6 +46,7 @@ import org.junit.Test import org.junit.runner.RunWith private const val SCREEN_SIZE = 100f +private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt()) @RunWith(AndroidJUnit4::class) class SceneGestureHandlerTest { @@ -68,6 +69,8 @@ class SceneGestureHandlerTest { scene(SceneC) { Text("SceneC") } } + val transitionInterceptionThreshold = 0.05f + val sceneGestureHandler = SceneGestureHandler( layoutImpl = @@ -78,9 +81,10 @@ class SceneGestureHandlerTest { state = layoutState, density = Density(1f), edgeDetector = DefaultEdgeDetector, + transitionInterceptionThreshold = transitionInterceptionThreshold, coroutineScope = coroutineScope, ) - .also { it.size = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt()) }, + .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }, orientation = Orientation.Vertical, coroutineScope = coroutineScope, ) @@ -106,6 +110,9 @@ class SceneGestureHandlerTest { val transitionState: TransitionState get() = layoutState.transitionState + val progress: Float + get() = (transitionState as Transition).progress + fun advanceUntilIdle() { coroutineScope.testScheduler.advanceUntilIdle() } @@ -128,31 +135,33 @@ class SceneGestureHandlerTest { runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() } } + private fun DraggableHandler.onDragStarted() = + onDragStarted(layoutSize = LAYOUT_SIZE, startedPosition = Offset.Zero) + @Test fun testPreconditions() = runGestureTest { assertScene(currentScene = SceneA, isIdle = true) } @Test fun onDragStarted_shouldStartATransition() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted() assertScene(currentScene = SceneA, isIdle = false) } @Test fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted() assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition draggable.onDelta(pixels = deltaInPixels10) - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) draggable.onDelta(pixels = deltaInPixels10) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) } @Test fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted() assertScene(currentScene = SceneA, isIdle = false) draggable.onDelta(pixels = deltaInPixels10) @@ -170,7 +179,7 @@ class SceneGestureHandlerTest { @Test fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted() assertScene(currentScene = SceneA, isIdle = false) draggable.onDelta(pixels = deltaInPixels10) @@ -188,7 +197,7 @@ class SceneGestureHandlerTest { @Test fun onDragStoppedAfterStarted_returnImmediatelyToIdle() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted() assertScene(currentScene = SceneA, isIdle = false) draggable.onDragStopped(velocity = 0f) @@ -197,7 +206,7 @@ class SceneGestureHandlerTest { @Test fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest { - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted() assertScene(currentScene = SceneA, isIdle = false) draggable.onDelta(pixels = deltaInPixels10) @@ -217,7 +226,7 @@ class SceneGestureHandlerTest { assertScene(currentScene = SceneC, isIdle = false) // Start a new gesture while the offset is animating - draggable.onDragStarted(startedPosition = Offset.Zero) + draggable.onDragStarted() assertThat(sceneGestureHandler.isAnimatingOffset).isFalse() } @@ -253,8 +262,7 @@ class SceneGestureHandlerTest { ) assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) assertThat(consumed).isEqualTo(offsetY10) } @@ -278,13 +286,12 @@ class SceneGestureHandlerTest { nestedScroll.scroll(available = offsetY10) assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) // start intercept preScroll val consumed = nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) // do nothing on postScroll nestedScroll.onPostScroll( @@ -292,13 +299,71 @@ class SceneGestureHandlerTest { available = Offset.Zero, source = NestedScrollSource.Drag ) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.3f) + assertThat(progress).isEqualTo(0.3f) assertScene(currentScene = SceneA, isIdle = false) } + private suspend fun TestGestureScope.preScrollAfterSceneTransition( + firstScroll: Float, + secondScroll: Float + ) { + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) + // start scene transition + nestedScroll.scroll(available = Offset(0f, SCREEN_SIZE * firstScroll)) + + // stop scene transition (start the "stop animation") + nestedScroll.onPreFling(available = Velocity.Zero) + + // a pre scroll event, that could be intercepted by SceneGestureHandler + nestedScroll.onPreScroll(Offset(0f, SCREEN_SIZE * secondScroll), NestedScrollSource.Drag) + } + + // Float tolerance for comparisons + private val tolerance = 0.00001f + + @Test + fun scrollAndFling_scrollLessThanInterceptable_goToIdleOnCurrentScene() = runGestureTest { + val first = transitionInterceptionThreshold - tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertScene(SceneA, isIdle = true) + } + + @Test + fun scrollAndFling_scrollMinInterceptable_interceptPreScrollEvents() = runGestureTest { + val first = transitionInterceptionThreshold + tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertThat(progress).isWithin(tolerance).of(first + second) + } + + @Test + fun scrollAndFling_scrollMaxInterceptable_interceptPreScrollEvents() = runGestureTest { + val first = 1f - transitionInterceptionThreshold - tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertThat(progress).isWithin(tolerance).of(first + second) + } + + @Test + fun scrollAndFling_scrollMoreThanInterceptable_goToIdleOnNextScene() = runGestureTest { + val first = 1f - transitionInterceptionThreshold + tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertScene(SceneC, isIdle = true) + } + @Test fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) @@ -421,6 +486,7 @@ class SceneGestureHandlerTest { draggable.onDelta(deltaInPixels10) assertScene(currentScene = SceneA, isIdle = true) } + @Test fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest { draggable.onDragStopped(velocityThreshold) @@ -437,26 +503,25 @@ class SceneGestureHandlerTest { @Test fun startNestedScrollWhileDragging() = runGestureTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = Always) - draggable.onDragStarted(Offset.Zero) + draggable.onDragStarted() assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition draggable.onDelta(deltaInPixels10) - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) // now we can intercept the scroll events nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) // this should be ignored, we are scrolling now! draggable.onDragStopped(velocityThreshold) assertScene(currentScene = SceneA, isIdle = false) nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.3f) + assertThat(progress).isEqualTo(0.3f) nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.4f) + assertThat(progress).isEqualTo(0.4f) nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold)) assertScene(currentScene = SceneC, isIdle = false) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt index 5afd420a5e16..321cf637824a 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt @@ -18,6 +18,8 @@ package com.android.compose.animation.scene import androidx.activity.ComponentActivity import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -48,6 +50,7 @@ import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.test.assertSizeIsEqualTo import com.android.compose.test.subjects.DpOffsetSubject import com.android.compose.test.subjects.assertThat import com.google.common.truth.Truth.assertThat @@ -307,6 +310,26 @@ class SceneTransitionLayoutTest { assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA) } + @Test + fun layoutSizeIsAnimated() { + val layoutTag = "layout" + rule.testTransition( + fromSceneContent = { Box(Modifier.size(200.dp, 100.dp)) }, + toSceneContent = { Box(Modifier.size(120.dp, 140.dp)) }, + transition = { + // 4 frames of animation. + spec = tween(4 * 16, easing = LinearEasing) + }, + layoutModifier = Modifier.testTag(layoutTag), + ) { + before { rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(200.dp, 100.dp) } + at(16) { rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(180.dp, 110.dp) } + at(32) { rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(160.dp, 120.dp) } + at(48) { rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(140.dp, 130.dp) } + after { rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(120.dp, 140.dp) } + } + } + private fun SemanticsNodeInteraction.offsetRelativeTo( other: SemanticsNodeInteraction, ): DpOffset { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt index 2a27763f1d5c..8cffcf6980cc 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt @@ -48,7 +48,7 @@ class EdgeTranslateTest { rule.testTransition( // The layout under test is 300dp x 300dp. layoutModifier = Modifier.size(300.dp), - fromSceneContent = {}, + fromSceneContent = { Box(Modifier.fillMaxSize()) }, toSceneContent = { // Foo is 100dp x 100dp in the center of the layout, so at offset = (100dp, 100dp) Box(Modifier.fillMaxSize()) { diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt index e0ae1be69aaf..06de2965f716 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt @@ -16,7 +16,6 @@ package com.android.compose.animation.scene -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -104,7 +103,7 @@ fun ComposeContentTestRule.testTransition( currentScene, onChangeScene, transitions { from(fromScene, to = toScene, transition) }, - layoutModifier.fillMaxSize(), + layoutModifier, ) { scene(fromScene, content = fromSceneContent) scene(toScene, content = toSceneContent) 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 15d4d2002b2b..67ce86b4e137 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,7 +25,7 @@ import com.android.internal.logging.nano.MetricsProto import com.android.internal.logging.testing.FakeMetricsLogger import com.android.internal.util.EmergencyAffordanceManager import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER import com.android.systemui.log.table.TableLogBuffer diff --git a/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml b/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml index be8fe8c57215..ff1146e9b945 100644 --- a/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml +++ b/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml @@ -19,9 +19,14 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - <path android:fillColor="@color/ksh_key_item_color" - android:pathData="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 -3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27 .28 v.79l5 4.99L20.49 -19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 -14z" /> + <path android:pathData="M5.5,5.5m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" + android:fillColor="@color/ksh_key_item_color" /> + <path android:pathData="M5.5,16.5m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" + android:fillColor="@color/ksh_key_item_color" /> + <path android:pathData="M16.5,5.5m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" + android:fillColor="@color/ksh_key_item_color" /> + <path android:pathData="M18.5,16.5C18.5,15.4 17.6,14.5 16.5,14.5C15.4,14.5 14.5,15.4 14.5,16.5C14.5,17.6 15.4,18.5 16.5,18.5C17.6,18.5 18.5,17.6 18.5,16.5ZM12.5,16.5C12.5,14.29 14.29,12.5 16.5,12.5C18.71,12.5 20.5,14.29 20.5,16.5C20.5,17.241 20.299,17.934 19.948,18.529L23,21.59L21.59,23L18.529,19.948C17.934,20.299 17.241,20.5 16.5,20.5C14.29,20.5 12.5,18.71 12.5,16.5Z" + android:fillColor="@color/ksh_key_item_color" + android:fillType="evenOdd" /> </vector> + diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml index 08eccbbe9669..4336ccc70c33 100644 --- a/packages/SystemUI/res/layout/bluetooth_device_item.xml +++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml @@ -21,7 +21,7 @@ style="@style/BluetoothTileDialog.Device" android:layout_width="match_parent" android:layout_height="@dimen/bluetooth_dialog_device_height" - android:paddingEnd="24dp" + android:paddingEnd="0dp" android:paddingStart="20dp" android:layout_marginBottom="4dp"> @@ -86,6 +86,7 @@ android:id="@+id/gear_icon_image" android:layout_width="0dp" android:layout_height="24dp" + android:paddingEnd="24dp" android:src="@drawable/ic_settings_24dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml index c11a18b795a1..af29cada2657 100644 --- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml +++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml @@ -120,6 +120,16 @@ android:visibility="gone" app:constraint_referenced_ids="ic_arrow,see_all_text" /> + <View + android:id="@+id/see_all_clickable_row" + android:layout_width="0dp" + android:layout_height="0dp" + android:contentDescription="@string/accessibility_bluetooth_device_settings_see_all" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/device_list" + app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" /> + <ImageView android:id="@+id/ic_arrow" android:layout_marginStart="36dp" @@ -141,6 +151,8 @@ android:maxLines="1" android:ellipsize="end" android:gravity="center_vertical" + android:importantForAccessibility="no" + android:clickable="false" android:layout_marginStart="0dp" android:paddingStart="20dp" android:text="@string/see_all_bluetooth_devices" @@ -158,6 +170,16 @@ android:visibility="gone" app:constraint_referenced_ids="ic_add,pair_new_device_text" /> + <View + android:id="@+id/pair_new_device_clickable_row" + android:layout_width="0dp" + android:layout_height="0dp" + android:contentDescription="@string/accessibility_bluetooth_device_settings_pair_new_device" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/see_all_text" + app:layout_constraintBottom_toTopOf="@+id/done_button" /> + <ImageView android:id="@+id/ic_add" android:layout_width="24dp" @@ -180,6 +202,8 @@ android:maxLines="1" android:ellipsize="end" android:gravity="center_vertical" + android:importantForAccessibility="no" + android:clickable="false" android:layout_marginStart="0dp" android:paddingStart="20dp" android:text="@string/pair_new_bluetooth_devices" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9a3c6d5b322f..3163533a3f4d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -467,6 +467,10 @@ <!-- Content description of the bluetooth device settings gear icon. [CHAR LIMIT=NONE] --> <string name="accessibility_bluetooth_device_settings_gear">Click to configure device detail</string> + <!-- Content description of the bluetooth device settings see all. [CHAR LIMIT=NONE] --> + <string name="accessibility_bluetooth_device_settings_see_all">Click to see all devices</string> + <!-- Content description of the bluetooth device settings pair new device. [CHAR LIMIT=NONE] --> + <string name="accessibility_bluetooth_device_settings_pair_new_device">Click to pair new device</string> <!-- Content description of the battery when battery state is unknown for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_battery_unknown">Battery percentage unknown.</string> @@ -640,6 +644,10 @@ <string name="quick_settings_bluetooth_device_connected">Connected</string> <!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]--> <string name="quick_settings_bluetooth_device_saved">Saved</string> + <!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]--> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect">disconnect</string> + <!-- QuickSettings: Accessibility label to activate a device [CHAR LIMIT=NONE]--> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate">activate</string> <!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]--> <string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt index 6082fb93aa26..8ad32b4f1695 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt @@ -25,7 +25,11 @@ enum class FingerprintSensorType { UDFPS_ULTRASONIC, UDFPS_OPTICAL, POWER_BUTTON, - HOME_BUTTON + HOME_BUTTON; + + fun isUdfps(): Boolean { + return (this == UDFPS_OPTICAL) || (this == UDFPS_ULTRASONIC) + } } /** Convert [this] to corresponding [FingerprintSensorType] */ diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 6b390b1191d2..c02ffa788d48 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -39,10 +39,10 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.DisplaySpecific import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW import com.android.systemui.flags.Flags.REGION_SAMPLING import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.LogBuffer @@ -298,7 +298,7 @@ constructor( object : KeyguardUpdateMonitorCallback() { override fun onKeyguardVisibilityChanged(visible: Boolean) { isKeyguardVisible = visible - if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { if (!isKeyguardVisible) { clock?.run { smallClock.animations.doze(if (isDozing) 1f else 0f) @@ -345,7 +345,7 @@ constructor( parent.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { listenForDozing(this) - if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { listenForDozeAmountTransition(this) listenForAnyStateToAodTransition(this) } else { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 32f9c3057753..36e18b36934c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -46,6 +46,7 @@ import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder; import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager; import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel; @@ -62,6 +63,7 @@ import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.AnimationProperties; @@ -105,6 +107,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final DozeParameters mDozeParameters; private final ScreenOffAnimationController mScreenOffAnimationController; private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore; + private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker; private FrameLayout mSmallClockFrame; // top aligned clock private FrameLayout mLargeClockFrame; // centered clock @@ -179,6 +182,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS LockscreenSmartspaceController smartspaceController, ConfigurationController configurationController, ScreenOffAnimationController screenOffAnimationController, + StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker, KeyguardUnlockAnimationController keyguardUnlockAnimationController, SecureSettings secureSettings, @Main DelayableExecutor uiExecutor, @@ -202,6 +206,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mSmartspaceController = smartspaceController; mConfigurationController = configurationController; mScreenOffAnimationController = screenOffAnimationController; + mIconViewBindingFailureTracker = iconViewBindingFailureTracker; mSecureSettings = secureSettings; mUiExecutor = uiExecutor; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; @@ -362,7 +367,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } int getNotificationIconAreaHeight() { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { return 0; } else if (NotificationIconContainerRefactor.isEnabled()) { return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0; @@ -590,7 +595,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } private void updateAodIcons() { - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { NotificationIconContainer nic = (NotificationIconContainer) mView.findViewById( com.android.systemui.res.R.id.left_aligned_notification_icon_container); @@ -605,6 +610,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mAodIconsViewModel, mConfigurationState, mConfigurationController, + mIconViewBindingFailureTracker, mAodIconViewStore); final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility( nic, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 758d1fef6e2e..87d937bc45fb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -51,10 +51,9 @@ import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.Dumpable; import com.android.systemui.animation.ViewHierarchyAnimator; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.plugins.ClockController; @@ -100,7 +99,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; - private final FeatureFlags mFeatureFlags; private final InteractionJankMonitor mInteractionJankMonitor; private final Rect mClipBounds = new Rect(); private final KeyguardInteractor mKeyguardInteractor; @@ -136,7 +134,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, KeyguardLogger logger, - FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, KeyguardInteractor keyguardInteractor, KeyguardTransitionInteractor keyguardTransitionInteractor, @@ -149,9 +146,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mConfigurationController = configurationController; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ true, - featureFlags, logger.getBuffer()); + logger.getBuffer()); mInteractionJankMonitor = interactionJankMonitor; - mFeatureFlags = featureFlags; mDumpManager = dumpManager; mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; @@ -188,7 +184,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } mDumpManager.registerDumpable(getInstanceName(), this); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { startCoroutines(EmptyCoroutineContext.INSTANCE); } } @@ -454,7 +450,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(layout); int guideline; - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { guideline = R.id.split_shade_guideline; } else { guideline = R.id.qs_edge_guideline; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index d524e4a8c408..ef6514447561 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -23,8 +23,7 @@ import android.util.Property; import android.view.View; import com.android.app.animation.Interpolators; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.statusbar.StatusBarState; @@ -55,7 +54,6 @@ public class KeyguardVisibilityHelper { private boolean mKeyguardViewVisibilityAnimating; private boolean mLastOccludedState = false; private final AnimationProperties mAnimationProperties = new AnimationProperties(); - private final FeatureFlags mFeatureFlags; private final LogBuffer mLogBuffer; public KeyguardVisibilityHelper(View view, @@ -63,14 +61,12 @@ public class KeyguardVisibilityHelper { DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, boolean animateYPos, - FeatureFlags featureFlags, LogBuffer logBuffer) { mView = view; mKeyguardStateController = keyguardStateController; mDozeParameters = dozeParameters; mScreenOffAnimationController = screenOffAnimationController; mAnimateYPos = animateYPos; - mFeatureFlags = featureFlags; mLogBuffer = logBuffer; } @@ -167,7 +163,7 @@ public class KeyguardVisibilityHelper { animProps, true /* animate */); } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { log("Using GoneToAodTransition"); mKeyguardViewVisibilityAnimating = false; } else { diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index f0915583f021..07359d1b446c 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -60,10 +60,10 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthRippleController; import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -127,7 +127,7 @@ public class LockIconViewController implements Dumpable { @NonNull private final KeyguardTransitionInteractor mTransitionInteractor; @NonNull private final KeyguardInteractor mKeyguardInteractor; @NonNull private final View.AccessibilityDelegate mAccessibilityDelegate; - @NonNull private final Lazy<BouncerInteractor> mBouncerInteractor; + @NonNull private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor; @NonNull private final SceneContainerFlags mSceneContainerFlags; // Tracks the velocity of a touch to help filter out the touches that move too fast. @@ -205,7 +205,7 @@ public class LockIconViewController implements Dumpable { @NonNull FeatureFlags featureFlags, PrimaryBouncerInteractor primaryBouncerInteractor, Context context, - Lazy<BouncerInteractor> bouncerInteractor, + Lazy<DeviceEntryInteractor> deviceEntryInteractor, SceneContainerFlags sceneContainerFlags ) { mStatusBarStateController = statusBarStateController; @@ -232,7 +232,7 @@ public class LockIconViewController implements Dumpable { dumpManager.registerDumpable(TAG, this); mResources = resources; mContext = context; - mBouncerInteractor = bouncerInteractor; + mDeviceEntryInteractor = deviceEntryInteractor; mSceneContainerFlags = sceneContainerFlags; mAccessibilityDelegate = new View.AccessibilityDelegate() { @@ -747,7 +747,7 @@ public class LockIconViewController implements Dumpable { vibrateOnLongPress(); if (mSceneContainerFlags.isEnabled()) { - mBouncerInteractor.get().showOrUnlockDevice(null); + mDeviceEntryInteractor.get().attemptDeviceEntry(); } else { mKeyguardViewController.showPrimaryBouncer(/* scrim */ true); } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index e8b16db10da5..7769dd9dc9ab 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -25,7 +25,7 @@ import com.android.internal.widget.LockPatternChecker import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationResultModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.broadcast.BroadcastDispatcher @@ -45,7 +45,9 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine @@ -68,6 +70,12 @@ interface AuthenticationRepository { val isAutoConfirmFeatureEnabled: StateFlow<Boolean> /** + * Emits the result whenever a PIN/Pattern/Password security challenge is attempted by the user + * in order to unlock the device. + */ + val authenticationChallengeResult: SharedFlow<Boolean> + + /** * The exact length a PIN should be for us to enable PIN length hinting. * * A PIN that's shorter or longer than this is not eligible for the UI to render hints showing @@ -100,6 +108,9 @@ interface AuthenticationRepository { /** The minimal length of a pattern. */ val minPatternLength: Int + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> + /** * Returns the currently-configured authentication method. This determines how the * authentication challenge needs to be completed in order to unlock an otherwise locked device. @@ -164,6 +175,7 @@ constructor( initialValue = false, getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled, ) + override val authenticationChallengeResult = MutableSharedFlow<Boolean>() override val hintedPinLength: Int = 6 @@ -203,6 +215,12 @@ constructor( override val minPatternLength: Int = LockPatternUtils.MIN_LOCK_PATTERN_SIZE + override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + refreshingFlow( + initialValue = true, + getFreshValue = { userId -> lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) }, + ) + override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return withContext(backgroundDispatcher) { blockingAuthenticationMethodInternal(userRepository.selectedUserId) @@ -224,6 +242,7 @@ constructor( } else { lockPatternUtils.reportFailedPasswordAttempt(selectedUserId) } + authenticationChallengeResult.emit(isSuccessful) } } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 22b44d56fe9b..5eefbf5353d3 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -16,19 +16,16 @@ package com.android.systemui.authentication.domain.interactor -import com.android.app.tracing.TraceUtils.Companion.async import com.android.app.tracing.TraceUtils.Companion.withContext import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential -import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.AuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -40,6 +37,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -47,7 +45,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext /** * Hosts application business logic related to user authentication. @@ -64,7 +61,6 @@ constructor( private val repository: AuthenticationRepository, @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, - private val deviceEntryRepository: DeviceEntryRepository, private val clock: SystemClock, ) { /** @@ -85,8 +81,7 @@ constructor( * `true` even when the lockscreen is showing and still needs to be dismissed by the user to * proceed. */ - val authenticationMethod: Flow<DomainLayerAuthenticationMethodModel> = - repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() } + val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */ val throttling: StateFlow<AuthenticationThrottlingModel> = repository.throttling @@ -143,6 +138,16 @@ constructor( /** Whether the pattern should be visible for the currently-selected user. */ val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible + /** + * Emits the outcome (successful or unsuccessful) whenever a PIN/Pattern/Password security + * challenge is attempted by the user in order to unlock the device. + */ + val authenticationChallengeResult: SharedFlow<Boolean> = + repository.authenticationChallengeResult + + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled + private var throttlingCountdownJob: Job? = null init { @@ -165,17 +170,8 @@ constructor( * The flow should be used for code that wishes to stay up-to-date its logic as the * authentication changes over time and this method should be used for simple code that only * needs to check the current value. - * - * Note: this layer adds the synthetic authentication method of "swipe" which is special. When - * the current authentication method is "swipe", the user does not need to complete any - * authentication challenge to unlock the device; they just need to dismiss the lockscreen to - * get past it. This also means that the value of `DeviceEntryInteractor#isUnlocked` remains - * `true` even when the lockscreen is showing and still needs to be dismissed by the user to - * proceed. */ - suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel { - return repository.getAuthenticationMethod().toDomainLayer() - } + suspend fun getAuthenticationMethod() = repository.getAuthenticationMethod() /** * Attempts to authenticate the user and unlock the device. @@ -205,13 +201,13 @@ constructor( // attempt. isThrottled.value -> true // The pattern is too short; skip the attempt. - authMethod == DomainLayerAuthenticationMethodModel.Pattern && + authMethod == AuthenticationMethodModel.Pattern && input.size < repository.minPatternLength -> true // Auto-confirm attempt when the feature is not enabled; skip the attempt. tryAutoConfirm && !isAutoConfirmEnabled.value -> true // Auto-confirm should skip the attempt if the pin entered is too short. tryAutoConfirm && - authMethod == DomainLayerAuthenticationMethodModel.Pin && + authMethod == AuthenticationMethodModel.Pin && input.size < repository.getPinLength() -> true else -> false } @@ -297,15 +293,15 @@ constructor( } } - private fun DomainLayerAuthenticationMethodModel.createCredential( + private fun AuthenticationMethodModel.createCredential( input: List<Any> ): LockscreenCredential? { return when (this) { - is DomainLayerAuthenticationMethodModel.Pin -> + is AuthenticationMethodModel.Pin -> LockscreenCredential.createPin(input.joinToString("")) - is DomainLayerAuthenticationMethodModel.Password -> + is AuthenticationMethodModel.Password -> LockscreenCredential.createPassword(input.joinToString("")) - is DomainLayerAuthenticationMethodModel.Pattern -> + is AuthenticationMethodModel.Pattern -> LockscreenCredential.createPattern( input .map { it as AuthenticationPatternCoordinate } @@ -315,23 +311,6 @@ constructor( } } - private suspend fun DataLayerAuthenticationMethodModel.toDomainLayer(): - DomainLayerAuthenticationMethodModel { - return when (this) { - is DataLayerAuthenticationMethodModel.None -> - if (deviceEntryRepository.isInsecureLockscreenEnabled()) { - DomainLayerAuthenticationMethodModel.Swipe - } else { - DomainLayerAuthenticationMethodModel.None - } - is DataLayerAuthenticationMethodModel.Pin -> DomainLayerAuthenticationMethodModel.Pin - is DataLayerAuthenticationMethodModel.Password -> - DomainLayerAuthenticationMethodModel.Password - is DataLayerAuthenticationMethodModel.Pattern -> - DomainLayerAuthenticationMethodModel.Pattern - } - } - companion object { const val TAG = "AuthenticationInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt deleted file mode 100644 index d7e6099a8908..000000000000 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt +++ /dev/null @@ -1,40 +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.authentication.domain.model - -/** Enumerates all known authentication methods. */ -sealed class AuthenticationMethodModel( - /** - * Whether the authentication method is considered to be "secure". - * - * "Secure" authentication methods require authentication to unlock the device. Non-secure auth - * methods simply require user dismissal. - */ - open val isSecure: Boolean, -) { - /** There is no authentication method on the device. We shouldn't even show the lock screen. */ - object None : AuthenticationMethodModel(isSecure = false) - - /** The most basic authentication method. The lock screen can be swiped away when displayed. */ - object Swipe : AuthenticationMethodModel(isSecure = false) - - object Pin : AuthenticationMethodModel(isSecure = true) - - object Password : AuthenticationMethodModel(isSecure = true) - - object Pattern : AuthenticationMethodModel(isSecure = true) -} diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt index 6d23b11e5d66..bb5b81d4d2f7 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Android Open Source Project + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.authentication.data.model +package com.android.systemui.authentication.shared.model /** Enumerates all known authentication methods. */ sealed class AuthenticationMethodModel( @@ -26,7 +26,10 @@ sealed class AuthenticationMethodModel( */ open val isSecure: Boolean, ) { - /** There is no authentication method on the device. We shouldn't even show the lock screen. */ + /** + * Device doesn't use a secure authentication method. Either there is no lockscreen or the lock + * screen can be swiped away when displayed. + */ object None : AuthenticationMethodModel(isSecure = false) object Pin : AuthenticationMethodModel(isSecure = true) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index b9a913ea866f..ff36839460be 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -19,24 +19,22 @@ package com.android.systemui.bouncer.domain.interactor import android.content.Context import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.domain.interactor.AuthenticationResult -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.classifier.FalsingClassifier import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.res.R -import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -51,9 +49,7 @@ constructor( @Application private val applicationScope: CoroutineScope, @Application private val applicationContext: Context, private val repository: BouncerRepository, - private val deviceEntryInteractor: DeviceEntryInteractor, private val authenticationInteractor: AuthenticationInteractor, - private val sceneInteractor: SceneInteractor, flags: SceneContainerFlags, private val falsingInteractor: FalsingInteractor, ) { @@ -96,10 +92,18 @@ constructor( /** Whether the pattern should be visible for the currently-selected user. */ val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + authenticationInteractor.isPinEnhancedPrivacyEnabled + /** Whether the user switcher should be displayed within the bouncer UI on large screens. */ val isUserSwitcherVisible: Boolean get() = repository.isUserSwitcherVisible + private val _onImeHidden = MutableSharedFlow<Unit>() + /** Provide the onImeHidden events from the bouncer */ + val onImeHidden: SharedFlow<Unit> = _onImeHidden + init { if (flags.isEnabled()) { // Clear the message if moved from throttling to no-longer throttling. @@ -142,32 +146,6 @@ constructor( } /** - * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown. - * - * @param message An optional message to show to the user in the bouncer. - */ - fun showOrUnlockDevice( - message: String? = null, - ) { - applicationScope.launch { - if (deviceEntryInteractor.isAuthenticationRequired()) { - repository.setMessage( - message ?: promptMessage(authenticationInteractor.getAuthenticationMethod()) - ) - sceneInteractor.changeScene( - scene = SceneModel(SceneKey.Bouncer), - loggingReason = "request to unlock device while authentication required", - ) - } else { - sceneInteractor.changeScene( - scene = SceneModel(SceneKey.Gone), - loggingReason = "request to unlock device while authentication isn't required", - ) - } - } - } - - /** * Resets the user-facing message back to the default according to the current authentication * method. */ @@ -212,17 +190,11 @@ constructor( return applicationScope .async { val authResult = authenticationInteractor.authenticate(input, tryAutoConfirm) - when (authResult) { - // Authentication succeeded. - AuthenticationResult.SUCCEEDED -> - sceneInteractor.changeScene( - scene = SceneModel(SceneKey.Gone), - loggingReason = "successful authentication", - ) - // Authentication failed. - AuthenticationResult.FAILED -> showErrorMessage() - // Authentication skipped. - AuthenticationResult.SKIPPED -> if (!tryAutoConfirm) showErrorMessage() + if ( + authResult == AuthenticationResult.FAILED || + (authResult == AuthenticationResult.SKIPPED && !tryAutoConfirm) + ) { + showErrorMessage() } authResult } @@ -242,16 +214,8 @@ constructor( } /** Notifies the interactor that the input method editor has been hidden. */ - fun onImeHidden() { - // If the bouncer is showing, hide it and return to the lockscreen scene. - if (sceneInteractor.desiredScene.value.key != SceneKey.Bouncer) { - return - } - - sceneInteractor.changeScene( - scene = SceneModel(SceneKey.Lockscreen), - loggingReason = "IME hidden", - ) + suspend fun onImeHidden() { + _onImeHidden.emit(Unit) } private fun promptMessage(authMethod: AuthenticationMethodModel): String { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 55bc653fc737..f46574ca5bbe 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -18,7 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.annotation.StringRes import com.android.systemui.authentication.domain.interactor.AuthenticationResult -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -75,7 +75,7 @@ sealed class AuthMethodBouncerViewModel( * Notifies that the input method editor (for example, the software keyboard) has been shown or * hidden. */ - fun onImeVisibilityChanged(isVisible: Boolean) { + suspend fun onImeVisibilityChanged(isVisible: Boolean) { if (isImeVisible && !isVisible) { interactor.onImeHidden() } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 73d15f0f369b..09c94c81581b 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -20,7 +20,7 @@ import android.content.Context import android.graphics.Bitmap import androidx.core.graphics.drawable.toBitmap import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel @@ -180,6 +180,19 @@ class BouncerViewModel( initialValue = isSideBySideSupported(authMethodViewModel.value), ) + /** + * Whether the splitting the UI around the fold seam (where the hinge is on a foldable device) + * is required. + */ + val isFoldSplitRequired: StateFlow<Boolean> = + authMethodViewModel + .map { authMethod -> isFoldSplitRequired(authMethod) } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = isFoldSplitRequired(authMethodViewModel.value), + ) + init { if (flags.isEnabled()) { applicationScope.launch { @@ -212,6 +225,10 @@ class BouncerViewModel( return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel } + private fun isFoldSplitRequired(authMethod: AuthMethodBouncerViewModel?): Boolean { + return authMethod !is PasswordBouncerViewModel + } + private fun toMessageViewModel( message: String?, isThrottled: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index fe7741930d33..a15698e1f90c 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -16,7 +16,7 @@ package com.android.systemui.bouncer.ui.viewmodel -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.res.R import kotlinx.coroutines.CoroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index d301085e823d..ed6a48f93d67 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -18,7 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import android.util.TypedValue -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.res.R diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index b90e25526726..b2b8049e3cff 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -18,7 +18,7 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import com.android.keyguard.PinShapeAdapter -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.res.R import kotlinx.coroutines.CoroutineScope @@ -84,6 +84,19 @@ class PinBouncerViewModel( override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message + /** + * Whether the digit buttons should be animated when touched. Note that this doesn't affect the + * delete or enter buttons; those should always animate. + */ + val isDigitButtonAnimationEnabled: StateFlow<Boolean> = + interactor.isPinEnhancedPrivacyEnabled + .map { !it } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = !interactor.isPinEnhancedPrivacyEnabled.value, + ) + /** Notifies that the user clicked on a PIN button with the given digit value. */ fun onPinButtonClicked(input: Int) { val pinInput = mutablePinInput.value 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 508f52c84983..771dfbcae138 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 @@ -141,6 +141,7 @@ constructor( private val umoContent: Flow<List<CommunalContentModel.Umo>> = mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying -> if (mediaPlaying) { + // TODO(b/310254801): support HALF and FULL layouts flowOf(listOf(CommunalContentModel.Umo(CommunalContentSize.THIRD))) } else { flowOf(emptyList()) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 9fc86adce091..1f2621d0c987 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -120,6 +120,7 @@ import com.android.systemui.statusbar.policy.PolicyModule; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule; import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule; +import com.android.systemui.statusbar.ui.binder.StatusBarViewBinderModule; import com.android.systemui.statusbar.window.StatusBarWindowModule; import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule; import com.android.systemui.temporarydisplay.dagger.TemporaryDisplayModule; @@ -215,6 +216,7 @@ import javax.inject.Named; StatusBarModule.class, StatusBarPipelineModule.class, StatusBarPolicyModule.class, + StatusBarViewBinderModule.class, StatusBarWindowModule.class, SystemPropertiesFlagsModule.class, SysUIConcurrencyModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt index 1e29e1fa3197..f27bbe6c7624 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt @@ -1,5 +1,6 @@ package com.android.systemui.deviceentry.data.repository +import android.util.Log import com.android.internal.widget.LockPatternUtils import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -15,9 +16,12 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @@ -35,10 +39,14 @@ interface DeviceEntryRepository { val isUnlocked: StateFlow<Boolean> /** - * Whether the lockscreen should be shown when the authentication method is not secure (e.g. - * `None` or `Swipe`). + * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has + * chosen any secure authentication method and even if they set the lockscreen to be dismissed + * when the user swipes on it. */ - suspend fun isInsecureLockscreenEnabled(): Boolean + suspend fun isLockscreenEnabled(): Boolean + + /** Report successful authentication for device entry. */ + fun reportSuccessfulAuthentication() /** * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically @@ -67,7 +75,9 @@ constructor( keyguardStateController: KeyguardStateController, ) : DeviceEntryRepository { - override val isUnlocked = + private val _isUnlocked = MutableStateFlow(false) + + private val isUnlockedReportedByLegacyKeyguard = conflatedCallbackFlow { val callback = object : KeyguardStateController.Callback { @@ -99,19 +109,27 @@ constructor( awaitClose { keyguardStateController.removeCallback(callback) } } .distinctUntilChanged() + .onEach { _isUnlocked.value = it } .stateIn( applicationScope, SharingStarted.Eagerly, initialValue = false, ) - override suspend fun isInsecureLockscreenEnabled(): Boolean { + override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() + + override suspend fun isLockscreenEnabled(): Boolean { return withContext(backgroundDispatcher) { val selectedUserId = userRepository.getSelectedUserInfo().id !lockPatternUtils.isLockScreenDisabled(selectedUserId) } } + override fun reportSuccessfulAuthentication() { + Log.d(TAG, "Successful authentication reported.") + _isUnlocked.value = true + } + override val isBypassEnabled: StateFlow<Boolean> = conflatedCallbackFlow { val listener = diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index e872d13bd913..298811baba6c 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -17,23 +17,29 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch /** * Hosts application business logic related to device entry. @@ -41,6 +47,7 @@ import kotlinx.coroutines.flow.stateIn * Device entry occurs when the user successfully dismisses (or bypasses) the lockscreen, regardless * of the authentication method used. */ +@ExperimentalCoroutinesApi @SysUISingleton class DeviceEntryInteractor @Inject @@ -48,9 +55,10 @@ constructor( @Application private val applicationScope: CoroutineScope, repository: DeviceEntryRepository, private val authenticationInteractor: AuthenticationInteractor, - sceneInteractor: SceneInteractor, + private val sceneInteractor: SceneInteractor, deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository, trustRepository: TrustRepository, + flags: SceneContainerFlags, ) { /** * Whether the device is unlocked. @@ -90,28 +98,33 @@ constructor( .map { it == SceneKey.Gone } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Eagerly, initialValue = false, ) // Authenticated by a TrustAgent like trusted device, location, etc or by face auth. private val passivelyAuthenticated = merge( - trustRepository.isCurrentUserTrusted, - deviceEntryFaceAuthRepository.isAuthenticated, - ) + trustRepository.isCurrentUserTrusted, + deviceEntryFaceAuthRepository.isAuthenticated, + ) + .onStart { emit(false) } /** * Whether it's currently possible to swipe up to enter the device without requiring - * authentication. This returns `false` whenever the lockscreen has been dismissed. + * authentication or when the device is already authenticated using a passive authentication + * mechanism like face or trust manager. This returns `false` whenever the lockscreen has been + * dismissed. * * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other * UI. */ val canSwipeToEnter = combine( + // This is true when the user has chosen to show the lockscreen but has not made it + // secure. authenticationInteractor.authenticationMethod.map { - it == AuthenticationMethodModel.Swipe + it == AuthenticationMethodModel.None && repository.isLockscreenEnabled() }, passivelyAuthenticated, isDeviceEntered @@ -120,11 +133,37 @@ constructor( } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Eagerly, initialValue = false, ) /** + * Attempt to enter the device and dismiss the lockscreen. If authentication is required to + * unlock the device it will transition to bouncer. + */ + fun attemptDeviceEntry() { + // TODO (b/307768356), + // 1. Check if the device is already authenticated by trust agent/passive biometrics + // 2. show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show + // 3. For face auth only setups trigger face auth, delay transitioning to bouncer for + // a small amount of time. + // 4. Transition to bouncer scene + applicationScope.launch { + if (isAuthenticationRequired()) { + sceneInteractor.changeScene( + scene = SceneModel(SceneKey.Bouncer), + loggingReason = "request to unlock device while authentication required", + ) + } else { + sceneInteractor.changeScene( + scene = SceneModel(SceneKey.Gone), + loggingReason = "request to unlock device while authentication isn't required", + ) + } + } + } + + /** * Returns `true` if the device currently requires authentication before entry is granted; * `false` if the device can be entered without authenticating first. */ @@ -133,10 +172,22 @@ constructor( } /** - * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically + * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically * dismissed once the authentication challenge is completed. For example, completing a biometric * authentication challenge via face unlock or fingerprint sensor can automatically bypass the - * lock screen. + * lockscreen. */ val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled + + init { + if (flags.isEnabled()) { + applicationScope.launch { + authenticationInteractor.authenticationChallengeResult.collectLatest { successful -> + if (successful) { + repository.reportSuccessfulAuthentication() + } + } + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt new file mode 100644 index 000000000000..72b9da669360 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt @@ -0,0 +1,62 @@ +/* + * 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.deviceentry.domain.interactor + +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Encapsulates business logic for device entry under-display fingerprint state changes. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DeviceEntryUdfpsInteractor +@Inject +constructor( + // TODO (b/309655554): create & use interactors for these repositories + fingerprintPropertyRepository: FingerprintPropertyRepository, + fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, + biometricSettingsRepository: BiometricSettingsRepository, +) { + /** Whether the device supports an under display fingerprint sensor. */ + val isUdfpsSupported: Flow<Boolean> = + fingerprintPropertyRepository.sensorType.map { it.isUdfps() } + + /** Whether the under-display fingerprint sensor is enrolled and enabled for device entry. */ + val isUdfpsEnrolledAndEnabled: Flow<Boolean> = + combine(isUdfpsSupported, biometricSettingsRepository.isFingerprintEnrolledAndEnabled) { + udfps, + fpEnrolledAndEnabled -> + udfps && fpEnrolledAndEnabled + } + /** Whether the under display fingerprint sensor is currently running. */ + val isListeningForUdfps = + isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + fingerprintAuthRepository.isRunning + } else { + flowOf(false) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index da3fd14cdcf0..83c16ae9ea78 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -17,7 +17,6 @@ package com.android.systemui.flags import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.Flags as Classic import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import javax.inject.Inject @@ -28,9 +27,5 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha FlagDependenciesBase(featureFlags, handler) { override fun defineDependencies() { FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token - - // These two flags are effectively linked. We should migrate them to a single aconfig flag. - Classic.MIGRATE_NSSL dependsOn Classic.MIGRATE_KEYGUARD_STATUS_VIEW - Classic.MIGRATE_KEYGUARD_STATUS_VIEW dependsOn Classic.MIGRATE_NSSL } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index d64a1312ac66..ad3d6d646f46 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -254,14 +254,6 @@ object Flags { // TODO(b/287268101): Tracking bug. @JvmField val TRANSIT_CLOCK = releasedFlag("lockscreen_custom_transit_clock") - /** Migrate the NSSL to the a sibling to both the panel and keyguard root view. */ - // TODO(b/288074305): Tracking bug. - @JvmField val MIGRATE_NSSL = unreleasedFlag("migrate_nssl") - - /** Migrate the status view from the notification panel to keyguard root view. */ - // TODO(b/291767565): Tracking bug. - @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag("migrate_keyguard_status_view") - /** Migrate the status bar view on keyguard from notification panel to keyguard root view. */ // TODO(b/299115332): Tracking Bug. @JvmField val MIGRATE_KEYGUARD_STATUS_BAR_VIEW = @@ -316,11 +308,6 @@ object Flags { val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = releasedFlag("smartspace_shared_element_transition_enabled") - // TODO(b/258517050): Clean up after the feature is launched. - @JvmField - val SMARTSPACE_DATE_WEATHER_DECOUPLED = - sysPropBooleanFlag("persist.sysui.ss.dw_decoupled", default = true) - // TODO(b/270223352): Tracking Bug @JvmField val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = releasedFlag("hide_smartspace_on_dream_overlay") @@ -441,9 +428,6 @@ object Flags { // TODO(b/270437894): Tracking Bug val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume") - // TODO(b/304506662): Tracking Bug - val MEDIA_DEVICE_NAME_FIX = releasedFlag("media_device_name_fix") - // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 8b93b171d241..331d892304b3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -55,6 +55,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule; import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl; +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; @@ -94,6 +95,7 @@ import kotlinx.coroutines.CoroutineDispatcher; KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.class}, includes = { + DeviceEntryIconTransitionModule.class, FalsingModule.class, KeyguardDataQuickAffordanceModule.class, KeyguardRepositoryModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt index 8bf2bc395f88..cc1cf911f1c1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt @@ -50,14 +50,18 @@ constructor( private val configurationRepository: ConfigurationRepository, private val keyguardInteractor: KeyguardInteractor, ) { - val udfpsBurnInXOffset: StateFlow<Int> = + val deviceEntryIconXOffset: StateFlow<Int> = burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true) - val udfpsBurnInYOffset: StateFlow<Int> = + val deviceEntryIconYOffset: StateFlow<Int> = burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false) - val udfpsBurnInProgress: StateFlow<Float> = + val udfpsProgress: StateFlow<Float> = keyguardInteractor.dozeTimeTick .mapLatest { burnInHelperWrapper.burnInProgressOffset() } - .stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset()) + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + burnInHelperWrapper.burnInProgressOffset() + ) val keyguardBurnIn: Flow<BurnInModel> = combine( 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 a331a668e135..858440185568 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 @@ -113,7 +113,9 @@ constructor( } companion object { - val TO_LOCKSCREEN_DURATION = 500.milliseconds private val DEFAULT_DURATION = 500.milliseconds + val TO_LOCKSCREEN_DURATION = 500.milliseconds + val TO_GONE_DURATION = DEFAULT_DURATION + val TO_OCCLUDED_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index e9719e7d584e..eca7088c079a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -26,11 +26,11 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch -import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds @SysUISingleton class FromDozingTransitionInteractor @@ -97,5 +97,6 @@ constructor( companion object { private val DEFAULT_DURATION = 500.milliseconds + val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index eace0c70cb5b..bd73d60cda29 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -138,6 +138,6 @@ constructor( companion object { private val DEFAULT_DURATION = 500.milliseconds val TO_DREAMING_DURATION = 933.milliseconds - val TO_AOD_DURATION = 1100.milliseconds + val TO_AOD_DURATION = 1300.milliseconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index ea40ba0376d4..152d2172ee4c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -391,5 +391,7 @@ constructor( val TO_DREAMING_DURATION = 933.milliseconds val TO_OCCLUDED_DURATION = 450.milliseconds val TO_AOD_DURATION = 500.milliseconds + val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION + val TO_GONE_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index dec38b504ee1..6a8555cb7f6b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -142,9 +142,7 @@ constructor( ::toTriple ) .collect { (isAsleep, lastStartedStep, isAodAvailable) -> - if ( - lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep - ) { + if (lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep) { startTransitionTo( if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING ) @@ -187,5 +185,6 @@ constructor( companion object { private val DEFAULT_DURATION = 500.milliseconds val TO_LOCKSCREEN_DURATION = 933.milliseconds + val TO_AOD_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 24b666185ce2..5f246e181c26 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -255,5 +255,7 @@ constructor( private val DEFAULT_DURATION = 300.milliseconds val TO_GONE_DURATION = 500.milliseconds val TO_GONE_SHORT_DURATION = 200.milliseconds + val TO_AOD_DURATION = DEFAULT_DURATION + val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt index c0308e6c5759..f5cd7676e4b1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt @@ -51,14 +51,14 @@ constructor( val scaleForResolution = configRepo.scaleForResolution /** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */ - val burnInOffsets: Flow<BurnInOffsets> = + val burnInOffsets: Flow<Offsets> = combine( keyguardInteractor.dozeAmount, - burnInInteractor.udfpsBurnInXOffset, - burnInInteractor.udfpsBurnInYOffset, - burnInInteractor.udfpsBurnInProgress + burnInInteractor.deviceEntryIconXOffset, + burnInInteractor.deviceEntryIconYOffset, + burnInInteractor.udfpsProgress ) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress -> - BurnInOffsets( + Offsets( intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX), intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY), floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress), @@ -86,8 +86,8 @@ constructor( .onStart { emit(0f) } } -data class BurnInOffsets( - val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount - val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount - val burnInProgress: Float, // current progress based on the aodTransitionAmount +data class Offsets( + val x: Int, // current x burn in offset based on the aodTransitionAmount + val y: Int, // current y burn in offset based on the aodTransitionAmount + val progress: Float, // current progress based on the aodTransitionAmount ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt new file mode 100644 index 000000000000..23642a741fb8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt @@ -0,0 +1,52 @@ +/* + * 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.keyguard.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the keyguard shade migration nssl flag state. */ +@Suppress("NOTHING_TO_INLINE") +object KeyguardShadeMigrationNssl { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.keyguardShadeMigrationNssl() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt index 9d7477c13be6..d5ad7ab0d0d1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt @@ -105,4 +105,9 @@ class KeyguardTransitionAnimationFlow( } .filterNotNull() } + + /** Immediately (after 1ms) emits the given value for every step of the KeyguardTransition. */ + fun immediatelyTransitionTo(value: Float): Flow<Float> { + return createFlow(duration = 1.milliseconds, onStep = { value }, onFinish = { value }) + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index e82ea7fecd05..a8b28bcfbbc0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -24,6 +24,8 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.common.ui.view.LongPressHandlingView import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager @@ -34,19 +36,24 @@ import kotlinx.coroutines.launch object DeviceEntryIconViewBinder { /** - * Updates UI for the device entry icon view (lock, unlock and fingerprint icons) and its - * background. + * Updates UI for: + * - device entry containing view (parent view for the below views) + * - long-press handling view (transparent, no UI) + * - foreground icon view (lock/unlock/fingerprint) + * - background view (optional) */ @SuppressLint("ClickableViewAccessibility") @JvmStatic fun bind( view: DeviceEntryIconView, viewModel: DeviceEntryIconViewModel, + fgViewModel: DeviceEntryForegroundViewModel, + bgViewModel: DeviceEntryBackgroundViewModel, falsingManager: FalsingManager, ) { - val iconView = view.iconView - val bgView = view.bgView val longPressHandlingView = view.longPressHandlingView + val fgIconView = view.iconView + val bgView = view.bgView longPressHandlingView.listener = object : LongPressHandlingView.Listener { override fun onLongPressDetected(view: View, x: Int, y: Int) { @@ -56,45 +63,69 @@ object DeviceEntryIconViewBinder { viewModel.onLongPress() } } + view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { + // Repeat on CREATED so that the view will always observe the entire + // GONE => AOD transition (even though the view may not be visible until the middle + // of the transition. + repeatOnLifecycle(Lifecycle.State.CREATED) { launch { - viewModel.iconViewModel.collect { iconViewModel -> - iconView.setImageState( - view.getIconState(iconViewModel.type, iconViewModel.useAodVariant), - /* merge */ false - ) - iconView.imageTintList = ColorStateList.valueOf(iconViewModel.tint) - iconView.alpha = iconViewModel.alpha - iconView.setPadding( - iconViewModel.padding, - iconViewModel.padding, - iconViewModel.padding, - iconViewModel.padding, - ) + viewModel.isLongPressEnabled.collect { isEnabled -> + longPressHandlingView.setLongPressHandlingEnabled(isEnabled) } } launch { - viewModel.backgroundViewModel.collect { bgViewModel -> - bgView.alpha = bgViewModel.alpha - bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint) + viewModel.accessibilityDelegateHint.collect { hint -> + view.accessibilityHintType = hint } } launch { - viewModel.burnInViewModel.collect { burnInViewModel -> - view.translationX = burnInViewModel.x.toFloat() - view.translationY = burnInViewModel.y.toFloat() - view.aodFpDrawable.progress = burnInViewModel.progress + viewModel.useBackgroundProtection.collect { useBackgroundProtection -> + if (useBackgroundProtection) { + bgView.visibility = View.VISIBLE + } else { + bgView.visibility = View.GONE + } } } launch { - viewModel.isLongPressEnabled.collect { isEnabled -> - longPressHandlingView.setLongPressHandlingEnabled(isEnabled) + viewModel.burnInOffsets.collect { burnInOffsets -> + view.translationX = burnInOffsets.x.toFloat() + view.translationY = burnInOffsets.y.toFloat() + view.aodFpDrawable.progress = burnInOffsets.progress } } + + launch { viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha } } + } + } + + fgIconView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { launch { - viewModel.accessibilityDelegateHint.collect { hint -> - view.accessibilityHintType = hint + fgViewModel.viewModel.collect { viewModel -> + fgIconView.setImageState( + view.getIconState(viewModel.type, viewModel.useAodVariant), + /* merge */ false + ) + fgIconView.imageTintList = ColorStateList.valueOf(viewModel.tint) + fgIconView.setPadding( + viewModel.padding, + viewModel.padding, + viewModel.padding, + viewModel.padding, + ) + } + } + } + } + + bgView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + bgViewModel.viewModel.collect { bgViewModel -> + bgView.alpha = bgViewModel.alpha + bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 1f74bb661135..4de9fc343af1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -41,7 +41,7 @@ import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.flags.RefactorFlag +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel @@ -116,7 +116,7 @@ object KeyguardRootViewBinder { launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } } } - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { launch { viewModel.burnInLayerVisibility.collect { visibility -> childViews[burnInLayerId]?.visibility = visibility @@ -342,7 +342,7 @@ object KeyguardRootViewBinder { featureFlags: FeatureFlagsClassic, screenOffAnimationController: ScreenOffAnimationController, ): DisposableHandle? { - RefactorFlag(featureFlags, Flags.MIGRATE_KEYGUARD_STATUS_VIEW).assertInLegacyMode() + KeyguardShadeMigrationNssl.assertInLegacyMode() if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return null return view.repeatWhenAttached { lifecycleScope.launch { @@ -368,7 +368,7 @@ object KeyguardRootViewBinder { iconsAppearTranslationPx: Int, screenOffAnimationController: ScreenOffAnimationController, ) { - val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW) + val statusViewMigrated = KeyguardShadeMigrationNssl.isEnabled animate().cancel() val animatorListener = object : AnimatorListenerAdapter() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt index 9872d97021fa..52d87d369083 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt @@ -42,9 +42,9 @@ object UdfpsAodFingerprintViewBinder { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { viewModel.burnInOffsets.collect { burnInOffsets -> - view.progress = burnInOffsets.burnInProgress - view.translationX = burnInOffsets.burnInXOffset.toFloat() - view.translationY = burnInOffsets.burnInYOffset.toFloat() + view.progress = burnInOffsets.progress + view.translationX = burnInOffsets.x.toFloat() + view.translationY = burnInOffsets.y.toFloat() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt index bab04f234b3f..d4621e6e2356 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt @@ -59,8 +59,8 @@ object UdfpsFingerprintViewBinder { launch { viewModel.burnInOffsets.collect { burnInOffsets -> - view.translationX = burnInOffsets.burnInXOffset.toFloat() - view.translationY = burnInOffsets.burnInYOffset.toFloat() + view.translationX = burnInOffsets.x.toFloat() + view.translationY = burnInOffsets.y.toFloat() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index bdd9a6bf3f79..a2e930c49511 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -49,7 +49,7 @@ import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder @@ -445,7 +445,7 @@ constructor( private fun setUpClock(previewContext: Context, parentView: ViewGroup) { largeClockHostView = - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view_large) } else { val hostView = FrameLayout(previewContext) @@ -460,7 +460,7 @@ constructor( largeClockHostView.isInvisible = true smallClockHostView = - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view) } else { val resources = parentView.resources diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt new file mode 100644 index 000000000000..b58a80ff8d34 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.transitions + +import kotlinx.coroutines.flow.Flow + +/** + * Each DeviceEntryIconTransition is responsible for updating the given parameters for the current + * keyguard transition. + * * + * MUST list implementing classes in dagger module [DeviceEntryIconTransitionModule]. + */ +interface DeviceEntryIconTransition { + val deviceEntryParentViewAlpha: Flow<Float> +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt new file mode 100644 index 000000000000..9d557bb8a3b2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.keyguard.ui.transitions + +import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +@Module +abstract class DeviceEntryIconTransitionModule { + @Binds + @IntoSet + abstract fun aodToLockscreen( + impl: AodToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun dozingToLockscreen( + impl: DozingToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun dreamingToLockscreen( + impl: DreamingToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToAod( + impl: LockscreenToAodTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToDreaming( + impl: LockscreenToDreamingTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToOccluded( + impl: LockscreenToOccludedTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToPrimaryBouncer( + impl: LockscreenToPrimaryBouncerTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToGone( + impl: LockscreenToGoneTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun goneToAod(impl: GoneToAodTransitionViewModel): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun occludedToAod(impl: OccludedToAodTransitionViewModel): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun occludedToLockscreen( + impl: OccludedToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun primaryBouncerToAod( + impl: PrimaryBouncerToAodTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun primaryBouncerToLockscreen( + impl: PrimaryBouncerToLockscreenTransitionViewModel + ): DeviceEntryIconTransition +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt index c9e395402dad..af1d0df92652 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt @@ -40,7 +40,10 @@ constructor( attrs: AttributeSet?, defStyleAttrs: Int = 0, ) : FrameLayout(context, attrs, defStyleAttrs) { - val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs) + val longPressHandlingView: LongPressHandlingView = + LongPressHandlingView(context, attrs) { + context.resources.getInteger(R.integer.config_lockIconLongPress).toLong() + } val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg } val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg } val aodFpDrawable: LottieDrawable = LottieDrawable() @@ -105,7 +108,7 @@ constructor( // FINGERPRINT animatedIconDrawable.addState( getIconState(IconType.FINGERPRINT, false), - context.getDrawable(R.drawable.ic_kg_fingerprint)!!, + context.getDrawable(R.drawable.ic_fingerprint)!!, R.id.locked_fp, ) @@ -220,7 +223,7 @@ constructor( val lp = longPressHandlingView.layoutParams as LayoutParams lp.height = ViewGroup.LayoutParams.MATCH_PARENT lp.width = ViewGroup.LayoutParams.MATCH_PARENT - longPressHandlingView.setLayoutParams(lp) + longPressHandlingView.layoutParams = lp } private fun addIconImageView() { @@ -231,7 +234,7 @@ constructor( lp.height = ViewGroup.LayoutParams.MATCH_PARENT lp.width = ViewGroup.LayoutParams.MATCH_PARENT lp.gravity = Gravity.CENTER - iconView.setLayoutParams(lp) + iconView.layoutParams = lp } private fun addBgImageView() { @@ -240,7 +243,7 @@ constructor( val lp = bgView.layoutParams as LayoutParams lp.height = ViewGroup.LayoutParams.MATCH_PARENT lp.width = ViewGroup.LayoutParams.MATCH_PARENT - bgView.setLayoutParams(lp) + bgView.layoutParams = lp } fun getIconState(icon: IconType, aod: Boolean): IntArray { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index 09caf4505c3b..484d351a362e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -22,8 +22,7 @@ import android.view.View import androidx.constraintlayout.helper.widget.Layer import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R import javax.inject.Inject @@ -33,11 +32,10 @@ class AodBurnInSection @Inject constructor( private val context: Context, - private val featureFlags: FeatureFlags, ) : KeyguardSection() { override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } @@ -53,13 +51,13 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index 975d62a0b9e3..c438e4956923 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -29,14 +29,15 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor -import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.policy.ConfigurationController @@ -49,8 +50,8 @@ constructor( private val context: Context, private val configurationState: ConfigurationState, private val configurationController: ConfigurationController, - private val dozeParameters: DozeParameters, private val featureFlags: FeatureFlagsClassic, + private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore, private val notificationIconAreaController: NotificationIconAreaController, @@ -62,7 +63,7 @@ constructor( private lateinit var nic: NotificationIconContainer override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } nic = @@ -81,7 +82,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } @@ -94,6 +95,7 @@ constructor( nicAodViewModel, configurationState, configurationController, + iconBindingFailureTracker, nicAodIconViewStore, ) } else { @@ -102,7 +104,7 @@ constructor( } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } val bottomMargin = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt index ace970a01054..13ea8ff8e388 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt @@ -36,6 +36,8 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R @@ -56,12 +58,15 @@ constructor( private val featureFlags: FeatureFlags, private val lockIconViewController: Lazy<LockIconViewController>, private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>, + private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>, + private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, private val falsingManager: Lazy<FalsingManager>, ) : KeyguardSection() { private val deviceEntryIconViewId = R.id.device_entry_icon_view override fun addViews(constraintLayout: ConstraintLayout) { - if (!keyguardBottomAreaRefactor() && + if ( + !keyguardBottomAreaRefactor() && !featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS) ) { return @@ -87,6 +92,8 @@ constructor( DeviceEntryIconViewBinder.bind( it, deviceEntryIconViewModel.get(), + deviceEntryForegroundViewModel.get(), + deviceEntryBackgroundViewModel.get(), falsingManager.get(), ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index 078fefff394a..165ee364c2c8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -26,10 +26,12 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject @@ -39,23 +41,24 @@ class DefaultNotificationStackScrollLayoutSection @Inject constructor( context: Context, - featureFlags: FeatureFlags, + private val featureFlags: FeatureFlags, notificationPanelView: NotificationPanelView, sharedNotificationContainer: SharedNotificationContainer, sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, controller: NotificationStackScrollLayoutController, + notificationStackSizeCalculator: NotificationStackSizeCalculator, private val smartspaceViewModel: KeyguardSmartspaceViewModel, ) : NotificationStackScrollLayoutSection( context, - featureFlags, notificationPanelView, sharedNotificationContainer, sharedNotificationContainerViewModel, controller, + notificationStackSizeCalculator, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } constraintSet.apply { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt index 0b0f21d7a281..4abcca9d1151 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt @@ -31,9 +31,8 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.keyguard.KeyguardStatusView import com.android.keyguard.dagger.KeyguardStatusViewComponent -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardViewConfigurator +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.media.controls.ui.KeyguardMediaController import com.android.systemui.res.R @@ -49,7 +48,6 @@ class DefaultStatusViewSection @Inject constructor( private val context: Context, - private val featureFlags: FeatureFlags, private val notificationPanelView: NotificationPanelView, private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, private val keyguardViewConfigurator: Lazy<KeyguardViewConfigurator>, @@ -60,7 +58,7 @@ constructor( private val statusViewId = R.id.keyguard_status_view override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available. @@ -82,7 +80,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { constraintLayout.findViewById<KeyguardStatusView?>(R.id.keyguard_status_view)?.let { val statusViewComponent = keyguardStatusViewComponentFactory.build(it, context.display) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt index 00966f235a57..441f59d6df1d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -21,12 +21,12 @@ import android.content.Context import android.view.View import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel @@ -35,17 +35,17 @@ import kotlinx.coroutines.DisposableHandle abstract class NotificationStackScrollLayoutSection constructor( protected val context: Context, - protected val featureFlags: FeatureFlags, private val notificationPanelView: NotificationPanelView, private val sharedNotificationContainer: SharedNotificationContainer, private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, private val controller: NotificationStackScrollLayoutController, + private val notificationStackSizeCalculator: NotificationStackSizeCalculator, ) : KeyguardSection() { private val placeHolderId = R.id.nssl_placeholder private var disposableHandle: DisposableHandle? = null override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } // This moves the existing NSSL view to a different parent, as the controller is a @@ -60,7 +60,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } disposableHandle?.dispose() @@ -69,6 +69,7 @@ constructor( sharedNotificationContainer, sharedNotificationContainerViewModel, controller, + notificationStackSizeCalculator, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt index bf95c77229e9..2c45da63edb4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt @@ -26,10 +26,12 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject @@ -39,23 +41,24 @@ class SplitShadeNotificationStackScrollLayoutSection @Inject constructor( context: Context, - featureFlags: FeatureFlags, + private val featureFlags: FeatureFlags, notificationPanelView: NotificationPanelView, sharedNotificationContainer: SharedNotificationContainer, sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, controller: NotificationStackScrollLayoutController, + notificationStackSizeCalculator: NotificationStackSizeCalculator, private val smartspaceViewModel: KeyguardSmartspaceViewModel, ) : NotificationStackScrollLayoutSection( context, - featureFlags, notificationPanelView, sharedNotificationContainer, sharedNotificationContainerViewModel, controller, + notificationStackSizeCalculator, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } constraintSet.apply { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt new file mode 100644 index 000000000000..4d2af0c7bd4d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** Breaks down AOD->GONE transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class AodToGoneTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromAodTransitionInteractor.TO_GONE_DURATION, + transitionFlow = interactor.transition(KeyguardState.AOD, KeyguardState.GONE), + ) + + override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt index 024707ad2885..14de01b41867 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt @@ -17,22 +17,29 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest /** * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class AodToLockscreenTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( @@ -47,4 +54,21 @@ constructor( onStart = { 1f }, onStep = { 1f }, ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + // fade in + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ) + } else { + // background view isn't visible, so return an empty flow + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt new file mode 100644 index 000000000000..06661d0a466b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt @@ -0,0 +1,41 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject + +/** Breaks down AOD->OCCLUDED transition into discrete steps for corresponding views to consume. */ +@SysUISingleton +class AodToOccludedTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION, + transitionFlow = interactor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED), + ) + + override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt new file mode 100644 index 000000000000..3e8bbb3b4c34 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt @@ -0,0 +1,83 @@ +/* + * 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.keyguard.ui.viewmodel + +import android.content.Context +import com.android.settingslib.Utils +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart + +/** Models the UI state for the device entry icon background view. */ +@ExperimentalCoroutinesApi +class DeviceEntryBackgroundViewModel +@Inject +constructor( + val context: Context, + configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor + lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel, + aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, + goneToAodTransitionViewModel: GoneToAodTransitionViewModel, + primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel, + occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel, + occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, + dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, +) { + private val color: Flow<Int> = + configurationRepository.onAnyConfigurationChange + .map { + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface) + } + .onStart { + emit( + Utils.getColorAttrDefaultColor( + context, + com.android.internal.R.attr.colorSurface + ) + ) + } + private val alpha: Flow<Float> = + setOf( + lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + aodToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha, + goneToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + primaryBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + occludedToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + occludedToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha, + dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha, + ) + .merge() + + val viewModel: Flow<BackgroundViewModel> = + combine(color, alpha) { color, alpha -> + BackgroundViewModel( + alpha = alpha, + tint = color, + ) + } + + data class BackgroundViewModel( + val alpha: Float, + val tint: Int, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt new file mode 100644 index 000000000000..99529a100b07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt @@ -0,0 +1,92 @@ +/* + * 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.keyguard.ui.viewmodel + +import android.content.Context +import com.android.settingslib.Utils +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.res.R +import javax.inject.Inject +import kotlin.math.roundToInt +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +/** Models the UI state for the device entry icon foreground view (displayed icon). */ +@ExperimentalCoroutinesApi +class DeviceEntryForegroundViewModel +@Inject +constructor( + val context: Context, + configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + transitionInteractor: KeyguardTransitionInteractor, + deviceEntryIconViewModel: DeviceEntryIconViewModel, +) { + private val isShowingAod: Flow<Boolean> = + transitionInteractor.startedKeyguardState.map { keyguardState -> + keyguardState == KeyguardState.AOD + } + private val color: Flow<Int> = + configurationRepository.onAnyConfigurationChange + .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) } + .onStart { + emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)) + } + private val useAodIconVariant: Flow<Boolean> = + combine(isShowingAod, deviceEntryUdfpsInteractor.isUdfpsSupported) { + isTransitionToAod, + isUdfps -> + isTransitionToAod && isUdfps + } + .distinctUntilChanged() + private val padding: Flow<Int> = + configurationRepository.scaleForResolution.map { scale -> + (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) + .roundToInt() + } + + val viewModel: Flow<ForegroundIconViewModel> = + combine( + deviceEntryIconViewModel.iconType, + useAodIconVariant, + color, + padding, + ) { iconType, useAodVariant, color, padding -> + ForegroundIconViewModel( + type = iconType, + useAodVariant = useAodVariant, + tint = color, + padding = padding, + ) + } + + data class ForegroundIconViewModel( + val type: DeviceEntryIconView.IconType, + val useAodVariant: Boolean, + val tint: Int, + val padding: Int, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index 842dde352c71..5b5a10380a5b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -12,57 +12,202 @@ * 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.keyguard.ui.viewmodel -import android.graphics.Color +import android.animation.FloatEvaluator +import android.animation.IntEvaluator +import com.android.keyguard.KeyguardViewController +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart +/** Models the UI state for the containing device entry icon & long-press handling view. */ @ExperimentalCoroutinesApi -class DeviceEntryIconViewModel @Inject constructor() { - // TODO: b/305234447 update these states from the data layer - val iconViewModel: Flow<IconViewModel> = - flowOf( - IconViewModel( - type = DeviceEntryIconView.IconType.LOCK, - useAodVariant = false, - tint = Color.WHITE, - alpha = 1f, - padding = 48, +class DeviceEntryIconViewModel +@Inject +constructor( + transitions: Set<@JvmSuppressWildcards DeviceEntryIconTransition>, + burnInInteractor: BurnInInteractor, + shadeInteractor: ShadeInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + transitionInteractor: KeyguardTransitionInteractor, + val keyguardInteractor: KeyguardInteractor, + val viewModel: AodToLockscreenTransitionViewModel, + val shadeDependentFlows: ShadeDependentFlows, + private val sceneContainerFlags: SceneContainerFlags, + private val keyguardViewController: Lazy<KeyguardViewController>, + private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, + udfpsInteractor: DeviceEntryUdfpsInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, +) { + private val intEvaluator = IntEvaluator() + private val floatEvaluator = FloatEvaluator() + private val toAodFromState: Flow<KeyguardState> = + transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.from } + private val showingAlternateBouncer: Flow<Boolean> = + transitionInteractor.startedKeyguardState.map { keyguardState -> + keyguardState == KeyguardState.ALTERNATE_BOUNCER + } + private val qsProgress: Flow<Float> = shadeInteractor.qsExpansion.onStart { emit(0f) } + private val shadeExpansion: Flow<Float> = shadeInteractor.shadeExpansion.onStart { emit(0f) } + private val transitionAlpha: Flow<Float> = + transitions.map { it.deviceEntryParentViewAlpha }.merge() + private val alphaMultiplierFromShadeExpansion: Flow<Float> = + combine( + showingAlternateBouncer, + shadeExpansion, + qsProgress, + ) { showingAltBouncer, shadeExpansion, qsProgress -> + val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f) + if (showingAltBouncer) { + 1f + } else { + (1f - shadeExpansion) * (1f - interpolatedQsProgress) + } + } + // Burn-in offsets in AOD + private val nonAnimatedBurnInOffsets: Flow<BurnInOffsets> = + combine( + burnInInteractor.deviceEntryIconXOffset, + burnInInteractor.deviceEntryIconYOffset, + burnInInteractor.udfpsProgress + ) { fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress -> + BurnInOffsets( + fullyDozingBurnInX, + fullyDozingBurnInY, + fullyDozingBurnInProgress, + ) + } + // Burn-in offsets that animate based on the transition amount to AOD + private val animatedBurnInOffsets: Flow<BurnInOffsets> = + combine( + nonAnimatedBurnInOffsets, + transitionInteractor.transitionStepsToState(KeyguardState.AOD) + ) { burnInOffsets, transitionStepsToAod -> + val dozeAmount = transitionStepsToAod.value + BurnInOffsets( + intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.x), + intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.y), + floatEvaluator.evaluate(dozeAmount, 0, burnInOffsets.progress) ) - ) - val backgroundViewModel: Flow<BackgroundViewModel> = - flowOf(BackgroundViewModel(alpha = 1f, tint = Color.GRAY)) - val burnInViewModel: Flow<BurnInViewModel> = flowOf(BurnInViewModel(0, 0, 0f)) - val isLongPressEnabled: Flow<Boolean> = flowOf(true) + } + + val deviceEntryViewAlpha: Flow<Float> = + combine( + transitionAlpha, + alphaMultiplierFromShadeExpansion, + ) { alpha, alphaMultiplier -> + alpha * alphaMultiplier + } + val useBackgroundProtection: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported + val burnInOffsets: Flow<BurnInOffsets> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled -> + if (udfpsEnrolled) { + toAodFromState.flatMapLatest { fromState -> + when (fromState) { + KeyguardState.AOD, + KeyguardState.GONE, + KeyguardState.OCCLUDED, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + KeyguardState.OFF, + KeyguardState.DOZING, + KeyguardState.DREAMING, + KeyguardState.PRIMARY_BOUNCER -> nonAnimatedBurnInOffsets + KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets + KeyguardState.LOCKSCREEN -> + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = nonAnimatedBurnInOffsets, + flowWhenShadeIsNotExpanded = animatedBurnInOffsets, + ) + } + } + } else { + // If UDFPS isn't enrolled, we don't show any UI on AOD so there's no need + // to use burn in offsets at all + flowOf(BurnInOffsets(x = 0, y = 0, progress = 0f)) + } + } + val iconType: Flow<DeviceEntryIconView.IconType> = + combine( + udfpsInteractor.isListeningForUdfps, + deviceEntryInteractor.isUnlocked, + ) { isListeningForUdfps, isUnlocked -> + if (isUnlocked) { + DeviceEntryIconView.IconType.UNLOCK + } else { + if (isListeningForUdfps) { + DeviceEntryIconView.IconType.FINGERPRINT + } else { + DeviceEntryIconView.IconType.LOCK + } + } + } + val isLongPressEnabled: Flow<Boolean> = + combine( + iconType, + deviceEntryUdfpsInteractor.isUdfpsSupported, + ) { deviceEntryStatus, isUdfps -> + when (deviceEntryStatus) { + DeviceEntryIconView.IconType.LOCK -> isUdfps + DeviceEntryIconView.IconType.UNLOCK -> true + DeviceEntryIconView.IconType.FINGERPRINT -> false + } + } val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> = - flowOf(DeviceEntryIconView.AccessibilityHintType.NONE) + combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled -> + if (longPressEnabled) { + deviceEntryStatus.toAccessibilityHintType() + } else { + DeviceEntryIconView.AccessibilityHintType.NONE + } + } fun onLongPress() { - // TODO() vibrate & perform action based on current lock/unlock state - } - data class BurnInViewModel( - val x: Int, // current x burn in offset based on the aodTransitionAmount - val y: Int, // current y burn in offset based on the aodTransitionAmount - val progress: Float, // current progress based on the aodTransitionAmount - ) + deviceEntryHapticsInteractor.vibrateSuccess() - class IconViewModel( - val type: DeviceEntryIconView.IconType, - val useAodVariant: Boolean, - val tint: Int, - val alpha: Float, - val padding: Int, - ) + // TODO (b/309804148): play auth ripple via an interactor - class BackgroundViewModel( - val alpha: Float, - val tint: Int, - ) + if (sceneContainerFlags.isEnabled()) { + deviceEntryInteractor.attemptDeviceEntry() + } else { + keyguardViewController.get().showPrimaryBouncer(/* scrim */ true) + } + } + + private fun DeviceEntryIconView.IconType.toAccessibilityHintType(): + DeviceEntryIconView.AccessibilityHintType { + return when (this) { + DeviceEntryIconView.IconType.LOCK -> + DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE + DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER + DeviceEntryIconView.IconType.FINGERPRINT -> + DeviceEntryIconView.AccessibilityHintType.NONE + } + } } + +data class BurnInOffsets( + val x: Int, // current x burn in offset based on the aodTransitionAmount + val y: Int, // current y burn in offset based on the aodTransitionAmount + val progress: Float, // current progress based on the aodTransitionAmount +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..27fb8a3d2473 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down DOZING->LOCKSCREEN transition into discrete steps for corresponding views to consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DozingToLockscreenTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation: KeyguardTransitionAnimationFlow = + KeyguardTransitionAnimationFlow( + transitionDuration = FromDozingTransitionInteractor.TO_LOCKSCREEN_DURATION, + transitionFlow = interactor.dozingToLockscreenTransition, + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(1f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index e24d326850e0..a3b8b85fc53d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -18,27 +18,34 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest /** * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to * consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class DreamingToLockscreenTransitionViewModel @Inject constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, - private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor -) { + private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor, + private val deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { fun startTransition() = fromDreamingTransitionInteractor.startToLockscreenTransition() private val transitionAnimation = @@ -88,4 +95,15 @@ constructor( duration = 250.milliseconds, onStep = { it }, ) + + val deviceEntryBackgroundViewAlpha = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + // immediately show; will fade in with deviceEntryParentViewAlpha + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } + override val deviceEntryParentViewAlpha = lockscreenAlpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt index 601dbccb1de1..62b2281ae473 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt @@ -18,20 +18,27 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest /** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class GoneToAodTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( @@ -60,4 +67,21 @@ constructor( onStart = { 0f }, onStep = { it }, ) + val deviceEntryBackgroundViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled -> + if (udfpsEnrolled) { + // fade in at the end of the transition to give time for FP to start running + // and avoid a flicker of the unlocked icon + transitionAnimation.createFlow( + startTime = 1100.milliseconds, + duration = 200.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ) + } else { + emptyFlow() + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt new file mode 100644 index 000000000000..2bf12e8e33b2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt @@ -0,0 +1,85 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest + +/** + * Breaks down LOCKSCREEN->AOD transition into discrete steps for corresponding views to consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class LockscreenToAodTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromLockscreenTransitionInteractor.TO_AOD_DURATION, + transitionFlow = interactor.lockscreenToAodTransition, + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + flowWhenShadeIsNotExpanded = + transitionAnimation.createFlow( + duration = 300.milliseconds, + onStep = { 1 - it }, + onFinish = { 0f }, + ), + ) + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { + isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = // fade in + transitionAnimation.createFlow( + duration = 300.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ), + flowWhenShadeIsNotExpanded = transitionAnimation.immediatelyTransitionTo(1f), + ) + } else { + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + flowWhenShadeIsNotExpanded = // fade out + transitionAnimation.createFlow( + duration = 200.milliseconds, + onStep = { 1f - it }, + onFinish = { 0f }, + ), + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt index a3ae67d906bd..52296137a3d6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -34,7 +35,8 @@ class LockscreenToDreamingTransitionViewModel @Inject constructor( interactor: KeyguardTransitionInteractor, -) { + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( transitionDuration = TO_DREAMING_DURATION, @@ -60,6 +62,12 @@ constructor( onStep = { 1f - it }, ) + override val deviceEntryParentViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsNotExpanded = lockscreenAlpha, + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + ) + companion object { @JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt new file mode 100644 index 000000000000..59e5aa845051 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt @@ -0,0 +1,48 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down LOCKSCREEN->GONE transition into discrete steps for corresponding views to consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class LockscreenToGoneTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromLockscreenTransitionInteractor.TO_GONE_DURATION, + transitionFlow = interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.GONE), + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt index d3ea89ce1935..d49bc4994b0f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -33,8 +34,9 @@ import kotlinx.coroutines.flow.Flow class LockscreenToOccludedTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( transitionDuration = TO_OCCLUDED_DURATION, @@ -59,4 +61,10 @@ constructor( interpolator = EMPHASIZED_ACCELERATE, ) } + + override val deviceEntryParentViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsNotExpanded = lockscreenAlpha, + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt new file mode 100644 index 000000000000..f04b67a1d4d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down LOCKSCREEN->PRIMARY BOUNCER transition into discrete steps for corresponding views to + * consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class LockscreenToPrimaryBouncerTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + transitionFlow = + interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER), + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsNotExpanded = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1f - it }, + onFinish = { 0f } + ), + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f) + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt new file mode 100644 index 000000000000..f7cff9b28542 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest + +/** Breaks down OCCLUDED->AOD transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class OccludedToAodTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromOccludedTransitionInteractor.TO_AOD_DURATION, + transitionFlow = interactor.transition(KeyguardState.OCCLUDED, KeyguardState.AOD), + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) + + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled + -> + if (udfpsEnrolledAndEnabled) { + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt index 6845c55b8385..0bdc85d05106 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt @@ -18,23 +18,30 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest /** * Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to * consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class OccludedToLockscreenTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( transitionDuration = TO_LOCKSCREEN_DURATION, @@ -58,4 +65,16 @@ constructor( duration = 250.milliseconds, onStep = { it }, ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { + isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt new file mode 100644 index 000000000000..05a6d5810be3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt @@ -0,0 +1,74 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest + +/** + * Breaks down PRIMARY BOUNCER->AOD transition into discrete steps for corresponding views to + * consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class PrimaryBouncerToAodTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION, + transitionFlow = + interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD), + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + transitionAnimation.immediatelyTransitionTo(0f) + } else { + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { + isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + transitionAnimation.createFlow( + duration = 300.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ) + } else { + emptyFlow() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..3cf793ab9dc8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt @@ -0,0 +1,62 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest + +/** + * Breaks down PRIMARY BOUNCER->LOCKSCREEN transition into discrete steps for corresponding views to + * consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class PrimaryBouncerToLockscreenTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION, + transitionFlow = + interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.LOCKSCREEN), + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(1f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt new file mode 100644 index 000000000000..e45d537155fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.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.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** Helper for flows that depend on the shade expansion */ +class ShadeDependentFlows +@Inject +constructor( + transitionInteractor: KeyguardTransitionInteractor, + shadeInteractor: ShadeInteractor, +) { + /** When the last keyguard state transition started, was the shade fully expanded? */ + private val lastStartedTransitionHadShadeFullyExpanded: Flow<Boolean> = + transitionInteractor.startedKeyguardState.sample(shadeInteractor.isAnyFullyExpanded) + + /** + * Decide which flow to use depending on the shade expansion state at the start of the last + * keyguard state transition. + */ + fun <T> transitionFlow( + flowWhenShadeIsExpanded: Flow<T>, + flowWhenShadeIsNotExpanded: Flow<T>, + ): Flow<T> { + val filteredFlowWhenShadeIsExpanded = + flowWhenShadeIsExpanded + .sample(lastStartedTransitionHadShadeFullyExpanded, ::Pair) + .filter { (_, shadeFullyExpanded) -> shadeFullyExpanded } + .map { (valueWhenShadeIsExpanded, _) -> valueWhenShadeIsExpanded } + val filteredFlowWhenShadeIsNotExpanded = + flowWhenShadeIsNotExpanded + .sample(lastStartedTransitionHadShadeFullyExpanded, ::Pair) + .filter { (_, shadeFullyExpanded) -> !shadeFullyExpanded } + .map { (valueWhenShadeIsNotExpanded, _) -> valueWhenShadeIsNotExpanded } + return merge(filteredFlowWhenShadeIsExpanded, filteredFlowWhenShadeIsNotExpanded) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt index c10a4635644f..6e77e13e8513 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt @@ -17,9 +17,9 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context -import com.android.systemui.res.R -import com.android.systemui.keyguard.domain.interactor.BurnInOffsets +import com.android.systemui.keyguard.domain.interactor.Offsets import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.res.R import javax.inject.Inject import kotlin.math.roundToInt import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -35,7 +35,7 @@ constructor( val context: Context, ) { val alpha: Flow<Float> = interactor.dozeAmount - val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets val isVisible: Flow<Boolean> = alpha.map { it != 0f } // Padding between the fingerprint icon and its bounding box in pixels. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt index 0b1079f69d6e..642904df21b7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt @@ -19,9 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context import androidx.annotation.ColorInt import com.android.settingslib.Utils.getColorAttrDefaultColor -import com.android.systemui.keyguard.domain.interactor.BurnInOffsets import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.Offsets import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState @@ -185,7 +185,7 @@ constructor( keyguardInteractor, ) { val dozeAmount: Flow<Float> = interactor.dozeAmount - val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets // Padding between the fingerprint icon and its bounding box in pixels. val padding: Flow<Int> = diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt index 2034d97f211c..dcbf670460ef 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt @@ -37,8 +37,6 @@ import com.android.systemui.Dumpable import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory @@ -70,7 +68,6 @@ constructor( @Main private val fgExecutor: Executor, @Background private val bgExecutor: Executor, dumpManager: DumpManager, - private val featureFlags: FeatureFlagsClassic, ) : MediaDataManager.Listener, Dumpable { private val listeners: MutableSet<Listener> = mutableSetOf() @@ -392,13 +389,6 @@ constructor( ) } - if (!featureFlags.isEnabled(Flags.MEDIA_DEVICE_NAME_FIX)) { - if (controller == null || routingSession != null) { - return routingSession?.name?.toString() ?: device?.name - } - return null - } - if (controller == null) { // In resume state, we don't have a controller - just use the device name return device?.name diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index 1ec43c5e3091..d277f32d5e54 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -60,7 +60,7 @@ constructor( } companion object { - @JvmField val GUTS_ANIMATION_DURATION = 500L + @JvmField val GUTS_ANIMATION_DURATION = 234L } /** A listener when the current dimensions of the player change */ @@ -234,7 +234,8 @@ constructor( currentStartLocation, currentEndLocation, currentTransitionProgress, - applyImmediately = false + applyImmediately = false, + isGutsAnimation = true, ) } @@ -254,7 +255,8 @@ constructor( currentStartLocation, currentEndLocation, currentTransitionProgress, - applyImmediately = immediate + applyImmediately = immediate, + isGutsAnimation = true, ) } @@ -414,7 +416,10 @@ constructor( * it's not available, it will recreate one by measuring, which may be expensive. */ @VisibleForTesting - fun obtainViewState(state: MediaHostState?): TransitionViewState? { + fun obtainViewState( + state: MediaHostState?, + isGutsAnimation: Boolean = false + ): TransitionViewState? { if (state == null || state.measurementInput == null) { return null } @@ -423,7 +428,7 @@ constructor( val viewState = viewStates[cacheKey] if (viewState != null) { // we already have cached this measurement, let's continue - if (state.squishFraction <= 1f) { + if (state.squishFraction <= 1f && !isGutsAnimation) { return squishViewState(viewState, state.squishFraction) } return viewState @@ -455,13 +460,14 @@ constructor( // Given that we have a measurement and a view, let's get (guaranteed) viewstates // from the start and end state and interpolate them - val startViewState = obtainViewState(startState) as TransitionViewState + val startViewState = obtainViewState(startState, isGutsAnimation) as TransitionViewState val endState = state.copy().also { it.expansion = 1.0f } - val endViewState = obtainViewState(endState) as TransitionViewState + val endViewState = obtainViewState(endState, isGutsAnimation) as TransitionViewState result = layoutController.getInterpolatedState(startViewState, endViewState, state.expansion) } - if (state.squishFraction <= 1f) { + // Skip the adjustments of squish view state if UMO changes due to guts animation. + if (state.squishFraction <= 1f && !isGutsAnimation) { return squishViewState(result, state.squishFraction) } return result @@ -521,7 +527,8 @@ constructor( @MediaLocation startLocation: Int, @MediaLocation endLocation: Int, transitionProgress: Float, - applyImmediately: Boolean + applyImmediately: Boolean, + isGutsAnimation: Boolean = false, ) = traceSection("MediaViewController#setCurrentState") { currentEndLocation = endLocation @@ -537,7 +544,7 @@ constructor( // Obtain the view state that we'd want to be at the end // The view might not be bound yet or has never been measured and in that case will be // reset once the state is fully available - var endViewState = obtainViewState(endHostState) ?: return + var endViewState = obtainViewState(endHostState, isGutsAnimation) ?: return endViewState = updateViewStateSize(endViewState, endLocation, tmpState2)!! layoutController.setMeasureState(endViewState) @@ -548,7 +555,7 @@ constructor( } val result: TransitionViewState - var startViewState = obtainViewState(startHostState) + var startViewState = obtainViewState(startHostState, isGutsAnimation) startViewState = updateViewStateSize(startViewState, startLocation, tmpState3) if (!endHostState.visible) { @@ -602,7 +609,8 @@ constructor( applyImmediately, shouldAnimate, animationDuration, - animationDelay + animationDelay, + isGutsAnimation, ) } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 79aedffc4498..9f5e1b79765e 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -35,6 +35,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.Display; import android.view.IWindowManager; import android.view.View; @@ -94,6 +95,9 @@ public class NavigationBarControllerImpl implements @VisibleForTesting SparseArray<NavigationBar> mNavigationBars = new SparseArray<>(); + /** Local cache for {@link IWindowManager#hasNavigationBar(int)}. */ + private SparseBooleanArray mHasNavBar = new SparseBooleanArray(); + // Tracks config changes that will actually recreate the nav bar private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_FONT_SCALE @@ -221,10 +225,16 @@ public class NavigationBarControllerImpl implements } private boolean shouldCreateNavBarAndTaskBar(int displayId) { + if (mHasNavBar.indexOfKey(displayId) > -1) { + return mHasNavBar.get(displayId); + } + final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); try { - return wms.hasNavigationBar(displayId); + boolean hasNavigationBar = wms.hasNavigationBar(displayId); + mHasNavBar.put(displayId, hasNavigationBar); + return hasNavigationBar; } catch (RemoteException e) { // Cannot get wms, just return false with warning message. Log.w(TAG, "Cannot get WindowManager."); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt index 736f7cfbfce9..ae554d954580 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt @@ -26,6 +26,7 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor import com.android.systemui.qs.tiles.base.logging.QSTileLogger import com.android.systemui.qs.tiles.impl.di.QSTileComponent +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.user.data.repository.UserRepository @@ -60,12 +61,17 @@ sealed interface QSTileViewModelFactory<T> { ) : QSTileViewModelFactory<T> { /** - * Creates [QSTileViewModelImpl] based on the interactors obtained from [component]. - * Reference of that [component] is then stored along the view model. + * Creates [QSTileViewModelImpl] based on the interactors obtained from [QSTileComponent]. + * Reference of that [QSTileComponent] is then stored along the view model. */ - fun create(tileSpec: TileSpec, component: QSTileComponent<T>): QSTileViewModelImpl<T> = - QSTileViewModelImpl( - qsTileConfigProvider.getConfig(tileSpec.spec), + fun create( + tileSpec: TileSpec, + componentFactory: (config: QSTileConfig) -> QSTileComponent<T> + ): QSTileViewModelImpl<T> { + val config = qsTileConfigProvider.getConfig(tileSpec.spec) + val component = componentFactory(config) + return QSTileViewModelImpl( + config, component::userActionInteractor, component::dataInteractor, component::dataToStateMapper, @@ -77,6 +83,7 @@ sealed interface QSTileViewModelFactory<T> { systemClock, backgroundDispatcher, ) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt index 12a083e990f8..5e19439cd643 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt @@ -116,7 +116,7 @@ class QSTileViewModelImpl<DATA_TYPE>( ) override fun forceUpdate() { - forceUpdates.tryEmit(Unit) + tileScope.launch { forceUpdates.emit(Unit) } } override fun onUserChanged(user: UserHandle) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt index 7d7af64a3038..27007bbf7aee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt @@ -19,6 +19,11 @@ package com.android.systemui.qs.tiles.di import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.qs.QSFactory import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent +import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter @@ -34,18 +39,31 @@ constructor( private val adapterFactory: QSTileViewModelAdapter.Factory, private val tileMap: Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>, + private val customTileComponentBuilder: CustomTileComponent.Builder, + private val customTileViewModelFactory: QSTileViewModelFactory.Component<CustomTileDataModel>, ) : QSFactory { init { for (viewModelTileSpec in tileMap.keys) { - // throws an exception when there is no config for a tileSpec of an injected viewModel - qsTileConfigProvider.getConfig(viewModelTileSpec) + require(qsTileConfigProvider.hasConfig(viewModelTileSpec)) { + "No config for $viewModelTileSpec" + } } } - override fun createTile(tileSpec: String): QSTile? = - tileMap[tileSpec]?.let { - val tile = it.get() - adapterFactory.create(tile) + override fun createTile(tileSpec: String): QSTile? { + val viewModel: QSTileViewModel = + when (val spec = TileSpec.create(tileSpec)) { + is TileSpec.CustomTileSpec -> createCustomTileViewModel(spec) + is TileSpec.PlatformTileSpec -> tileMap[tileSpec]?.get() + is TileSpec.Invalid -> null + } + ?: return null + return adapterFactory.create(viewModel) + } + + private fun createCustomTileViewModel(spec: TileSpec.CustomTileSpec): QSTileViewModel = + customTileViewModelFactory.create(spec) { config -> + customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt index 7b4b55702f55..5bdb592a3558 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt @@ -20,9 +20,12 @@ import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View +import android.view.View.AccessibilityDelegate import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup +import android.view.accessibility.AccessibilityNodeInfo +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction import android.widget.ImageView import android.widget.Switch import android.widget.TextView @@ -32,13 +35,18 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.time.SystemClock +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.isActive +import kotlinx.coroutines.withContext /** Dialog for showing active, connected and saved bluetooth devices. */ @SysUISingleton @@ -47,6 +55,7 @@ constructor( private val bluetoothToggleInitialValue: Boolean, private val subtitleResIdInitialValue: Int, private val bluetoothTileDialogCallback: BluetoothTileDialogCallback, + @Main private val mainDispatcher: CoroutineDispatcher, private val systemClock: SystemClock, private val uiEventLogger: UiEventLogger, private val logger: BluetoothTileDialogLogger, @@ -65,13 +74,17 @@ constructor( private val deviceItemAdapter: Adapter = Adapter(bluetoothTileDialogCallback) + private var lastUiUpdateMs: Long = -1 + + private var lastItemRow: Int = -1 + private lateinit var toggleView: Switch private lateinit var subtitleTextView: TextView private lateinit var doneButton: View private lateinit var seeAllViewGroup: View private lateinit var pairNewDeviceViewGroup: View - private lateinit var seeAllText: View - private lateinit var pairNewDeviceText: View + private lateinit var seeAllRow: View + private lateinit var pairNewDeviceRow: View private lateinit var deviceListView: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { @@ -88,8 +101,8 @@ constructor( doneButton = requireViewById(R.id.done_button) seeAllViewGroup = requireViewById(R.id.see_all_layout_group) pairNewDeviceViewGroup = requireViewById(R.id.pair_new_device_layout_group) - seeAllText = requireViewById(R.id.see_all_text) - pairNewDeviceText = requireViewById(R.id.pair_new_device_text) + seeAllRow = requireViewById(R.id.see_all_clickable_row) + pairNewDeviceRow = requireViewById(R.id.pair_new_device_clickable_row) deviceListView = requireViewById<RecyclerView>(R.id.device_list) setupToggle() @@ -97,22 +110,37 @@ constructor( subtitleTextView.text = context.getString(subtitleResIdInitialValue) doneButton.setOnClickListener { dismiss() } - seeAllText.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) } - pairNewDeviceText.setOnClickListener { + seeAllRow.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) } + pairNewDeviceRow.setOnClickListener { bluetoothTileDialogCallback.onPairNewDeviceClicked(it) } } - internal fun onDeviceItemUpdated( + override fun start() { + lastUiUpdateMs = systemClock.elapsedRealtime() + } + + internal suspend fun onDeviceItemUpdated( deviceItem: List<DeviceItem>, showSeeAll: Boolean, showPairNewDevice: Boolean ) { - val start = systemClock.elapsedRealtime() - deviceItemAdapter.refreshDeviceItemList(deviceItem) { - seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE - pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE - logger.logDeviceUiUpdate(systemClock.elapsedRealtime() - start) + withContext(mainDispatcher) { + val start = systemClock.elapsedRealtime() + val itemRow = deviceItem.size + showSeeAll.toInt() + showPairNewDevice.toInt() + // Add a slight delay for smoother dialog height change + if (itemRow != lastItemRow) { + delay(MIN_HEIGHT_CHANGE_INTERVAL_MS - (start - lastUiUpdateMs)) + } + if (isActive) { + deviceItemAdapter.refreshDeviceItemList(deviceItem) { + seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE + pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE + lastUiUpdateMs = systemClock.elapsedRealtime() + lastItemRow = itemRow + logger.logDeviceUiUpdate(lastUiUpdateMs - start) + } + } } } @@ -169,7 +197,8 @@ constructor( deviceItem1.iconWithDescription?.second == deviceItem2.iconWithDescription?.second && deviceItem1.background == deviceItem2.background && - deviceItem1.isEnabled == deviceItem2.isEnabled + deviceItem1.isEnabled == deviceItem2.isEnabled && + deviceItem1.actionAccessibilityLabel == deviceItem2.actionAccessibilityLabel } } @@ -213,6 +242,21 @@ constructor( mutableDeviceItemClick.tryEmit(item) uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED) } + accessibilityDelegate = + object : AccessibilityDelegate() { + override fun onInitializeAccessibilityNodeInfo( + host: View, + info: AccessibilityNodeInfo + ) { + super.onInitializeAccessibilityNodeInfo(host, info) + info.addAction( + AccessibilityAction( + AccessibilityAction.ACTION_CLICK.id, + item.actionAccessibilityLabel + ) + ) + } + } } nameView.text = item.deviceName summaryView.text = item.connectionSummary @@ -230,6 +274,7 @@ constructor( } internal companion object { + const val MIN_HEIGHT_CHANGE_INTERVAL_MS = 800L const val MAX_DEVICE_ITEM_ENTRY = 3 const val ACTION_BLUETOOTH_DEVICE_DETAILS = "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS" @@ -238,5 +283,9 @@ constructor( const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS" const val DISABLED_ALPHA = 0.3f const val ENABLED_ALPHA = 1f + + private fun Boolean.toInt(): Int { + return if (this) 1 else 0 + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt index f7e0de3ed883..34c2aba1a71f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt @@ -40,7 +40,6 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -76,6 +75,7 @@ constructor( dismissDialog() var updateDeviceItemJob: Job? = null + var updateDialogUiJob: Job? = null job = coroutineScope.launch(mainDispatcher) { @@ -93,10 +93,9 @@ constructor( ) } ?: dialog!!.show() + updateDeviceItemJob?.cancel() updateDeviceItemJob = launch { - // Add a slight delay for smoother dialog bounds change - delay(FIRST_LOAD_DELAY_MS) deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD) } @@ -128,11 +127,14 @@ constructor( deviceItemInteractor.deviceItemUpdate .onEach { - dialog!!.onDeviceItemUpdated( - it.take(MAX_DEVICE_ITEM_ENTRY), - showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY, - showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled - ) + updateDialogUiJob?.cancel() + updateDialogUiJob = launch { + dialog?.onDeviceItemUpdated( + it.take(MAX_DEVICE_ITEM_ENTRY), + showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY, + showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled + ) + } } .launchIn(this) @@ -153,6 +155,7 @@ constructor( bluetoothStateInteractor.isBluetoothEnabled, getSubtitleResId(bluetoothStateInteractor.isBluetoothEnabled), this@BluetoothTileDialogViewModel, + mainDispatcher, systemClock, uiEventLogger, logger, @@ -205,7 +208,6 @@ constructor( companion object { private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog" - private const val FIRST_LOAD_DELAY_MS = 500L private fun getSubtitleResId(isBluetoothEnabled: Boolean) = if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle else R.string.bt_is_off diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt index 2c8d2a0806d2..1c621b87533d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt @@ -49,5 +49,6 @@ data class DeviceItem( val connectionSummary: String = "", val iconWithDescription: Pair<Drawable, String>? = null, val background: Int? = null, - var isEnabled: Boolean = true + var isEnabled: Boolean = true, + var actionAccessibilityLabel: String = "", ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt index 7bb1619c5001..1c9be0f105b2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt @@ -28,6 +28,10 @@ private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy private val connected = R.string.quick_settings_bluetooth_device_connected private val saved = R.string.quick_settings_bluetooth_device_saved +private val actionAccessibilityLabelActivate = + R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate +private val actionAccessibilityLabelDisconnect = + R.string.accessibility_quick_settings_bluetooth_device_tap_to_disconnect /** Factories to create different types of Bluetooth device items from CachedBluetoothDevice. */ internal abstract class DeviceItemFactory { @@ -60,6 +64,7 @@ internal class ActiveMediaDeviceItemFactory : DeviceItemFactory() { }, background = backgroundOn, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect), ) } } @@ -87,6 +92,7 @@ internal class AvailableMediaDeviceItemFactory : DeviceItemFactory() { }, background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate), ) } } @@ -112,6 +118,7 @@ internal class ConnectedDeviceItemFactory : DeviceItemFactory() { }, background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect), ) } } @@ -137,6 +144,7 @@ internal class SavedDeviceItemFactory : DeviceItemFactory() { }, background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt index 761274e96e43..14bf25d10d88 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt @@ -19,17 +19,18 @@ package com.android.systemui.qs.tiles.impl.custom import android.os.UserHandle import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.di.QSTileScope import javax.inject.Inject import kotlinx.coroutines.flow.Flow @QSTileScope -class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileData> { +class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileDataModel> { override fun tileData( user: UserHandle, triggers: Flow<DataUpdateTrigger> - ): Flow<CustomTileData> { + ): Flow<CustomTileDataModel> { TODO("Not yet implemented") } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt index f7bec024b7bb..e23a5c2f0b6a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt @@ -17,15 +17,16 @@ package com.android.systemui.qs.tiles.impl.custom import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.di.QSTileScope import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import javax.inject.Inject @QSTileScope -class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileData> { +class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileDataModel> { - override fun map(config: QSTileConfig, data: CustomTileData): QSTileState { + override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState { TODO("Not yet implemented") } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt index 6c1c1a34abc0..f34704be8bc5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt @@ -18,14 +18,15 @@ package com.android.systemui.qs.tiles.impl.custom import com.android.systemui.qs.tiles.base.interactor.QSTileInput import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.di.QSTileScope import javax.inject.Inject @QSTileScope class CustomTileUserActionInteractor @Inject constructor() : - QSTileUserActionInteractor<CustomTileData> { + QSTileUserActionInteractor<CustomTileDataModel> { - override suspend fun handleInput(input: QSTileInput<CustomTileData>) { + override suspend fun handleInput(input: QSTileInput<CustomTileDataModel>) { TODO("Not yet implemented") } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt index 01df90662579..88bc8fa81e1a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt @@ -16,13 +16,14 @@ package com.android.systemui.qs.tiles.impl.custom.di +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.di.QSTileComponent import com.android.systemui.qs.tiles.impl.di.QSTileScope import dagger.Subcomponent @QSTileScope @Subcomponent(modules = [QSTileConfigModule::class, CustomTileModule::class]) -interface CustomTileComponent : QSTileComponent<Any> { +interface CustomTileComponent : QSTileComponent<CustomTileDataModel> { @Subcomponent.Builder interface Builder { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt index 482bf9bcd051..83767aa9d444 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt @@ -19,13 +19,13 @@ package com.android.systemui.qs.tiles.impl.custom.di import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor -import com.android.systemui.qs.tiles.impl.custom.CustomTileData import com.android.systemui.qs.tiles.impl.custom.CustomTileInteractor import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import dagger.Binds import dagger.Module @@ -36,15 +36,15 @@ interface CustomTileModule { @Binds fun bindDataInteractor( dataInteractor: CustomTileInteractor - ): QSTileDataInteractor<CustomTileData> + ): QSTileDataInteractor<CustomTileDataModel> @Binds fun bindUserActionInteractor( userActionInteractor: CustomTileUserActionInteractor - ): QSTileUserActionInteractor<CustomTileData> + ): QSTileUserActionInteractor<CustomTileDataModel> @Binds - fun bindMapper(customTileMapper: CustomTileMapper): QSTileDataToStateMapper<CustomTileData> + fun bindMapper(customTileMapper: CustomTileMapper): QSTileDataToStateMapper<CustomTileDataModel> @Binds fun bindCustomTileDefaultsRepository( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt index bb5a229a0696..f095c01126c4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.impl.custom +package com.android.systemui.qs.tiles.impl.custom.domain.entity import android.content.ComponentName import android.graphics.drawable.Icon @@ -22,12 +22,11 @@ import android.os.UserHandle import android.service.quicksettings.Tile import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent -data class CustomTileData( +data class CustomTileDataModel( val user: UserHandle, val componentName: ComponentName, val tile: Tile, val callingAppUid: Int, - val isActive: Boolean, val hasPendingBind: Boolean, val shouldShowChevron: Boolean, val defaultTileLabel: CharSequence?, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt index 3f3b94e65294..0609e797d53b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt @@ -18,20 +18,31 @@ package com.android.systemui.qs.tiles.viewmodel import com.android.internal.util.Preconditions import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject interface QSTileConfigProvider { /** - * Returns a [QSTileConfig] for a [tileSpec] or throws [IllegalArgumentException] if there is no - * config for such [tileSpec]. + * Returns a [QSTileConfig] for a [tileSpec]: + * - injected config for [TileSpec.PlatformTileSpec] or throws [IllegalArgumentException] if + * there is none + * - new config for [TileSpec.CustomTileSpec]. + * - throws [IllegalArgumentException] for [TileSpec.Invalid] */ fun getConfig(tileSpec: String): QSTileConfig + + fun hasConfig(tileSpec: String): Boolean } @SysUISingleton -class QSTileConfigProviderImpl @Inject constructor(private val configs: Map<String, QSTileConfig>) : - QSTileConfigProvider { +class QSTileConfigProviderImpl +@Inject +constructor( + private val configs: Map<String, QSTileConfig>, + private val qsEventLogger: QsEventLogger, +) : QSTileConfigProvider { init { for (entry in configs.entries) { @@ -44,6 +55,26 @@ class QSTileConfigProviderImpl @Inject constructor(private val configs: Map<Stri } } + override fun hasConfig(tileSpec: String): Boolean = + when (TileSpec.create(tileSpec)) { + is TileSpec.PlatformTileSpec -> configs.containsKey(tileSpec) + is TileSpec.CustomTileSpec -> true + is TileSpec.Invalid -> false + } + override fun getConfig(tileSpec: String): QSTileConfig = - configs[tileSpec] ?: throw IllegalArgumentException("There is no config for spec=$tileSpec") + when (val spec = TileSpec.create(tileSpec)) { + is TileSpec.PlatformTileSpec -> { + configs[tileSpec] + ?: throw IllegalArgumentException("There is no config for spec=$tileSpec") + } + is TileSpec.CustomTileSpec -> + QSTileConfig( + spec, + QSTileUIConfig.Empty, + qsEventLogger.getNewInstanceId(), + ) + is TileSpec.Invalid -> + throw IllegalArgumentException("TileSpec.Invalid doesn't support configs") + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt index 30b87cc9e662..f9e0b160acd6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -18,7 +18,10 @@ package com.android.systemui.qs.tiles.viewmodel import android.content.Context import android.service.quicksettings.Tile +import android.view.View +import android.widget.Switch import com.android.systemui.common.shared.model.Icon +import kotlin.reflect.KClass /** * Represents current a state of the tile to be displayed in on the view. Consider using @@ -111,7 +114,7 @@ data class QSTileState( var stateDescription: CharSequence? = null var sideViewIcon: SideViewIcon = SideViewIcon.None var enabledState: EnabledState = EnabledState.ENABLED - var expandedAccessibilityClassName: String? = null + var expandedAccessibilityClass: KClass<out View>? = Switch::class fun build(): QSTileState = QSTileState( @@ -124,7 +127,7 @@ data class QSTileState( stateDescription, sideViewIcon, enabledState, - expandedAccessibilityClassName, + expandedAccessibilityClass?.qualifiedName, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 9edd2c6cf927..5993cf104318 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -16,8 +16,8 @@ package com.android.systemui.qs.ui.viewmodel -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import javax.inject.Inject @@ -26,11 +26,9 @@ import javax.inject.Inject class QuickSettingsSceneViewModel @Inject constructor( - private val bouncerInteractor: BouncerInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, val shadeHeaderViewModel: ShadeHeaderViewModel, ) { /** Notifies that some content in quick settings was clicked. */ - fun onContentClicked() { - bouncerInteractor.showOrUnlockDevice() - } + fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry() } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 91b4d1778e1c..ca2828b99d95 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -19,8 +19,7 @@ package com.android.systemui.scene.domain.startable import com.android.systemui.CoreStartable -import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorActual import com.android.systemui.dagger.SysUISingleton @@ -45,6 +44,7 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_B import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.emptyFlow @@ -64,7 +64,7 @@ constructor( @Application private val applicationScope: CoroutineScope, private val sceneInteractor: SceneInteractor, private val deviceEntryInteractor: DeviceEntryInteractor, - private val authenticationInteractor: AuthenticationInteractor, + private val bouncerInteractor: BouncerInteractor, private val keyguardInteractor: KeyguardInteractor, private val flags: SceneContainerFlags, private val sysUiState: SysUiState, @@ -121,6 +121,17 @@ constructor( /** Switches between scenes based on ever-changing application state. */ private fun automaticallySwitchScenes() { applicationScope.launch { + // TODO (b/308001302): Move this to a bouncer specific interactor. + bouncerInteractor.onImeHidden.collectLatest { + if (sceneInteractor.desiredScene.value.key == SceneKey.Bouncer) { + sceneInteractor.changeScene( + scene = SceneModel(SceneKey.Lockscreen), + loggingReason = "IME hidden", + ) + } + } + } + applicationScope.launch { deviceEntryInteractor.isUnlocked .mapNotNull { isUnlocked -> val renderedScenes = @@ -132,41 +143,41 @@ constructor( transitionState.toScene, ) } - when { - isUnlocked -> - when { - // When the device becomes unlocked in Bouncer, go to Gone. - renderedScenes.contains(SceneKey.Bouncer) -> - SceneKey.Gone to "device unlocked in Bouncer scene" - - // When the device becomes unlocked in Lockscreen, go to Gone if - // bypass is enabled. - renderedScenes.contains(SceneKey.Lockscreen) -> - if (deviceEntryInteractor.isBypassEnabled.value) { - SceneKey.Gone to - "device unlocked in Lockscreen scene with bypass" - } else { - null - } - - // We got unlocked while on a scene that's not Lockscreen or - // Bouncer, no need to change scenes. - else -> null - } - - // When the device becomes locked, to Lockscreen. - !isUnlocked -> - when { - // Already on lockscreen or bouncer, no need to change scenes. - renderedScenes.contains(SceneKey.Lockscreen) || - renderedScenes.contains(SceneKey.Bouncer) -> null + val isOnLockscreen = renderedScenes.contains(SceneKey.Lockscreen) + val isOnBouncer = renderedScenes.contains(SceneKey.Bouncer) + if (!isUnlocked) { + return@mapNotNull if (isOnLockscreen || isOnBouncer) { + // Already on lockscreen or bouncer, no need to change scenes. + null + } else { + // The device locked while on a scene that's not Lockscreen or Bouncer, + // go to Lockscreen. + SceneKey.Lockscreen to + "device locked in non-Lockscreen and non-Bouncer scene" + } + } - // We got locked while on a scene that's not Lockscreen or Bouncer, - // go to Lockscreen. - else -> - SceneKey.Lockscreen to - "device locked in non-Lockscreen and non-Bouncer scene" - } + val isBypassEnabled = deviceEntryInteractor.isBypassEnabled.value + val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value + when { + isOnBouncer -> + // When the device becomes unlocked in Bouncer, go to Gone. + SceneKey.Gone to "device was unlocked in Bouncer scene" + isOnLockscreen -> + // The lockscreen should be dismissed automatically in 2 scenarios: + // 1. When face auth bypass is enabled and authentication happens while + // the user is on the lockscreen. + // 2. Whenever the user authenticates using an active authentication + // mechanism like fingerprint auth. Since canSwipeToEnter is true + // when the user is passively authenticated, the false value here + // when the unlock state changes indicates this is an active + // authentication attempt. + if (isBypassEnabled || !canSwipeToEnter) + SceneKey.Gone to + "device has been unlocked on lockscreen with either " + + "bypass enabled or using an active authentication mechanism" + else null + // Not on lockscreen or bouncer, so remain in the current scene. else -> null } } @@ -186,24 +197,15 @@ constructor( loggingReason = "device is starting to sleep", ) } else { - val authMethod = authenticationInteractor.getAuthenticationMethod() + val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value val isUnlocked = deviceEntryInteractor.isUnlocked.value - when { - authMethod == AuthenticationMethodModel.None -> { - switchToScene( - targetSceneKey = SceneKey.Gone, - loggingReason = - "device is starting to wake up while auth method is" + " none", - ) - } - authMethod.isSecure && isUnlocked -> { - switchToScene( - targetSceneKey = SceneKey.Gone, - loggingReason = - "device is starting to wake up while unlocked with a" + - " secure auth method", - ) - } + if (isUnlocked && !canSwipeToEnter) { + switchToScene( + targetSceneKey = SceneKey.Gone, + loggingReason = + "device is waking up while unlocked without the ability" + + " to swipe up on lockscreen to enter.", + ) } } } 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 e40d2b7fd659..d14ef35027a3 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 @@ -17,8 +17,8 @@ package com.android.systemui.scene.shared.flag import androidx.annotation.VisibleForTesting -import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.Flags as AConfigFlags +import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.Flags.sceneContainer import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton @@ -28,6 +28,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.flags.ReleasedFlag import com.android.systemui.flags.ResourceBooleanFlag import com.android.systemui.flags.UnreleasedFlag +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import dagger.Module import dagger.Provides import dagger.assisted.Assisted @@ -58,8 +59,6 @@ constructor( @VisibleForTesting val classicFlagTokens: List<Flag<Boolean>> = listOf( - Flags.MIGRATE_NSSL, - Flags.MIGRATE_KEYGUARD_STATUS_VIEW, Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, ) } @@ -75,6 +74,10 @@ constructor( flagName = AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, flagValue = keyguardBottomAreaRefactor(), ), + AconfigFlagMustBeEnabled( + flagName = KeyguardShadeMigrationNssl.FLAG_NAME, + flagValue = KeyguardShadeMigrationNssl.isEnabled, + ), ) + classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } + listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled()) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 285cb5a93b33..5a007fc5c3df 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -137,6 +137,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInterac import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder; @@ -1265,7 +1266,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewController.onDestroy(); } - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { // Need a shared controller until mKeyguardStatusViewController can be removed from // here, due to important state being set in that controller. Rebind in order to pick // up config changes @@ -1318,7 +1319,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Reset any left over overscroll state. It is a rare corner case but can happen. mQsController.setOverScrollAmount(0); mScrimController.setNotificationsOverScrollAmount(0); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mNotificationStackScrollLayoutController.setOverExpansion(0); mNotificationStackScrollLayoutController.setOverScrollAmount(0); } @@ -1339,7 +1340,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } updateClockAppearance(); mQsController.updateQsState(); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mNotificationStackScrollLayoutController.updateFooter(); } } @@ -1371,7 +1372,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump void reInflateViews() { debugLog("reInflateViews"); // Re-inflate the status view group. - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { KeyguardStatusView keyguardStatusView = mNotificationContainerParent.findViewById(R.id.keyguard_status_view); int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView); @@ -1483,16 +1484,16 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateMaxDisplayedNotifications(boolean recompute) { + if (KeyguardShadeMigrationNssl.isEnabled()) { + return; + } + if (recompute) { setMaxDisplayedNotifications(Math.max(computeMaxKeyguardNotifications(), 1)); } else { if (SPEW_LOGCAT) Log.d(TAG, "Skipping computeMaxKeyguardNotifications() by request"); } - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { - return; - } - if (isKeyguardShowing() && !mKeyguardBypassController.getBypassEnabled()) { mNotificationStackScrollLayoutController.setMaxDisplayedNotifications( mMaxAllowedKeyguardNotifications); @@ -1640,7 +1641,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard), mKeyguardStatusViewController.isClockTopAligned()); mClockPositionAlgorithm.run(mClockPositionResult); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.setLockscreenClockY( mClockPositionAlgorithm.getExpandedPreferredClockY()); } @@ -1654,7 +1655,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange; - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.updatePosition( mClockPositionResult.clockX, mClockPositionResult.clockY, mClockPositionResult.clockScale, animateClock); @@ -1727,7 +1728,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void updateKeyguardStatusViewAlignment(boolean animate) { boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); ConstraintLayout layout; - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { layout = mKeyguardViewConfigurator.getKeyguardRootView(); } else { layout = mNotificationContainerParent; @@ -1902,7 +1903,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha; mKeyguardStatusViewController.setAlpha(alpha); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { // TODO (b/296373478) This is for split shade media movement. } else { mKeyguardStatusViewController @@ -2482,7 +2483,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump void requestScrollerTopPaddingUpdate(boolean animate) { float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing, getKeyguardNotificationStaticPadding(), mExpandedFraction); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { mSharedNotificationContainerInteractor.setTopPosition(padding); } else { mNotificationStackScrollLayoutController.updateTopPadding(padding, animate); @@ -2949,7 +2950,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void onScreenTurningOn() { - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.dozeTimeTick(); } } @@ -3201,7 +3202,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump public void dozeTimeTick() { mLockIconViewController.dozeTimeTick(); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.dozeTimeTick(); } if (mInterpolatedDarkAmount > 0) { @@ -4427,7 +4428,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump && statusBarState == KEYGUARD) { // This means we're doing the screen off animation - position the keyguard status // view where it'll be on AOD, so we can animate it in. - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.updatePosition( mClockPositionResult.clockX, mClockPositionResult.clockYFullyDozing, @@ -4547,7 +4548,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump setDozing(true /* dozing */, false /* animate */); mStatusBarStateController.setUpcomingState(KEYGUARD); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { mStatusBarStateController.setState(KEYGUARD); } else { mStatusBarStateListener.onStateChanged(KEYGUARD); @@ -4608,7 +4609,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth()); // Update Clock Pivot (used by anti-burnin transformations) - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight()); } @@ -4718,7 +4719,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private Consumer<Float> setTransitionY( NotificationStackScrollLayoutController stackScroller) { return (Float translationY) -> { - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.setTranslationY(translationY, /* excludeMedia= */false); stackScroller.setTranslationY(translationY); @@ -4760,7 +4761,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL) && !mUseExternalTouch) { + if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) { return false; } @@ -4926,7 +4927,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump */ @Override public boolean onTouchEvent(MotionEvent event) { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL) && !mUseExternalTouch) { + if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index a2ca49d9ba57..d0f2784a98b3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -53,6 +53,7 @@ import com.android.systemui.flags.Flags; import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; @@ -457,7 +458,7 @@ public class NotificationShadeWindowViewController implements Dumpable { && !bouncerShowing && !mStatusBarStateController.isDozing()) { if (mDragDownHelper.isDragDownEnabled()) { - if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { // When on lockscreen, if the touch originates at the top of the screen // go directly to QS and not the shade if (mQuickSettingsController.shouldQuickSettingsIntercept( @@ -469,7 +470,7 @@ public class NotificationShadeWindowViewController implements Dumpable { // This handles drag down over lockscreen boolean result = mDragDownHelper.onInterceptTouchEvent(ev); - if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { if (result) { mLastInterceptWasDragDownHelper = true; if (ev.getAction() == MotionEvent.ACTION_DOWN) { @@ -501,7 +502,7 @@ public class NotificationShadeWindowViewController implements Dumpable { MotionEvent cancellation = MotionEvent.obtain(ev); cancellation.setAction(MotionEvent.ACTION_CANCEL); mStackScrollLayout.onInterceptTouchEvent(cancellation); - if (!mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mNotificationPanelViewController.handleExternalInterceptTouch(cancellation); } cancellation.recycle(); @@ -516,7 +517,7 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mStatusBarKeyguardViewManager.onTouch(ev)) { return true; } - if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) { // we still want to finish our drag down gesture when locking the screen handled |= mDragDownHelper.onTouchEvent(ev) || handled; @@ -602,7 +603,7 @@ public class NotificationShadeWindowViewController implements Dumpable { } private boolean didNotificationPanelInterceptEvent(MotionEvent ev) { - if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need // to also ask NotificationPanelViewController directly, in order to process swipe up // events originating from notifications diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 866cfb443b0a..9c8a286b2918 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -32,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.fragments.FragmentService +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.plugins.qs.QS @@ -129,7 +130,6 @@ class NotificationsQSContainerController @Inject constructor( isGestureNavigation = QuickStepContract.isGesturalMode(currentMode) mView.setStackScroller(notificationStackScrollLayoutController.getView()) - mView.setMigratingNSSL(featureFlags.isEnabled(Flags.MIGRATE_NSSL)) if (featureFlags.isEnabled(Flags.QS_CONTAINER_GRAPH_OPTIMIZER)){ mView.enableGraphOptimization() } @@ -283,7 +283,7 @@ class NotificationsQSContainerController @Inject constructor( } private fun setNotificationsConstraints(constraintSet: ConstraintSet) { - if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled) { return } val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java index af44c4e36315..de3d16a57a1f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java @@ -32,6 +32,7 @@ import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.res.R; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.plugins.qs.QS; @@ -59,7 +60,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout private QS mQs; private View mQSContainer; private int mLastQSPaddingBottom; - private boolean mIsMigratingNSSL; /** * These are used to compute the bounding box containing the shade and the notification scrim, @@ -180,10 +180,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout super.dispatchDraw(canvas); } - void setMigratingNSSL(boolean isMigrating) { - mIsMigratingNSSL = isMigrating; - } - void enableGraphOptimization() { setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH); } @@ -196,7 +192,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - if (mIsMigratingNSSL) { + if (KeyguardShadeMigrationNssl.isEnabled()) { return super.drawChild(canvas, child, drawingTime); } int layoutIndex = mLayoutDrawingOrder.indexOf(child); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index d73fa1460bd4..5a4d8764759e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -68,10 +68,9 @@ import com.android.systemui.Dumpable; import com.android.systemui.classifier.Classifier; 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.fragments.FragmentHostManager; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; @@ -155,7 +154,6 @@ public class QuickSettingsController implements Dumpable { private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; private final CastController mCastController; private final SplitShadeStateController mSplitShadeStateController; - private final FeatureFlags mFeatureFlags; private final InteractionJankMonitor mInteractionJankMonitor; private final ShadeRepository mShadeRepository; private final ShadeInteractor mShadeInteractor; @@ -333,7 +331,6 @@ public class QuickSettingsController implements Dumpable { AccessibilityManager accessibilityManager, LockscreenGestureLogger lockscreenGestureLogger, MetricsLogger metricsLogger, - FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, ShadeLogger shadeLog, DumpManager dumpManager, @@ -384,7 +381,6 @@ public class QuickSettingsController implements Dumpable { mShadeLog = shadeLog; mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; mCastController = castController; - mFeatureFlags = featureFlags; mInteractionJankMonitor = interactionJankMonitor; mShadeRepository = shadeRepository; mShadeInteractor = shadeInteractor; @@ -1776,7 +1772,7 @@ public class QuickSettingsController implements Dumpable { // Dragging down on the lockscreen statusbar should prohibit other interactions // immediately, otherwise we'll wait on the touchslop. This is to allow // dragging down to expanded quick settings directly on the lockscreen. - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mPanelView.getParent().requestDisallowInterceptTouchEvent(true); } } @@ -1821,7 +1817,7 @@ public class QuickSettingsController implements Dumpable { && Math.abs(h) > Math.abs(x - mInitialTouchX) && shouldQuickSettingsIntercept( mInitialTouchX, mInitialTouchY, h)) { - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mPanelView.getParent().requestDisallowInterceptTouchEvent(true); } mShadeLog.onQsInterceptMoveQsTrackingEnabled(h); diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index e2e4556f59f7..8bab6696e2d3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -65,6 +65,10 @@ interface ShadeRepository { */ @Deprecated("Use ShadeInteractor instead") val legacyShadeTracking: StateFlow<Boolean> + /** Specifically tracks the user expanding the shade on the lockscreen only */ + @Deprecated("Use ShadeInteractor.isUserInteractingWithShade instead") + val legacyLockscreenShadeTracking: MutableStateFlow<Boolean> + /** * QuickSettingsController.mTracking as a flow. "Tracking" means that the user is moving quick * settings up or down with a pointer. Going forward, this concept will be replaced by checks @@ -106,6 +110,9 @@ interface ShadeRepository { /** Sets whether the user is moving the shade with a pointer */ fun setLegacyShadeTracking(tracking: Boolean) + /** Sets whether the user is moving the shade with a pointer, on lockscreen only */ + fun setLegacyLockscreenShadeTracking(tracking: Boolean) + /** Amount shade has expanded with regard to the UDFPS location */ val udfpsTransitionToFullShadeProgress: StateFlow<Float> @@ -177,6 +184,8 @@ constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepos @Deprecated("Use ShadeInteractor instead") override val legacyShadeTracking: StateFlow<Boolean> = _legacyShadeTracking.asStateFlow() + override val legacyLockscreenShadeTracking = MutableStateFlow(false) + private val _legacyQsTracking = MutableStateFlow(false) @Deprecated("Use ShadeInteractor instead") override val legacyQsTracking: StateFlow<Boolean> = _legacyQsTracking.asStateFlow() @@ -212,6 +221,11 @@ constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepos _legacyShadeTracking.value = tracking } + @Deprecated("Should only be called by NPVC and tests") + override fun setLegacyLockscreenShadeTracking(tracking: Boolean) { + legacyLockscreenShadeTracking.value = tracking + } + override fun setQsExpansion(qsExpansion: Float) { _qsExpansion.value = qsExpansion } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index b2ffeb3f2925..d687ef64aec4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -72,7 +72,7 @@ constructor( userSetupRepository: UserSetupRepository, userSwitcherInteractor: UserSwitcherInteractor, sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, - repository: ShadeRepository, + private val repository: ShadeRepository, ) { /** Emits true if the shade is currently allowed and false otherwise. */ val isShadeEnabled: StateFlow<Boolean> = @@ -185,7 +185,15 @@ constructor( if (sceneContainerFlags.isEnabled()) { sceneBasedInteracting(sceneInteractorProvider.get(), SceneKey.Shade) } else { - userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion) + combine( + userInteractingFlow( + repository.legacyShadeTracking, + repository.legacyShadeExpansion + ), + repository.legacyLockscreenShadeTracking + ) { legacyShadeTracking, legacyLockscreenShadeTracking -> + legacyShadeTracking || legacyLockscreenShadeTracking + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 9c5a20189dd2..20b9edee2d70 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade.ui.viewmodel -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor @@ -34,8 +33,7 @@ class ShadeSceneViewModel @Inject constructor( @Application private val applicationScope: CoroutineScope, - deviceEntryInteractor: DeviceEntryInteractor, - private val bouncerInteractor: BouncerInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, val shadeHeaderViewModel: ShadeHeaderViewModel, ) { /** The key of the scene we should switch to when swiping up. */ @@ -60,9 +58,7 @@ constructor( ) /** Notifies that some content in the shade was clicked. */ - fun onContentClicked() { - bouncerInteractor.showOrUnlockDevice() - } + fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry() private fun upDestinationSceneKey( isUnlocked: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt index ab0d6e3a6382..922560f16dce 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt @@ -17,11 +17,10 @@ package com.android.systemui.smartspace.config import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.plugins.BcSmartspaceConfigPlugin class BcSmartspaceConfigProvider(private val featureFlags: FeatureFlags) : BcSmartspaceConfigPlugin { override val isDefaultDateWeatherDisabled: Boolean - get() = featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) + get() = true } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index bf722af80934..2e3f3f894687 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -340,6 +340,7 @@ constructor( ) nsslController.resetScrollPosition() nsslController.resetCheckSnoozeLeavebehind() + shadeRepository.setLegacyLockscreenShadeTracking(false) setDragDownAmountAnimated(0f) } @@ -366,6 +367,7 @@ constructor( cancel() } } + shadeRepository.setLegacyLockscreenShadeTracking(true) } /** Do we need a falsing check currently? */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 3f080c247924..0e83c78edb1d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -680,7 +680,7 @@ public class NotificationLockscreenUserManagerImpl implements } NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key); if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { - return entry != null + return entry != null && entry.getRanking().getChannel() != null && entry.getRanking().getChannel().getLockscreenVisibility() == Notification.VISIBILITY_PRIVATE; } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 2cd55609a749..ef87406036b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -269,8 +269,7 @@ constructor( fun isDateWeatherDecoupled(): Boolean { execution.assertIsMainThread() - return featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) && - datePlugin != null && weatherPlugin != null + return datePlugin != null && weatherPlugin != null } fun isWeatherEnabled(): Boolean { @@ -501,8 +500,8 @@ constructor( } private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean { - if (isDateWeatherDecoupled()) { - return t.featureType != SmartspaceTarget.FEATURE_WEATHER + if (isDateWeatherDecoupled() && t.featureType == SmartspaceTarget.FEATURE_WEATHER) { + return false } if (!showNotifications) { return t.featureType == SmartspaceTarget.FEATURE_WEATHER diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index 2ea7f61aee4c..f8bc0ee287e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -23,27 +23,37 @@ import androidx.annotation.ColorInt import androidx.collection.ArrayMap import androidx.lifecycle.lifecycleScope import com.android.internal.policy.SystemBarUtils +import com.android.internal.statusbar.StatusBarIcon import com.android.internal.util.ContrastColorUtil +import com.android.systemui.CoreStartable import com.android.systemui.common.ui.ConfigurationState +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.collection.NotifCollection +import com.android.systemui.statusbar.notification.icon.IconPack import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.onConfigChanged -import com.android.systemui.util.children +import com.android.systemui.util.asIndenting import com.android.systemui.util.kotlin.mapValuesNotNullTo -import com.android.systemui.util.kotlin.sample import com.android.systemui.util.kotlin.stateFlow +import com.android.systemui.util.printCollection import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.stopAnimating import com.android.systemui.util.ui.value +import dagger.Binds +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job @@ -62,11 +72,18 @@ object NotificationIconContainerViewBinder { viewModel: NotificationIconContainerShelfViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, + failureTracker: StatusBarIconViewBindingFailureTracker, viewStore: ShelfNotificationIconViewStore, ): DisposableHandle { return view.repeatWhenAttached { lifecycleScope.launch { - viewModel.icons.bindIcons(view, configuration, configurationController, viewStore) + viewModel.icons.bindIcons( + view, + configuration, + configurationController, + notifyBindingFailures = { failureTracker.shelfFailures = it }, + viewStore, + ) } } } @@ -77,18 +94,20 @@ object NotificationIconContainerViewBinder { viewModel: NotificationIconContainerStatusBarViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, + failureTracker: StatusBarIconViewBindingFailureTracker, viewStore: StatusBarNotificationIconViewStore, ): DisposableHandle { val contrastColorUtil = ContrastColorUtil.getInstance(view.context) return view.repeatWhenAttached { lifecycleScope.run { launch { - val iconColors = + val iconColors: Flow<NotificationIconColors> = viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) } viewModel.icons.bindIcons( view, configuration, configurationController, + notifyBindingFailures = { failureTracker.statusBarFailures = it }, viewStore, ) { _, sbiv -> StatusBarIconViewBinder.bindIconColors( @@ -110,6 +129,7 @@ object NotificationIconContainerViewBinder { viewModel: NotificationIconContainerAlwaysOnDisplayViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, + failureTracker: StatusBarIconViewBindingFailureTracker, viewStore: IconViewStore, ): DisposableHandle { return view.repeatWhenAttached { @@ -119,6 +139,7 @@ object NotificationIconContainerViewBinder { view, configuration, configurationController, + notifyBindingFailures = { failureTracker.aodFailures = it }, viewStore, ) { _, sbiv -> viewModel.bindAodStatusBarIconView(sbiv, configuration) @@ -176,7 +197,7 @@ object NotificationIconContainerViewBinder { } /** - * Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s [children]. + * Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s children. * * [bindIcon] will be invoked to bind a child [StatusBarIconView] to an icon associated with the * given `iconKey`. The parent [Job] of this coroutine will be cancelled automatically when the @@ -186,6 +207,7 @@ object NotificationIconContainerViewBinder { view: NotificationIconContainer, configuration: ConfigurationState, configurationController: ConfigurationController, + notifyBindingFailures: (Collection<String>) -> Unit, viewStore: IconViewStore, bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit = { _, _ -> }, ): Unit = coroutineScope { @@ -208,57 +230,59 @@ object NotificationIconContainerViewBinder { FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight) } - launch { - layoutParams.collect { params: FrameLayout.LayoutParams -> - for (child in view.children) { - child.layoutParams = params - } - } - } - - val iconBindings = mutableMapOf<String, Job>() + val failedBindings = mutableSetOf<String>() + val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>() var prevIcons = NotificationIconsViewData() - sample(layoutParams, ::Pair).collect { - (iconsData: NotificationIconsViewData, layoutParams: FrameLayout.LayoutParams), - -> + collect { iconsData: NotificationIconsViewData -> val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons) prevIcons = iconsData - val replacingIcons = - iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, v) -> - viewStore.iconView(v.notifKey).statusBarIcon + val replacingIcons: ArrayMap<String, StatusBarIcon> = + iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, info) -> + boundViewsByNotifKey[info.notifKey]?.first?.statusBarIcon } view.setReplacingIcons(replacingIcons) - val childrenByNotifKey: Map<String, StatusBarIconView> = - view.children.filterIsInstance<StatusBarIconView>().associateByTo(ArrayMap()) { - it.notification.key - } + for (notifKey in iconsDiff.removed) { + failedBindings.remove(notifKey) + val (child, job) = boundViewsByNotifKey.remove(notifKey) ?: continue + view.removeView(child) + job.cancel() + } - iconsDiff.removed - .mapNotNull { key -> childrenByNotifKey[key]?.let { key to it } } - .forEach { (key, child) -> - view.removeView(child) - iconBindings.remove(key)?.cancel() + val toAdd: Sequence<String> = + iconsDiff.added.asSequence().map { it.notifKey } + failedBindings + for ((idx, notifKey) in toAdd.withIndex()) { + val sbiv = viewStore.iconView(notifKey) + if (sbiv == null) { + failedBindings.add(notifKey) + continue } - - val toAdd = iconsDiff.added.map { it.notifKey to viewStore.iconView(it.notifKey) } - for ((i, keyAndView) in toAdd.withIndex()) { - val (key, sbiv) = keyAndView - // The view might still be transiently added if it was just removed - // and added again + // The view might still be transiently added if it was just removed and added again view.removeTransientView(sbiv) - view.addView(sbiv, i, layoutParams) - iconBindings.remove(key)?.cancel() - iconBindings[key] = launch { bindIcon(key, sbiv) } + view.addView(sbiv, idx) + boundViewsByNotifKey.remove(notifKey)?.second?.cancel() + boundViewsByNotifKey[notifKey] = + Pair( + sbiv, + launch { + launch { layoutParams.collect { sbiv.layoutParams = it } } + bindIcon(notifKey, sbiv) + }, + ) } + notifyBindingFailures(failedBindings) + view.setChangingViewPositions(true) + // Re-sort notification icons + val expectedChildren = + iconsData.visibleKeys.mapNotNull { boundViewsByNotifKey[it.notifKey]?.first } val childCount = view.childCount for (i in 0 until childCount) { val actual = view.getChildAt(i) - val expected = viewStore.iconView(iconsData.visibleKeys[i].notifKey) + val expected = expectedChildren[i] if (actual === expected) { continue } @@ -273,47 +297,30 @@ object NotificationIconContainerViewBinder { /** External storage for [StatusBarIconView] instances. */ fun interface IconViewStore { - fun iconView(key: String): StatusBarIconView + fun iconView(key: String): StatusBarIconView? } @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE } /** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */ -class ShelfNotificationIconViewStore -@Inject -constructor( - private val notifCollection: NotifCollection, -) : IconViewStore { - override fun iconView(key: String): StatusBarIconView { - val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key") - return entry.icons.shelfIcon ?: error("No shelf IconView found for key: $key") - } -} +class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon }) /** [IconViewStore] for the always-on display. */ class AlwaysOnDisplayNotificationIconViewStore @Inject -constructor( - private val notifCollection: NotifCollection, -) : IconViewStore { - override fun iconView(key: String): StatusBarIconView { - val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key") - return entry.icons.aodIcon ?: error("No AOD IconView found for key: $key") - } -} +constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon }) /** [IconViewStore] for the status bar. */ -class StatusBarNotificationIconViewStore -@Inject -constructor( - private val notifCollection: NotifCollection, -) : IconViewStore { - override fun iconView(key: String): StatusBarIconView { - val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key") - return entry.icons.statusBarIcon ?: error("No status bar IconView found for key: $key") +class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon }) + +private fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) = + IconViewStore { key -> + getEntry(key)?.icons?.let(block) } -} private val View.viewBounds: Rect get() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt new file mode 100644 index 000000000000..0c114a2ac55d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.android.systemui.statusbar.notification.icon.ui.viewbinder + +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor +import com.android.systemui.util.asIndenting +import com.android.systemui.util.printCollection +import dagger.Binds +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import java.io.PrintWriter +import javax.inject.Inject + +@SysUISingleton +class StatusBarIconViewBindingFailureTracker @Inject constructor() : CoreStartable { + + var aodFailures: Collection<String> = emptyList() + var statusBarFailures: Collection<String> = emptyList() + var shelfFailures: Collection<String> = emptyList() + + // TODO(b/310681665): Ideally we wouldn't need to implement CoreStartable at all, and could just + // @Binds @IntoSet the Dumpable. + override fun start() { + // no-op, we're just using this as a dumpable + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + if (!NotificationIconContainerRefactor.isEnabled) return + pw.asIndenting().run { + printCollection("AOD Icon binding failures:", aodFailures) + printCollection("Status Bar Icon binding failures:", statusBarFailures) + printCollection("Shelf Icon binding failures:", shelfFailures) + } + } + + @dagger.Module + interface Module { + @Binds + @IntoMap + @ClassKey(StatusBarIconViewBindingFailureTracker::class) + fun bindStartable(impl: StatusBarIconViewBindingFailureTracker): CoreStartable + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt index 82626acc4b04..c03a4a5f5aab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt @@ -31,6 +31,7 @@ import com.android.systemui.util.ui.toAnimatedValueFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map @@ -83,19 +84,18 @@ constructor( /** An Icon to show "isolated" in the IconContainer. */ val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> = headsUpIconInteractor.isolatedNotification + .combine(icons) { isolatedNotif, iconsViewData -> + isolatedNotif?.let { + iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif } + } + } .pairwise(initialValue = null) - .sample(combine(icons, shadeInteractor.shadeExpansion, ::Pair)) { - (prev, isolatedNotif), - (iconsViewData, shadeExpansion), - -> - val iconInfo = - isolatedNotif?.let { - iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif } - } + .distinctUntilChanged() + .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion -> val animate = when { - isolatedNotif == prev -> false - isolatedNotif == null || prev == null -> shadeExpansion == 0f + iconInfo?.notifKey == prev?.notifKey -> false + iconInfo == null || prev == null -> shadeExpansion == 0f else -> false } AnimatableEvent(iconInfo, animate) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt index 538be142b8f2..9ff416a02ee6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt @@ -43,9 +43,9 @@ import com.android.systemui.util.time.SystemClock class PeekDisabledSuppressor( private val globalSettings: GlobalSettings, private val headsUpManager: HeadsUpManager, - private val logger: NotificationInterruptLogger, + private val logger: VisualInterruptionDecisionLogger, @Main private val mainHandler: Handler, -) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek setting disabled") { +) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek disabled by global setting") { private var isEnabled = false override fun shouldSuppress(): Boolean = !isEnabled @@ -87,16 +87,13 @@ class PeekDisabledSuppressor( class PulseDisabledSuppressor( private val ambientDisplayConfiguration: AmbientDisplayConfiguration, private val userTracker: UserTracker, -) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse setting disabled") { +) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by user setting") { override fun shouldSuppress(): Boolean = !ambientDisplayConfiguration.pulseOnNotificationEnabled(userTracker.userId) } class PulseBatterySaverSuppressor(private val batteryController: BatteryController) : - VisualInterruptionCondition( - types = setOf(PULSE), - reason = "pulsing disabled by battery saver" - ) { + VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by battery saver") { override fun shouldSuppress() = batteryController.isAodPowerSave() } @@ -128,14 +125,14 @@ class PeekDndSuppressor() : } class PeekNotImportantSuppressor() : - VisualInterruptionFilter(types = setOf(PEEK), reason = "not important") { + VisualInterruptionFilter(types = setOf(PEEK), reason = "importance < HIGH") { override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_HIGH } class PeekDeviceNotInUseSuppressor( private val powerManager: PowerManager, private val statusBarStateController: StatusBarStateController -) : VisualInterruptionCondition(types = setOf(PEEK), reason = "not in use") { +) : VisualInterruptionCondition(types = setOf(PEEK), reason = "device not in use") { override fun shouldSuppress() = when { !powerManager.isScreenOn || statusBarStateController.isDreaming -> true @@ -144,7 +141,7 @@ class PeekDeviceNotInUseSuppressor( } class PeekOldWhenSuppressor(private val systemClock: SystemClock) : - VisualInterruptionFilter(types = setOf(PEEK), reason = "old when") { + VisualInterruptionFilter(types = setOf(PEEK), reason = "has old `when`") { private fun whenAge(entry: NotificationEntry) = systemClock.currentTimeMillis() - entry.sbn.notification.`when` @@ -165,21 +162,21 @@ class PeekOldWhenSuppressor(private val systemClock: SystemClock) : } class PulseEffectSuppressor() : - VisualInterruptionFilter(types = setOf(PULSE), reason = "ambient effect suppressed") { + VisualInterruptionFilter(types = setOf(PULSE), reason = "suppressed by DND") { override fun shouldSuppress(entry: NotificationEntry) = entry.shouldSuppressAmbient() } class PulseLockscreenVisibilityPrivateSuppressor() : VisualInterruptionFilter( types = setOf(PULSE), - reason = "notification hidden on lock screen by override" + reason = "hidden by lockscreen visibility override" ) { override fun shouldSuppress(entry: NotificationEntry) = entry.ranking.lockscreenVisibilityOverride == VISIBILITY_PRIVATE } class PulseLowImportanceSuppressor() : - VisualInterruptionFilter(types = setOf(PULSE), reason = "importance less than DEFAULT") { + VisualInterruptionFilter(types = setOf(PULSE), reason = "importance < DEFAULT") { override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_DEFAULT } @@ -198,12 +195,12 @@ class HunJustLaunchedFsiSuppressor() : } class BubbleNotAllowedSuppressor() : - VisualInterruptionFilter(types = setOf(BUBBLE), reason = "not allowed") { + VisualInterruptionFilter(types = setOf(BUBBLE), reason = "cannot bubble") { override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble() } class BubbleNoMetadataSuppressor() : - VisualInterruptionFilter(types = setOf(BUBBLE), reason = "no bubble metadata") { + VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") { private fun isValidMetadata(metadata: BubbleMetadata?) = metadata != null && (metadata.intent != null || metadata.shortcutId != null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt new file mode 100644 index 000000000000..b44a36724bdb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.interruption + +import android.app.NotificationManager.IMPORTANCE_HIGH +import android.os.PowerManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.StatusBarState.KEYGUARD +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_DEVICE_DREAMING +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_DEVICE_NOT_INTERACTIVE +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_DEVICE_NOT_PROVISIONED +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_OCCLUDED +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_KEYGUARD_SHOWING +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.FSI_LOCKED_SHADE +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_EXPECTED_TO_HUN +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NOT_IMPORTANT_ENOUGH +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NO_FULL_SCREEN_INTENT +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_NO_HUN_OR_KEYGUARD +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_PACKAGE_SUSPENDED +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SHOW_STICKY_HUN +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSED_BY_DND +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSED_ONLY_BY_DND +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA +import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR +import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.KeyguardStateController + +class FullScreenIntentDecisionProvider( + private val deviceProvisionedController: DeviceProvisionedController, + private val keyguardStateController: KeyguardStateController, + private val powerManager: PowerManager, + private val statusBarStateController: StatusBarStateController +) { + interface Decision { + val shouldFsi: Boolean + val wouldFsiWithoutDnd: Boolean + val logReason: String + val shouldLog: Boolean + val isWarning: Boolean + } + + private enum class DecisionImpl( + override val shouldFsi: Boolean, + override val logReason: String, + override val wouldFsiWithoutDnd: Boolean = shouldFsi, + val supersedesDnd: Boolean = false, + override val shouldLog: Boolean = true, + override val isWarning: Boolean = false + ) : Decision { + NO_FSI_NO_FULL_SCREEN_INTENT( + false, + "no full-screen intent", + supersedesDnd = true, + shouldLog = false + ), + NO_FSI_SHOW_STICKY_HUN(false, "full-screen intents are disabled", supersedesDnd = true), + NO_FSI_NOT_IMPORTANT_ENOUGH(false, "not important enough"), + NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR( + false, + "suppressive group alert behavior", + isWarning = true + ), + NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false, "suppressive bubble metadata", isWarning = true), + NO_FSI_PACKAGE_SUSPENDED(false, "package suspended"), + FSI_DEVICE_NOT_INTERACTIVE(true, "device is not interactive"), + FSI_DEVICE_DREAMING(true, "device is dreaming"), + FSI_KEYGUARD_SHOWING(true, "keyguard is showing"), + NO_FSI_EXPECTED_TO_HUN(false, "expected to heads-up instead"), + FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"), + FSI_LOCKED_SHADE(true, "locked shade"), + FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"), + NO_FSI_NO_HUN_OR_KEYGUARD(false, "no HUN or keyguard", isWarning = true), + NO_FSI_SUPPRESSED_BY_DND(false, "suppressed by DND", wouldFsiWithoutDnd = false), + NO_FSI_SUPPRESSED_ONLY_BY_DND(false, "suppressed only by DND", wouldFsiWithoutDnd = true) + } + + fun makeFullScreenIntentDecision(entry: NotificationEntry, couldHeadsUp: Boolean): Decision { + val reasonWithoutDnd = makeDecisionWithoutDnd(entry, couldHeadsUp) + + val suppressedWithoutDnd = !reasonWithoutDnd.shouldFsi + val suppressedByDnd = entry.shouldSuppressFullScreenIntent() + + val reasonWithDnd = + when { + reasonWithoutDnd.supersedesDnd -> reasonWithoutDnd + suppressedByDnd && !suppressedWithoutDnd -> NO_FSI_SUPPRESSED_ONLY_BY_DND + suppressedByDnd -> NO_FSI_SUPPRESSED_BY_DND + else -> reasonWithoutDnd + } + + return reasonWithDnd + } + + private fun makeDecisionWithoutDnd( + entry: NotificationEntry, + couldHeadsUp: Boolean + ): DecisionImpl { + val sbn = entry.sbn + val notification = sbn.notification!! + + if (notification.fullScreenIntent == null) { + return if (entry.isStickyAndNotDemoted) { + NO_FSI_SHOW_STICKY_HUN + } else { + NO_FSI_NO_FULL_SCREEN_INTENT + } + } + + if (entry.importance < IMPORTANCE_HIGH) { + return NO_FSI_NOT_IMPORTANT_ENOUGH + } + + if (sbn.isGroup && notification.suppressAlertingDueToGrouping()) { + return NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR + } + + val bubbleMetadata = notification.bubbleMetadata + if (bubbleMetadata != null && bubbleMetadata.isNotificationSuppressed) { + return NO_FSI_SUPPRESSIVE_BUBBLE_METADATA + } + + if (entry.ranking.isSuspended) { + return NO_FSI_PACKAGE_SUSPENDED + } + + if (!powerManager.isInteractive) { + return FSI_DEVICE_NOT_INTERACTIVE + } + + if (statusBarStateController.isDreaming) { + return FSI_DEVICE_DREAMING + } + + if (statusBarStateController.state == KEYGUARD) { + return FSI_KEYGUARD_SHOWING + } + + if (couldHeadsUp) { + return NO_FSI_EXPECTED_TO_HUN + } + + if (keyguardStateController.isShowing) { + return if (keyguardStateController.isOccluded) { + FSI_KEYGUARD_OCCLUDED + } else { + FSI_LOCKED_SHADE + } + } + + if (!deviceProvisionedController.isDeviceProvisioned) { + return FSI_DEVICE_NOT_PROVISIONED + } + + return NO_FSI_NO_HUN_OR_KEYGUARD + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt new file mode 100644 index 000000000000..1470b0331359 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.interruption + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.WARNING +import com.android.systemui.log.dagger.NotificationInterruptLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision +import com.android.systemui.statusbar.notification.logKey +import javax.inject.Inject + +class VisualInterruptionDecisionLogger +@Inject +constructor(@NotificationInterruptLog val buffer: LogBuffer) { + fun logHeadsUpFeatureChanged(isEnabled: Boolean) { + buffer.log( + TAG, + INFO, + { bool1 = isEnabled }, + { "HUN feature is now ${if (bool1) "enabled" else "disabled"}" } + ) + } + + fun logWillDismissAll() { + buffer.log(TAG, INFO, {}, { "dismissing all HUNs since feature was disabled" }) + } + + fun logDecision( + type: String, + entry: NotificationEntry, + decision: VisualInterruptionDecisionProvider.Decision + ) { + buffer.log( + TAG, + DEBUG, + { + str1 = type + bool1 = decision.shouldInterrupt + str2 = decision.logReason + str3 = entry.logKey + }, + { + val outcome = if (bool1) "allowed" else "suppressed" + "$str1 $outcome: $str2 (key=$str3)" + } + ) + } + + fun logFullScreenIntentDecision( + entry: NotificationEntry, + decision: FullScreenIntentDecision, + warning: Boolean + ) { + buffer.log( + TAG, + if (warning) WARNING else DEBUG, + { + bool1 = decision.shouldInterrupt + bool2 = decision.wouldInterruptWithoutDnd + str1 = decision.logReason + str2 = entry.logKey + }, + { + val outcome = + when { + bool1 -> "allowed" + bool2 -> "suppressed only by DND" + else -> "suppressed" + } + "FSI $outcome: $str1 (key=$str2)" + } + ) + } +} + +private const val TAG = "VisualInterruptionDecisionProvider" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt index 2730683a31c9..c0a1a32b1401 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt @@ -30,7 +30,9 @@ import com.android.systemui.statusbar.notification.interruption.VisualInterrupti import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -40,16 +42,77 @@ class VisualInterruptionDecisionProviderImpl constructor( private val ambientDisplayConfiguration: AmbientDisplayConfiguration, private val batteryController: BatteryController, + deviceProvisionedController: DeviceProvisionedController, private val globalSettings: GlobalSettings, private val headsUpManager: HeadsUpManager, private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider, - private val logger: NotificationInterruptLogger, + keyguardStateController: KeyguardStateController, + private val logger: VisualInterruptionDecisionLogger, @Main private val mainHandler: Handler, private val powerManager: PowerManager, private val statusBarStateController: StatusBarStateController, private val systemClock: SystemClock, private val userTracker: UserTracker, ) : VisualInterruptionDecisionProvider { + private class DecisionImpl( + override val shouldInterrupt: Boolean, + override val logReason: String + ) : Decision + + private data class LoggableDecision private constructor(val decision: DecisionImpl) { + companion object { + val unsuppressed = + LoggableDecision(DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")) + + fun suppressed(legacySuppressor: NotificationInterruptSuppressor, methodName: String) = + LoggableDecision( + DecisionImpl( + shouldInterrupt = false, + logReason = "${legacySuppressor.name}.$methodName" + ) + ) + + fun suppressed(suppressor: VisualInterruptionSuppressor) = + LoggableDecision( + DecisionImpl(shouldInterrupt = false, logReason = suppressor.reason) + ) + } + } + + private class FullScreenIntentDecisionImpl( + val entry: NotificationEntry, + private val fsiDecision: FullScreenIntentDecisionProvider.Decision + ) : FullScreenIntentDecision { + var hasBeenLogged = false + + override val shouldInterrupt + get() = fsiDecision.shouldFsi + + override val wouldInterruptWithoutDnd + get() = fsiDecision.wouldFsiWithoutDnd + + override val logReason + get() = fsiDecision.logReason + + val shouldLog + get() = fsiDecision.shouldLog + + val isWarning + get() = fsiDecision.isWarning + } + + private val fullScreenIntentDecisionProvider = + FullScreenIntentDecisionProvider( + deviceProvisionedController, + keyguardStateController, + powerManager, + statusBarStateController + ) + + private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>() + private val conditions = mutableListOf<VisualInterruptionCondition>() + private val filters = mutableListOf<VisualInterruptionFilter>() + private var started = false override fun start() { @@ -76,24 +139,6 @@ constructor( started = true } - private class DecisionImpl( - override val shouldInterrupt: Boolean, - override val logReason: String - ) : Decision - - private class FullScreenIntentDecisionImpl( - override val shouldInterrupt: Boolean, - override val wouldInterruptWithoutDnd: Boolean, - override val logReason: String, - val originalEntry: NotificationEntry, - ) : FullScreenIntentDecision { - var hasBeenLogged = false - } - - private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>() - private val conditions = mutableListOf<VisualInterruptionCondition>() - private val filters = mutableListOf<VisualInterruptionFilter>() - override fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor) { legacySuppressors.add(suppressor) } @@ -124,155 +169,113 @@ constructor( override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision { check(started) - return makeHeadsUpDecision(entry) + + return if (statusBarStateController.isDozing) { + makeLoggablePulseDecision(entry) + } else { + makeLoggablePeekDecision(entry) + } + .decision } override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision { check(started) - return makeHeadsUpDecision(entry).also { logHeadsUpDecision(entry, it) } - } - override fun makeUnloggedFullScreenIntentDecision( - entry: NotificationEntry - ): FullScreenIntentDecision { - check(started) - return makeFullScreenDecision(entry) + return if (statusBarStateController.isDozing) { + makeLoggablePulseDecision(entry).also { logDecision(PULSE, entry, it) } + } else { + makeLoggablePeekDecision(entry).also { logDecision(PEEK, entry, it) } + } + .decision } - override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) { - check(started) - val decisionImpl = - decision as? FullScreenIntentDecisionImpl - ?: run { - Log.wtf(TAG, "Wrong subclass of FullScreenIntentDecision: $decision") - return - } - if (decision.hasBeenLogged) { - Log.wtf(TAG, "Already logged decision: $decision") - return - } - logFullScreenIntentDecision(decisionImpl) - decision.hasBeenLogged = true - } + private fun makeLoggablePeekDecision(entry: NotificationEntry): LoggableDecision = + checkConditions(PEEK) + ?: checkFilters(PEEK, entry) ?: checkSuppressInterruptions(entry) + ?: checkSuppressAwakeInterruptions(entry) ?: checkSuppressAwakeHeadsUp(entry) + ?: LoggableDecision.unsuppressed + + private fun makeLoggablePulseDecision(entry: NotificationEntry): LoggableDecision = + checkConditions(PULSE) + ?: checkFilters(PULSE, entry) ?: checkSuppressInterruptions(entry) + ?: LoggableDecision.unsuppressed override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision { check(started) - return makeBubbleDecision(entry).also { logBubbleDecision(entry, it) } - } - private fun makeHeadsUpDecision(entry: NotificationEntry): DecisionImpl { - if (statusBarStateController.isDozing) { - return makePulseDecision(entry) - } else { - return makePeekDecision(entry) - } + return makeLoggableBubbleDecision(entry).also { logDecision(BUBBLE, entry, it) }.decision } - private fun makePeekDecision(entry: NotificationEntry): DecisionImpl { - checkConditions(PEEK)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkFilters(PEEK, entry)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressInterruptions" - ) - } - checkAwakeSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressAwakeInterruptions" - ) - } - checkAwakeHeadsUpSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressAwakeHeadsUpInterruptions" - ) - } - return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed") - } + private fun makeLoggableBubbleDecision(entry: NotificationEntry): LoggableDecision = + checkConditions(BUBBLE) + ?: checkFilters(BUBBLE, entry) ?: checkSuppressInterruptions(entry) + ?: checkSuppressAwakeInterruptions(entry) ?: LoggableDecision.unsuppressed - private fun makePulseDecision(entry: NotificationEntry): DecisionImpl { - checkConditions(PULSE)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkFilters(PULSE, entry)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressInterruptions" - ) - } - return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed") - } - - private fun makeBubbleDecision(entry: NotificationEntry): DecisionImpl { - checkConditions(BUBBLE)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkFilters(BUBBLE, entry)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressInterruptions" - ) - } - checkAwakeSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressAwakeInterruptions" - ) - } - return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed") + private fun logDecision( + type: VisualInterruptionType, + entry: NotificationEntry, + loggable: LoggableDecision + ) { + logger.logDecision(type.name, entry, loggable.decision) } - private fun makeFullScreenDecision(entry: NotificationEntry): FullScreenIntentDecisionImpl { - // Not yet implemented. - return FullScreenIntentDecisionImpl( - shouldInterrupt = true, - wouldInterruptWithoutDnd = true, - logReason = "FSI logic not yet implemented in VisualInterruptionDecisionProviderImpl", - originalEntry = entry - ) - } + override fun makeUnloggedFullScreenIntentDecision( + entry: NotificationEntry + ): FullScreenIntentDecision { + check(started) - private fun logHeadsUpDecision(entry: NotificationEntry, decision: DecisionImpl) { - // Not yet implemented. + val couldHeadsUp = makeUnloggedHeadsUpDecision(entry).shouldInterrupt + val fsiDecision = + fullScreenIntentDecisionProvider.makeFullScreenIntentDecision(entry, couldHeadsUp) + return FullScreenIntentDecisionImpl(entry, fsiDecision) } - private fun logBubbleDecision(entry: NotificationEntry, decision: DecisionImpl) { - // Not yet implemented. - } + override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) { + check(started) - private fun logFullScreenIntentDecision(decision: FullScreenIntentDecisionImpl) { - // Not yet implemented. - } + if (decision !is FullScreenIntentDecisionImpl) { + Log.wtf(TAG, "FSI decision $decision was not created by this class") + return + } - private fun checkSuppressors(entry: NotificationEntry) = - legacySuppressors.firstOrNull { it.suppressInterruptions(entry) } + if (decision.hasBeenLogged) { + Log.wtf(TAG, "FSI decision $decision has already been logged") + return + } - private fun checkAwakeSuppressors(entry: NotificationEntry) = - legacySuppressors.firstOrNull { it.suppressAwakeInterruptions(entry) } + decision.hasBeenLogged = true - private fun checkAwakeHeadsUpSuppressors(entry: NotificationEntry) = - legacySuppressors.firstOrNull { it.suppressAwakeHeadsUp(entry) } + if (!decision.shouldLog) { + return + } - private fun checkConditions(type: VisualInterruptionType): VisualInterruptionCondition? = - conditions.firstOrNull { it.types.contains(type) && it.shouldSuppress() } + logger.logFullScreenIntentDecision(decision.entry, decision, decision.isWarning) + } - private fun checkFilters( - type: VisualInterruptionType, - entry: NotificationEntry - ): VisualInterruptionFilter? = - filters.firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) } + private fun checkSuppressInterruptions(entry: NotificationEntry) = + legacySuppressors + .firstOrNull { it.suppressInterruptions(entry) } + ?.let { LoggableDecision.suppressed(it, "suppressInterruptions") } + + private fun checkSuppressAwakeInterruptions(entry: NotificationEntry) = + legacySuppressors + .firstOrNull { it.suppressAwakeInterruptions(entry) } + ?.let { LoggableDecision.suppressed(it, "suppressAwakeInterruptions") } + + private fun checkSuppressAwakeHeadsUp(entry: NotificationEntry) = + legacySuppressors + .firstOrNull { it.suppressAwakeHeadsUp(entry) } + ?.let { LoggableDecision.suppressed(it, "suppressAwakeHeadsUp") } + + private fun checkConditions(type: VisualInterruptionType) = + conditions + .firstOrNull { it.types.contains(type) && it.shouldSuppress() } + ?.let { LoggableDecision.suppressed(it) } + + private fun checkFilters(type: VisualInterruptionType, entry: NotificationEntry) = + filters + .firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) } + ?.let { LoggableDecision.suppressed(it) } } private const val TAG = "VisualInterruptionDecisionProviderImpl" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt index 7667e17cac14..5cdead407891 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -24,6 +24,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel @@ -40,6 +41,7 @@ object NotificationShelfViewBinder { configuration: ConfigurationState, configurationController: ConfigurationController, falsingManager: FalsingManager, + iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker, notificationIconAreaController: NotificationIconAreaController, shelfIconViewStore: ShelfNotificationIconViewStore, ) { @@ -51,6 +53,7 @@ object NotificationShelfViewBinder { viewModel.icons, configuration, configurationController, + iconViewBindingFailureTracker, shelfIconViewStore, ) } else { 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 a0ffba304027..14ec08f3545f 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 @@ -253,6 +253,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private NotificationLogger.OnChildLocationsChangedListener mListener; private OnOverscrollTopChangedListener mOverscrollTopChangedListener; private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; + private Runnable mOnHeightChangedRunnable; private OnEmptySpaceClickListener mOnEmptySpaceClickListener; private boolean mNeedsAnimation; private boolean mTopPaddingNeedsAnimation; @@ -1121,6 +1122,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mOnHeightChangedListener != null) { mOnHeightChangedListener.onHeightChanged(view, needsAnimation); } + + if (mOnHeightChangedRunnable != null) { + mOnHeightChangedRunnable.run(); + } } public boolean isPulseExpanding() { @@ -4252,6 +4257,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable this.mOnHeightChangedListener = onHeightChangedListener; } + void setOnHeightChangedRunnable(Runnable r) { + this.mOnHeightChangedRunnable = r; + } + void onChildAnimationFinished() { setAnimationRunning(false); requestChildrenUpdate(); 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 99b3a005ab0c..2cf0c262c528 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 @@ -20,6 +20,7 @@ import static android.service.notification.NotificationStats.DISMISSAL_SHADE; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; import static com.android.app.animation.Interpolators.STANDARD; + import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; @@ -34,7 +35,6 @@ import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.animation.ObjectAnimator; import android.content.res.Configuration; -import android.content.res.Resources; import android.graphics.Point; import android.os.Trace; import android.os.UserHandle; @@ -65,9 +65,7 @@ import com.android.systemui.Gefingerpoken; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; -import com.android.systemui.common.ui.ConfigurationState; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlagsClassic; import com.android.systemui.flags.Flags; @@ -94,7 +92,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.LaunchAnimationParameters; -import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -112,7 +109,6 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.dagger.SilentHeader; import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; -import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -122,11 +118,9 @@ 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.ui.viewbinder.NotificationListViewBinder; -import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -960,6 +954,13 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setOnHeightChangedListener(listener); } + /** + * Invoked in addition to {@see #setOnHeightChangedListener} + */ + public void setOnHeightChangedRunnable(Runnable r) { + mView.setOnHeightChangedRunnable(r); + } + public void setOverscrollTopChangedListener( OnOverscrollTopChangedListener listener) { mView.setOverscrollTopChangedListener(listener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt index 57cea5d3b31e..eb1c17aaca78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt @@ -18,13 +18,15 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import android.content.Context -import com.android.systemui.res.R import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.SplitShadeStateController import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map @@ -43,6 +45,10 @@ constructor( private val _topPosition = MutableStateFlow(0f) val topPosition = _topPosition.asStateFlow() + private val _notificationStackChanged = MutableSharedFlow<Unit>(extraBufferCapacity = 1) + /** An internal modification was made to notifications */ + val notificationStackChanged = _notificationStackChanged.asSharedFlow() + val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> = configurationRepository.onAnyConfigurationChange .onStart { emit(Unit) } @@ -72,6 +78,11 @@ constructor( _topPosition.value = top } + /** An internal modification was made to notifications */ + fun notificationStackChanged() { + _notificationStackChanged.tryEmit(Unit) + } + data class ConfigurationBasedDimensions( val useSplitShade: Boolean, val useLargeScreenHeader: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index 6cf56102d65f..a5b87f088578 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController @@ -45,6 +46,7 @@ constructor( private val configurationController: ConfigurationController, private val falsingManager: FalsingManager, private val iconAreaController: NotificationIconAreaController, + private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val shelfIconViewStore: ShelfNotificationIconViewStore, ) { @@ -68,6 +70,7 @@ constructor( configuration, configurationController, falsingManager, + iconViewBindingFailureTracker, iconAreaController, shelfIconViewStore, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index a1a0ccac3500..0ff1bec84d16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import kotlinx.coroutines.DisposableHandle @@ -33,6 +34,7 @@ object SharedNotificationContainerBinder { view: SharedNotificationContainer, viewModel: SharedNotificationContainerViewModel, controller: NotificationStackScrollLayoutController, + notificationStackSizeCalculator: NotificationStackSizeCalculator, ): DisposableHandle { val disposableHandle = view.repeatWhenAttached { @@ -54,9 +56,16 @@ object SharedNotificationContainerBinder { } launch { - viewModel.maxNotifications.collect { - controller.setMaxDisplayedNotifications(it) - } + viewModel + .getMaxNotifications { space -> + notificationStackSizeCalculator.computeMaxKeyguardNotifications( + controller.getView(), + space, + 0f, // Vertical space for shelf is already accounted for + controller.getShelfHeight().toFloat(), + ) + } + .collect { controller.setMaxDisplayedNotifications(it) } } launch { @@ -70,9 +79,12 @@ object SharedNotificationContainerBinder { } } + controller.setOnHeightChangedRunnable(Runnable { viewModel.notificationStackChanged() }) + return object : DisposableHandle { override fun dispose() { disposableHandle.dispose() + controller.setOnHeightChangedRunnable(null) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index b86b5dcc7939..d6b6f75b3186 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -22,28 +22,26 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController -import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart /** View-model for the shared notification container, used by both the shade and keyguard spaces */ class SharedNotificationContainerViewModel @Inject constructor( - interactor: SharedNotificationContainerInteractor, + private val interactor: SharedNotificationContainerInteractor, keyguardInteractor: KeyguardInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, - notificationStackSizeCalculator: NotificationStackSizeCalculator, - controller: NotificationStackScrollLayoutController, - shadeInteractor: ShadeInteractor, + private val shadeInteractor: ShadeInteractor, ) { private val statesForConstrainedNotifications = setOf( @@ -151,24 +149,46 @@ constructor( * When on keyguard, there is limited space to display notifications so calculate how many could * be shown. Otherwise, there is no limit since the vertical space will be scrollable. * - * TODO: b/296606746 - Need to rerun logic when notifs change + * When expanding or when the user is interacting with the shade, keep the count stable; do not + * emit a value. */ - val maxNotifications: Flow<Int> = - combine(isOnLockscreen, shadeInteractor.shadeExpansion, position) { - onLockscreen, - shadeExpansion, - positionInfo -> - if (onLockscreen && shadeExpansion < 1f) { - notificationStackSizeCalculator.computeMaxKeyguardNotifications( - controller.getView(), - positionInfo.bottom - positionInfo.top, - 0f, // Vertical space for shelf is already accounted for - controller.getShelfHeight().toFloat(), - ) - } else { - -1 // No limit + fun getMaxNotifications(calculateSpace: (Float) -> Int): Flow<Int> { + // When to limit notifications: on lockscreen with an unexpanded shade. Also, recalculate + // when the notification stack has changed internally + val limitedNotifications = + combineTransform( + isOnLockscreen, + position, + shadeInteractor.shadeExpansion, + interactor.notificationStackChanged.onStart { emit(Unit) }, + ) { isOnLockscreen, position, shadeExpansion, _ -> + if (isOnLockscreen && shadeExpansion == 0f) { + emit(calculateSpace(position.bottom - position.top)) + } } - } + + // When to show unlimited notifications: When the shade is fully expanded and the user is + // not actively dragging the shade + val unlimitedNotifications = + combineTransform( + shadeInteractor.shadeExpansion, + shadeInteractor.isUserInteracting, + ) { shadeExpansion, isUserInteracting -> + if (shadeExpansion == 1f && !isUserInteracting) { + emit(-1) + } + } + + return merge( + limitedNotifications, + unlimitedNotifications, + ) + .distinctUntilChanged() + } + + fun notificationStackChanged() { + interactor.notificationStackChanged() + } data class ConfigurationBasedDimensions( val marginStart: Int, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt new file mode 100644 index 000000000000..bcf732214d57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.ui.viewbinder + +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker +import dagger.Module + +@Module(includes = [StatusBarIconViewBindingFailureTracker.Module::class]) +object StatusBarNotificationViewBinderModule diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 2e5717de1a86..4586f58c5837 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -139,6 +139,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder; import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel; import com.android.systemui.navigationbar.NavigationBarController; @@ -1449,7 +1450,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return (v, event) -> { mAutoHideController.checkUserAutoHide(event); mRemoteInputManager.checkRemoteInputOutside(event); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mShadeController.onStatusBarTouch(event); } return getNotificationShadeWindowView().onTouchEvent(event); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index beeee1be401d..495b4e1e14cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -252,7 +252,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar } if (NotificationIconContainerRefactor.isEnabled()) { mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey( - newEntry == null ? null : newEntry.getKey()); + newEntry == null ? null : newEntry.getRepresentativeEntry().getKey()); } else { updateIsolatedIconLocation(false /* requireUpdate */); mNotificationIconAreaController.showIconIsolated(newEntry == null ? null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java index 3b3d8b6b2109..1f9952a8d4ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java @@ -40,7 +40,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -115,7 +115,6 @@ public class LegacyNotificationIconAreaControllerImpl implements private int mAodIconTint; private boolean mAodIconsVisible; private boolean mShowLowPriority = true; - private boolean mIsStatusViewMigrated = false; @VisibleForTesting final NotificationListener.NotificationSettingsListener mSettingsListener = @@ -159,7 +158,6 @@ public class LegacyNotificationIconAreaControllerImpl implements mStatusBarWindowController = statusBarWindowController; mScreenOffAnimationController = screenOffAnimationController; notificationListener.addNotificationSettingsListener(mSettingsListener); - mIsStatusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW); initializeNotificationAreaViews(context); reloadAodColor(); darkIconDispatcher.addDarkReceiver(this); @@ -551,7 +549,7 @@ public class LegacyNotificationIconAreaControllerImpl implements return; } if (mScreenOffAnimationController.shouldAnimateAodIcons()) { - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.setTranslationY(-mAodIconAppearTranslation); } mAodIcons.setAlpha(0); @@ -563,14 +561,14 @@ public class LegacyNotificationIconAreaControllerImpl implements .start(); } else { mAodIcons.setAlpha(1.0f); - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.setTranslationY(0); } } } private void animateInAodIconTranslation() { - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.animate() .setInterpolator(Interpolators.DECELERATE_QUINT) .translationY(0) @@ -673,7 +671,7 @@ public class LegacyNotificationIconAreaControllerImpl implements } } else { mAodIcons.setAlpha(1.0f); - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.setTranslationY(0); } mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index fb586eac5ab7..1a17e7c28410 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -20,6 +20,7 @@ import com.android.app.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.shade.ShadeViewController import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealScrim @@ -34,8 +35,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.app.tracing.TraceUtils import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags /** * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely @@ -68,7 +67,6 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val interactionJankMonitor: InteractionJankMonitor, private val powerManager: PowerManager, private val handler: Handler = Handler(), - private val featureFlags: FeatureFlags, ) : WakefulnessLifecycle.Observer, ScreenOffAnimation { private lateinit var centralSurfaces: CentralSurfaces private lateinit var shadeViewController: ShadeViewController @@ -288,7 +286,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( // up, with unpredictable consequences. if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) && shouldAnimateInKeyguard) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { // Tracking this state should no longer be relevant, as the isInteractive // check covers it aodUiAnimationPlaying = true diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 3921e69501d2..7adc08ca00c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -56,6 +56,7 @@ import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableSta import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; @@ -217,6 +218,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mWaitingForWindowStateChangeAfterCameraLaunch = false; mTransitionFromLockscreenToDreamStarted = false; }; + private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker; @Inject public CollapsedStatusBarFragment( @@ -235,6 +237,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue KeyguardStateController keyguardStateController, ShadeViewController shadeViewController, StatusBarStateController statusBarStateController, + StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker, CommandQueue commandQueue, CarrierConfigTracker carrierConfigTracker, CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, @@ -264,6 +267,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mKeyguardStateController = keyguardStateController; mShadeViewController = shadeViewController; mStatusBarStateController = statusBarStateController; + mIconViewBindingFailureTracker = iconViewBindingFailureTracker; mCommandQueue = commandQueue; mCarrierConfigTracker = carrierConfigTracker; mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger; @@ -471,6 +475,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBarIconsViewModel, mConfigurationState, mConfigurationController, + mIconViewBindingFailureTracker, mStatusBarIconViewStore); } else { mNotificationIconAreaInner = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index b614b6d0547d..78954dea27ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -35,13 +35,12 @@ import com.android.keyguard.KeyguardConstants; import com.android.keyguard.KeyguardVisibilityHelper; import com.android.keyguard.dagger.KeyguardUserSwitcherScope; import com.android.settingslib.drawable.CircleFramedDrawable; -import com.android.systemui.res.R; import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.user.UserSwitchDialogController; +import com.android.systemui.res.R; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -149,7 +148,6 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, UserSwitchDialogController userSwitchDialogController, - FeatureFlags featureFlags, UiEventLogger uiEventLogger) { super(view); if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController"); @@ -163,7 +161,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ false, - featureFlags, /* logBuffer= */ null); + /* logBuffer= */ null); mUserSwitchDialogController = userSwitchDialogController; mUiEventLogger = uiEventLogger; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index dfe26865f978..770f441799b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -39,7 +39,6 @@ import com.android.keyguard.KeyguardVisibilityHelper; import com.android.keyguard.dagger.KeyguardUserSwitcherScope; import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; @@ -161,7 +160,6 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - FeatureFlags featureFlags, DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController) { super(keyguardUserSwitcherView); @@ -177,7 +175,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ false, - featureFlags, /* logBuffer= */ null); + /* logBuffer= */ null); mBackground = new KeyguardUserSwitcherScrim(context); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt new file mode 100644 index 000000000000..4cbdd6fd6488 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.ui.binder + +import com.android.systemui.statusbar.notification.ui.viewbinder.StatusBarNotificationViewBinderModule +import dagger.Module + +@Module(includes = [StatusBarNotificationViewBinderModule::class]) object StatusBarViewBinderModule diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt index db4ab7edbcf1..5b9161593703 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt @@ -25,6 +25,16 @@ import com.android.app.animation.Interpolators * The fraction after which we start fading in when going from a gone widget to a visible one */ private const val GONE_FADE_FRACTION = 0.8f +/** + * The fraction after which we start fading in going from a gone widget to a visible one in guts + * animation. + */ +private const val GONE_FADE_GUTS_FRACTION = 0.286f +/** + * The fraction before which we fade out when going from a visible widget to a gone one in guts + * animation. + */ +private const val VISIBLE_FADE_GUTS_FRACTION = 0.355f /** * The amont we're scaling appearing views @@ -45,6 +55,7 @@ open class TransitionLayoutController { private var animationStartState: TransitionViewState? = null private var state = TransitionViewState() private var animator: ValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f) + private var isGutsAnimation = false private var currentHeight: Int = 0 private var currentWidth: Int = 0 var sizeChangedListener: ((Int, Int) -> Unit)? = null @@ -152,15 +163,6 @@ open class TransitionLayoutController { // this looks quite ugly val nowGone: Boolean if (widgetStart.gone) { - - // Only fade it in at the very end - alphaProgress = MathUtils.map(GONE_FADE_FRACTION, 1.0f, 0.0f, 1.0f, progress) - nowGone = progress < GONE_FADE_FRACTION - - // Scale it just a little, not all the way - val endScale = widgetEnd.scale - newScale = MathUtils.lerp(GONE_SCALE_AMOUNT * endScale, endScale, progress) - // don't clip widthProgress = 1.0f @@ -168,25 +170,52 @@ open class TransitionLayoutController { resultMeasureWidth = widgetEnd.measureWidth resultMeasureHeight = widgetEnd.measureHeight - // Let's make sure we're centering the view in the gone view instead of having - // the left at 0 - resultX = MathUtils.lerp(widgetStart.x - resultMeasureWidth / 2.0f, - widgetEnd.x, - progress) - resultY = MathUtils.lerp(widgetStart.y - resultMeasureHeight / 2.0f, - widgetEnd.y, - progress) + if (isGutsAnimation) { + // if animation is open/close guts, fade in starts early. + alphaProgress = MathUtils.map( + GONE_FADE_GUTS_FRACTION, + 1.0f, + 0.0f, + 1.0f, + progress + ) + nowGone = progress < GONE_FADE_GUTS_FRACTION + + // Do not change scale of widget. + newScale = 1.0f + + // We do not want any horizontal or vertical movement. + resultX = widgetStart.x + resultY = widgetStart.y + } else { + // Only fade it in at the very end + alphaProgress = MathUtils.map( + GONE_FADE_FRACTION, + 1.0f, + 0.0f, + 1.0f, + progress + ) + nowGone = progress < GONE_FADE_FRACTION + + // Scale it just a little, not all the way + val endScale = widgetEnd.scale + newScale = MathUtils.lerp(GONE_SCALE_AMOUNT * endScale, endScale, progress) + + // Let's make sure we're centering the view in the gone view instead of + // having the left at 0 + resultX = MathUtils.lerp( + widgetStart.x - resultMeasureWidth / 2.0f, + widgetEnd.x, + progress + ) + resultY = MathUtils.lerp( + widgetStart.y - resultMeasureHeight / 2.0f, + widgetEnd.y, + progress + ) + } } else { - - // Fadeout in the very beginning - alphaProgress = MathUtils.map(0.0f, 1.0f - GONE_FADE_FRACTION, 0.0f, 1.0f, - progress) - nowGone = progress > 1.0f - GONE_FADE_FRACTION - - // Scale it just a little, not all the way - val startScale = widgetStart.scale - newScale = MathUtils.lerp(startScale, startScale * GONE_SCALE_AMOUNT, progress) - // Don't clip widthProgress = 0.0f @@ -194,14 +223,54 @@ open class TransitionLayoutController { resultMeasureWidth = widgetStart.measureWidth resultMeasureHeight = widgetStart.measureHeight - // Let's make sure we're centering the view in the gone view instead of having - // the left at 0 - resultX = MathUtils.lerp(widgetStart.x, - widgetEnd.x - resultMeasureWidth / 2.0f, - progress) - resultY = MathUtils.lerp(widgetStart.y, - widgetEnd.y - resultMeasureHeight / 2.0f, - progress) + // Fadeout in the very beginning + if (isGutsAnimation) { + alphaProgress = MathUtils.map( + 0.0f, + VISIBLE_FADE_GUTS_FRACTION, + 0.0f, + 1.0f, + progress + ) + nowGone = progress > VISIBLE_FADE_GUTS_FRACTION + + // Do not change scale of widget during open/close guts animation. + newScale = 1.0f + + // We do not want any horizontal or vertical movement. + resultX = widgetEnd.x + resultY = widgetEnd.y + } else { + alphaProgress = MathUtils.map( + 0.0f, + 1.0f - GONE_FADE_FRACTION, + 0.0f, + 1.0f, + progress + ) + nowGone = progress > 1.0f - GONE_FADE_FRACTION + + // Scale it just a little, not all the way + val startScale = widgetStart.scale + newScale = MathUtils.lerp( + startScale, + startScale * GONE_SCALE_AMOUNT, + progress + ) + + // Let's make sure we're centering the view in the gone view instead of + // having the left at 0 + resultX = MathUtils.lerp( + widgetStart.x, + widgetEnd.x - resultMeasureWidth / 2.0f, + progress + ) + resultY = MathUtils.lerp( + widgetStart.y, + widgetEnd.y - resultMeasureHeight / 2.0f, + progress + ) + } } resultWidgetState.gone = nowGone } else { @@ -279,8 +348,10 @@ open class TransitionLayoutController { applyImmediately: Boolean, animate: Boolean, duration: Long = 0, - delay: Long = 0 + delay: Long = 0, + isGuts: Boolean, ) { + isGutsAnimation = isGuts val animated = animate && currentState.width != 0 this.state = state.copy() if (applyImmediately || transitionLayout == null) { @@ -291,6 +362,8 @@ open class TransitionLayoutController { animationStartState = currentState.copy() animator.duration = duration animator.startDelay = delay + animator.interpolator = + if (isGutsAnimation) Interpolators.LINEAR else Interpolators.FAST_OUT_SLOW_IN animator.start() } else if (!animator.isRunning) { applyStateToLayout(this.state) diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 84d2b3761fcc..404621d1fe81 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -34,7 +34,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL; import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder; -import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; @@ -83,7 +82,6 @@ import android.util.Slog; import android.util.SparseBooleanArray; import android.view.ContextThemeWrapper; import android.view.Gravity; -import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import android.view.View.AccessibilityDelegate; @@ -120,7 +118,6 @@ import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.Prefs; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialog; @@ -304,7 +301,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final DevicePostureController mDevicePostureController; private @DevicePostureController.DevicePostureInt int mDevicePosture; private int mOrientation; - private final FeatureFlags mFeatureFlags; private final Lazy<SecureSettings> mSecureSettings; private int mDialogTimeoutMillis; @@ -323,9 +319,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, DevicePostureController devicePostureController, Looper looper, DumpManager dumpManager, - FeatureFlags featureFlags, Lazy<SecureSettings> secureSettings) { - mFeatureFlags = featureFlags; mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); mHandler = new H(looper); @@ -1373,14 +1367,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private void provideTouchFeedbackH(int newRingerMode) { VibrationEffect effect = null; - int hapticConstant = HapticFeedbackConstants.NO_HAPTICS; switch (newRingerMode) { case RINGER_MODE_NORMAL: mController.scheduleTouchFeedback(); break; case RINGER_MODE_SILENT: effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - hapticConstant = HapticFeedbackConstants.TOGGLE_OFF; break; case RINGER_MODE_VIBRATE: // Feedback handled by onStateChange, for feedback both when user toggles @@ -1388,11 +1380,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, break; default: effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); - hapticConstant = HapticFeedbackConstants.TOGGLE_ON; } - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - mDialogView.performHapticFeedback(hapticConstant); - } else if (effect != null) { + if (effect != null) { mController.vibrate(effect); } } @@ -1820,22 +1809,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, && mState.ringerModeInternal != -1 && mState.ringerModeInternal != state.ringerModeInternal && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { - - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - if (mShowing) { - // The dialog view is responsible for triggering haptics in the oneway API - mDialogView.performHapticFeedback(HapticFeedbackConstants.TOGGLE_ON); - } - /* - TODO(b/290642122): If the dialog is not showing, we have the case where haptics is - enabled by dragging the volume slider of Settings to a value of 0. This must be - handled by view Slices in Settings whilst using the performHapticFeedback API. - */ - - } else { - // Old behavior only active if the oneway API is not used. - mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)); - } + mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)); } mState = state; mDynamic.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index e3b3c21d5d0d..53217d481599 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -22,7 +22,6 @@ import android.os.Looper; import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialog; @@ -65,7 +64,6 @@ public interface VolumeModule { CsdWarningDialog.Factory csdFactory, DevicePostureController devicePostureController, DumpManager dumpManager, - FeatureFlags featureFlags, Lazy<SecureSettings> secureSettings) { VolumeDialogImpl impl = new VolumeDialogImpl( context, @@ -82,7 +80,6 @@ public interface VolumeModule { devicePostureController, Looper.getMainLooper(), dumpManager, - featureFlags, secureSettings); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 4d8768f5e9e0..2e9b7e84344b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -127,7 +127,6 @@ class ClockEventControllerTest : SysuiTestCase() { withDeps.featureFlags.apply { set(Flags.REGION_SAMPLING, false) - set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false) set(Flags.FACE_AUTH_REFACTOR, false) } underTest = diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index a38ba00d898f..6a08eeac8108 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -21,7 +21,6 @@ import static android.view.View.INVISIBLE; import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.flags.Flags.MIGRATE_CLOCKS_TO_BLUEPRINT; -import static com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; @@ -60,6 +59,7 @@ import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationIconAreaController; @@ -181,7 +181,6 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { mFakeFeatureFlags = new FakeFeatureFlags(); mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false); mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); - mFakeFeatureFlags.set(MIGRATE_KEYGUARD_STATUS_VIEW, false); mFakeFeatureFlags.set(MIGRATE_CLOCKS_TO_BLUEPRINT, false); mController = new KeyguardClockSwitchController( mView, @@ -192,6 +191,7 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { mSmartspaceController, mock(ConfigurationController.class), mock(ScreenOffAnimationController.class), + mock(StatusBarIconViewBindingFailureTracker.class), mKeyguardUnlockAnimationController, mSecureSettings, mExecutor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 22c75d85d4a1..146715d26b7d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -30,7 +30,6 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -60,7 +59,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected ScreenOffAnimationController mScreenOffAnimationController; @Mock protected KeyguardLogger mKeyguardLogger; @Mock protected KeyguardStatusViewController mControllerMock; - @Mock protected FeatureFlags mFeatureFlags; @Mock protected InteractionJankMonitor mInteractionJankMonitor; @Mock protected ViewTreeObserver mViewTreeObserver; @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -91,7 +89,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { mDozeParameters, mScreenOffAnimationController, mKeyguardLogger, - mFeatureFlags, mInteractionJankMonitor, deps.getKeyguardInteractor(), mKeyguardTransitionInteractor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 776799ec054e..2b41e08065d1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -3436,7 +3436,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus); when(mUsbPort.supportsComplianceWarnings()).thenReturn(true); when(mUsbPortStatus.isConnected()).thenReturn(true); - when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1}); + when(mUsbPortStatus.getComplianceWarnings()) + .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY}); } private Context getSpyContext() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index 1d4f2cbe6b64..d2f45ae8685a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -42,8 +42,8 @@ import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthRippleController; -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.doze.util.BurnInHelperKt; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; @@ -78,7 +78,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { protected MockitoSession mStaticMockSession; protected final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this); - protected @Mock BouncerInteractor mBouncerInteractor; + protected @Mock DeviceEntryInteractor mDeviceEntryInteractor; protected @Mock LockIconView mLockIconView; protected @Mock AnimatedStateListDrawable mIconDrawable; protected @Mock Context mContext; @@ -176,7 +176,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mFeatureFlags, mPrimaryBouncerInteractor, mContext, - () -> mBouncerInteractor, + () -> mDeviceEntryInteractor, mSceneTestUtils.getSceneContainerFlags() ); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java index adcec10f9172..93a5393b41cf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java @@ -381,7 +381,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { // THEN show primary bouncer via keyguard view controller, not scene container verify(mKeyguardViewController).showPrimaryBouncer(anyBoolean()); - verify(mBouncerInteractor, never()).showOrUnlockDevice(any()); + verify(mDeviceEntryInteractor, never()).attemptDeviceEntry(); } @Test @@ -395,7 +395,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { // THEN show primary bouncer verify(mKeyguardViewController, never()).showPrimaryBouncer(anyBoolean()); - verify(mBouncerInteractor).showOrUnlockDevice(any()); + verify(mDeviceEntryInteractor).attemptDeviceEntry(); } @Test @@ -408,6 +408,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { mUnderTest.onLongPress(); // THEN don't show primary bouncer - verify(mBouncerInteractor, never()).showOrUnlockDevice(any()); + verify(mDeviceEntryInteractor, never()).attemptDeviceEntry(); + verify(mKeyguardViewController, never()).showPrimaryBouncer(anyBoolean()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index 0c06808b10c9..87ab5b0d157f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt @@ -26,7 +26,7 @@ import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.keyguard.KeyguardSecurityModel import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.scene.SceneTestUtils @@ -127,6 +127,39 @@ class AuthenticationRepositoryTest : SysuiTestCase() { assertThat(values.last()).isTrue() } + @Test + fun reportAuthenticationAttempt_emitsAuthenticationChallengeResult() = + testScope.runTest { + val authenticationChallengeResults by + collectValues(underTest.authenticationChallengeResult) + + runCurrent() + underTest.reportAuthenticationAttempt(true) + runCurrent() + underTest.reportAuthenticationAttempt(false) + runCurrent() + underTest.reportAuthenticationAttempt(true) + + assertThat(authenticationChallengeResults).isEqualTo(listOf(true, false, true)) + } + + @Test + fun isPinEnhancedPrivacyEnabled() = + testScope.runTest { + whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[0].id)) + .thenReturn(false) + whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[1].id)) + .thenReturn(true) + + val values by collectValues(underTest.isPinEnhancedPrivacyEnabled) + assertThat(values.first()).isTrue() + assertThat(values.last()).isFalse() + + userRepository.setSelectedUserInfo(USER_INFOS[1]) + assertThat(values.last()).isTrue() + + } + private fun setSecurityModeAndDispatchBroadcast( securityMode: KeyguardSecurityModel.SecurityMode, ) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 103f2b8ac313..7439db29b513 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -20,9 +20,8 @@ import android.app.admin.DevicePolicyManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue @@ -51,33 +50,16 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val authMethod by collectLastValue(underTest.authenticationMethod) runCurrent() - assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Pin) - assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(DomainLayerAuthenticationMethodModel.Pin) + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin) + assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin) utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Password + AuthenticationMethodModel.Password ) - assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Password) + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password) assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(DomainLayerAuthenticationMethodModel.Password) - } - - @Test - fun authenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() = - testScope.runTest { - val authMethod by collectLastValue(underTest.authenticationMethod) - runCurrent() - - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.None - ) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) - - assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe) - assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(DomainLayerAuthenticationMethodModel.Swipe) + .isEqualTo(AuthenticationMethodModel.Password) } @Test @@ -86,23 +68,18 @@ class AuthenticationInteractorTest : SysuiTestCase() { val authMethod by collectLastValue(underTest.authenticationMethod) runCurrent() - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.None - ) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(false) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None) + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None) assertThat(underTest.getAuthenticationMethod()) - .isEqualTo(DomainLayerAuthenticationMethodModel.None) + .isEqualTo(AuthenticationMethodModel.None) } @Test fun authenticate_withCorrectPin_returnsTrue() = testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(isThrottled).isFalse() @@ -111,9 +88,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withIncorrectPin_returnsFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))) .isEqualTo(AuthenticationResult.FAILED) } @@ -121,9 +96,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test(expected = IllegalArgumentException::class) fun authenticate_withEmptyPin_throwsException() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) underTest.authenticate(listOf()) } @@ -132,7 +105,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val pin = List(16) { 9 } utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) overrideCredential(pin) } @@ -148,9 +121,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( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(List(17) { 9 })) .isEqualTo(AuthenticationResult.FAILED) } @@ -160,7 +131,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val isThrottled by collectLastValue(underTest.isThrottled) utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Password + AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList())) @@ -172,7 +143,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withIncorrectPassword_returnsFalse() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Password + AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("alohomora".toList())) @@ -183,7 +154,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withCorrectPattern_returnsTrue() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pattern + AuthenticationMethodModel.Pattern ) assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)) @@ -194,7 +165,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withIncorrectPattern_returnsFalse() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pattern + AuthenticationMethodModel.Pattern ) assertThat( @@ -211,12 +182,12 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test - fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() = + fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) val isThrottled by collectLastValue(underTest.isThrottled) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() @@ -234,11 +205,11 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test - fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() = + fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalse() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() @@ -250,16 +221,14 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.FAILED) - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) - assertThat(isUnlocked).isFalse() } @Test - fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() = + fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalse() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() @@ -271,16 +240,14 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.FAILED) - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) - assertThat(isUnlocked).isFalse() } @Test - fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() = + fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrue() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) } assertThat(isAutoConfirmEnabled).isTrue() @@ -292,18 +259,16 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SUCCEEDED) - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) - assertThat(isUnlocked).isTrue() } @Test - fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringThrottling_returnsNullAndHasNoEffects() = + fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringThrottling_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) setThrottleDuration(42) } @@ -321,10 +286,10 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test - fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() = + fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() = testScope.runTest { utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(false) } assertThat( @@ -334,53 +299,38 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SKIPPED) - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) - assertThat(isUnlocked).isFalse() } @Test - fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() = + fun tryAutoConfirm_withoutCorrectPassword_returnsNull() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Password + AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)) .isEqualTo(AuthenticationResult.SKIPPED) - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) - assertThat(isUnlocked).isFalse() } @Test fun throttling() = testScope.runTest { - val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) val throttling by collectLastValue(underTest.throttling) val isThrottled by collectLastValue(underTest.isThrottled) - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) - assertThat(isUnlocked).isTrue() - assertThat(isThrottled).isFalse() - assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) - - utils.deviceEntryRepository.setUnlocked(false) - assertThat(isUnlocked).isFalse() assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) // Make many wrong attempts, but just shy of what's needed to get throttled: repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) { underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN - assertThat(isUnlocked).isFalse() assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) } // Make one more wrong attempt, leading to throttling: underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN - assertThat(isUnlocked).isFalse() assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( @@ -394,7 +344,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Correct PIN, but throttled, so doesn't attempt it: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(isUnlocked).isFalse() assertThat(isThrottled).isTrue() assertThat(throttling) .isEqualTo( @@ -427,7 +376,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Move the clock forward one more second, to completely finish the throttling period: advanceTimeBy(1000) - assertThat(isUnlocked).isFalse() assertThat(isThrottled).isFalse() assertThat(throttling) .isEqualTo( @@ -441,7 +389,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { // Correct PIN and no longer throttled so unlocks successfully: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(isUnlocked).isTrue() assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) } @@ -451,7 +398,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(false) } @@ -463,7 +410,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) } @@ -480,7 +427,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) overrideCredential( buildList { @@ -497,7 +444,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) utils.authenticationRepository.apply { - setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin) + setAuthenticationMethod(AuthenticationMethodModel.Pin) overrideCredential( buildList { repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 577539620ee3..6ead0e9dc1f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -19,16 +19,14 @@ package com.android.systemui.bouncer.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationResult +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R import com.android.systemui.scene.SceneTestUtils -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlin.math.ceil import kotlin.time.Duration.Companion.milliseconds @@ -48,17 +46,9 @@ class BouncerInteractorTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope private val authenticationInteractor = utils.authenticationInteractor() - private val sceneInteractor = utils.sceneInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ) private val underTest = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, ) @Before @@ -74,16 +64,10 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() - utils.deviceEntryRepository.setUnlocked(false) - underTest.showOrUnlockDevice() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) - underTest.clearMessage() assertThat(message).isEmpty() @@ -94,7 +78,6 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(underTest.authenticate(listOf(9, 8, 7))) .isEqualTo(AuthenticationResult.FAILED) assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) @@ -102,37 +85,25 @@ class BouncerInteractorTest : SysuiTestCase() { // Correct input. assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @Test fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - val message by collectLastValue(underTest.message) val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) - utils.deviceEntryRepository.setUnlocked(false) - underTest.showOrUnlockDevice() assertThat(isAutoConfirmEnabled).isTrue() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) - underTest.clearMessage() // Incomplete input. assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(message).isEmpty() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) // Wrong 6-digit pin assertThat(underTest.authenticate(listOf(1, 2, 3, 5, 5, 6), tryAutoConfirm = true)) .isEqualTo(AuthenticationResult.FAILED) - assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) // Correct input. assertThat( @@ -142,27 +113,20 @@ class BouncerInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @Test fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) runCurrent() - utils.deviceEntryRepository.setUnlocked(false) - underTest.showOrUnlockDevice() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - underTest.clearMessage() // Incomplete input. assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true)) .isEqualTo(AuthenticationResult.SKIPPED) assertThat(message).isEmpty() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) // Correct input. assertThat( @@ -173,25 +137,16 @@ class BouncerInteractorTest : SysuiTestCase() { ) .isEqualTo(AuthenticationResult.SKIPPED) assertThat(message).isEmpty() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @Test fun passwordAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) runCurrent() - utils.deviceEntryRepository.setUnlocked(false) - underTest.showOrUnlockDevice() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) - - underTest.clearMessage() - assertThat(message).isEmpty() underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) @@ -200,7 +155,6 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(underTest.authenticate("alohamora".toList())) .isEqualTo(AuthenticationResult.FAILED) assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) @@ -208,26 +162,16 @@ class BouncerInteractorTest : SysuiTestCase() { // Correct input. assertThat(underTest.authenticate("password".toList())) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) } @Test fun patternAuthMethod() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) runCurrent() - utils.deviceEntryRepository.setUnlocked(false) - underTest.showOrUnlockDevice() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) - - underTest.clearMessage() - assertThat(message).isEmpty() - underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) @@ -243,7 +187,6 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(wrongPattern.size).isAtLeast(utils.authenticationRepository.minPatternLength) assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED) assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) @@ -257,7 +200,6 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(underTest.authenticate(tooShortPattern)) .isEqualTo(AuthenticationResult.SKIPPED) assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN) @@ -265,51 +207,6 @@ class BouncerInteractorTest : SysuiTestCase() { // Correct input. assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun showOrUnlockDevice_notLocked_switchesToGoneScene() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(true) - runCurrent() - - underTest.showOrUnlockDevice() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) - utils.deviceEntryRepository.setUnlocked(false) - - underTest.showOrUnlockDevice() - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) - } - - @Test - fun showOrUnlockDevice_customMessageShown() = - testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) - val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Password - ) - runCurrent() - utils.deviceEntryRepository.setUnlocked(false) - - val customMessage = "Hello there!" - underTest.showOrUnlockDevice(customMessage) - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(message).isEqualTo(customMessage) } @Test @@ -318,15 +215,9 @@ class BouncerInteractorTest : SysuiTestCase() { val isThrottled by collectLastValue(underTest.isThrottled) val throttling by collectLastValue(underTest.throttling) val message by collectLastValue(underTest.message) - val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - runCurrent() - underTest.showOrUnlockDevice() - runCurrent() - assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) - assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN) repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times -> // Wrong PIN. assertThat(underTest.authenticate(listOf(6, 7, 8, 9))) @@ -355,7 +246,6 @@ class BouncerInteractorTest : SysuiTestCase() { // Correct PIN, but throttled, so doesn't change away from the bouncer scene: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer) assertTryAgainMessage( message, FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds @@ -381,42 +271,24 @@ class BouncerInteractorTest : SysuiTestCase() { FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, ) ) - assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer) // Correct PIN and no longer throttled so changes to the Gone scene: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(currentScene?.key).isEqualTo(SceneKey.Gone) assertThat(isThrottled).isFalse() assertThat(throttling).isEqualTo(AuthenticationThrottlingModel()) } @Test - fun hide_whenOnBouncerScene_hidesBouncerAndGoesToLockscreenScene() = - testScope.runTest { - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "") - val currentScene by collectLastValue(sceneInteractor.desiredScene) - val bouncerSceneKey = currentScene?.key - assertThat(bouncerSceneKey).isEqualTo(SceneKey.Bouncer) - - underTest.onImeHidden() - - assertThat(currentScene?.key).isEqualTo(SceneKey.Lockscreen) - } - - @Test - fun hide_whenNotOnBouncerScene_doesNothing() = + fun imeHiddenEvent_isTriggered() = testScope.runTest { - sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "") - val currentScene by collectLastValue(sceneInteractor.desiredScene) - val notBouncerSceneKey = currentScene?.key - assertThat(notBouncerSceneKey).isNotEqualTo(SceneKey.Bouncer) + val imeHiddenEvent by collectLastValue(underTest.onImeHidden) + runCurrent() underTest.onImeHidden() + runCurrent() - assertThat(currentScene?.key).isEqualTo(notBouncerSceneKey) + assertThat(imeHiddenEvent).isNotNull() } private fun assertTryAgainMessage( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 8e1f5ac58b68..cfcb54574144 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -19,8 +19,8 @@ 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.model.AuthenticationMethodModel 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.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -37,23 +37,16 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val authenticationInteractor = utils.authenticationInteractor() private val sceneInteractor = utils.sceneInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, + private val bouncerInteractor = + utils.bouncerInteractor( + authenticationInteractor = utils.authenticationInteractor(), ) private val underTest = PinBouncerViewModel( applicationContext = context, viewModelScope = testScope.backgroundScope, - interactor = - utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), + interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true), ) @@ -85,18 +78,14 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { @Test fun onImeVisibilityChanged() = testScope.runTest { - val desiredScene by collectLastValue(sceneInteractor.desiredScene) sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "") - assertThat(desiredScene?.key).isEqualTo(SceneKey.Bouncer) - - underTest.onImeVisibilityChanged(false) - assertThat(desiredScene?.key).isEqualTo(SceneKey.Bouncer) + val onImeHidden by collectLastValue(bouncerInteractor.onImeHidden) underTest.onImeVisibilityChanged(true) - assertThat(desiredScene?.key).isEqualTo(SceneKey.Bouncer) + assertThat(onImeHidden).isNull() underTest.onImeVisibilityChanged(false) - assertThat(desiredScene?.key).isEqualTo(SceneKey.Lockscreen) + assertThat(onImeHidden).isNotNull() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 6ef518e3ab51..f4346b56676d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -19,10 +19,8 @@ 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.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel -import com.android.systemui.authentication.data.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.scene.SceneTestUtils @@ -48,16 +46,9 @@ class BouncerViewModelTest : SysuiTestCase() { private val testScope = utils.testScope private val authenticationInteractor = utils.authenticationInteractor() private val actionButtonInteractor = utils.bouncerActionButtonInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = utils.sceneInteractor(), - ) private val bouncerInteractor = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = utils.sceneInteractor(), ) private val underTest = utils.bouncerViewModel( @@ -96,8 +87,7 @@ class BouncerViewModelTest : SysuiTestCase() { @Test fun authMethodChanged_doesNotReuseInstances() = testScope.runTest { - val seen = - mutableMapOf<DomainLayerAuthenticationMethodModel, AuthMethodBouncerViewModel>() + val seen = mutableMapOf<AuthenticationMethodModel, AuthMethodBouncerViewModel>() val authMethodViewModel: AuthMethodBouncerViewModel? by collectLastValue(underTest.authMethodViewModel) @@ -137,7 +127,7 @@ class BouncerViewModelTest : SysuiTestCase() { @Test fun authMethodsToTest_returnsCompleteSampleOfAllAuthMethodTypes() { assertThat(authMethodsToTest().map { it::class }.toSet()) - .isEqualTo(DomainLayerAuthenticationMethodModel::class.sealedSubclasses.toSet()) + .isEqualTo(AuthenticationMethodModel::class.sealedSubclasses.toSet()) } @Test @@ -145,9 +135,7 @@ class BouncerViewModelTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(underTest.message) val throttling by collectLastValue(bouncerInteractor.throttling) - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(message?.isUpdateAnimated).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { @@ -170,9 +158,7 @@ class BouncerViewModelTest : SysuiTestCase() { } ) val throttling by collectLastValue(bouncerInteractor.throttling) - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(isInputEnabled).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { @@ -189,9 +175,7 @@ class BouncerViewModelTest : SysuiTestCase() { fun throttlingDialogMessage() = testScope.runTest { val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage) - utils.authenticationRepository.setAuthenticationMethod( - DataLayerAuthenticationMethodModel.Pin - ) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { // Wrong PIN. @@ -226,34 +210,29 @@ class BouncerViewModelTest : SysuiTestCase() { assertThat(isSideBySideSupported).isFalse() } - private fun authMethodsToTest(): List<DomainLayerAuthenticationMethodModel> { - return listOf( - DomainLayerAuthenticationMethodModel.None, - DomainLayerAuthenticationMethodModel.Swipe, - DomainLayerAuthenticationMethodModel.Pin, - DomainLayerAuthenticationMethodModel.Password, - DomainLayerAuthenticationMethodModel.Pattern, - ) - } + @Test + fun isFoldSplitRequired() = + testScope.runTest { + val isFoldSplitRequired by collectLastValue(underTest.isFoldSplitRequired) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + assertThat(isFoldSplitRequired).isTrue() + utils.authenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Password + ) + assertThat(isFoldSplitRequired).isFalse() - private fun FakeAuthenticationRepository.setAuthenticationMethod( - model: DomainLayerAuthenticationMethodModel, - ) { - setAuthenticationMethod( - when (model) { - is DomainLayerAuthenticationMethodModel.None, - is DomainLayerAuthenticationMethodModel.Swipe -> - DataLayerAuthenticationMethodModel.None - is DomainLayerAuthenticationMethodModel.Pin -> - DataLayerAuthenticationMethodModel.Pin - is DomainLayerAuthenticationMethodModel.Password -> - DataLayerAuthenticationMethodModel.Password - is DomainLayerAuthenticationMethodModel.Pattern -> - DataLayerAuthenticationMethodModel.Pattern - } - ) - utils.deviceEntryRepository.setInsecureLockscreenEnabled( - model !is DomainLayerAuthenticationMethodModel.None + utils.authenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pattern + ) + assertThat(isFoldSplitRequired).isTrue() + } + + private fun authMethodsToTest(): List<AuthenticationMethodModel> { + return listOf( + AuthenticationMethodModel.None, + AuthenticationMethodModel.Pin, + AuthenticationMethodModel.Password, + AuthenticationMethodModel.Pattern, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 390742031381..c498edf0e971 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -19,8 +19,7 @@ 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.model.AuthenticationMethodModel -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R import com.android.systemui.scene.SceneTestUtils @@ -46,16 +45,9 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private val testScope = utils.testScope private val authenticationInteractor = utils.authenticationInteractor() private val sceneInteractor = utils.sceneInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = utils.sceneInteractor(), - ) private val bouncerInteractor = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, ) private val bouncerViewModel = utils.bouncerViewModel( @@ -88,8 +80,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) assertThat(password).isEqualTo("") assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(underTest.authenticationMethod) - .isEqualTo(DomainAuthenticationMethodModel.Password) + assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Password) } @Test @@ -110,19 +101,19 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateKeyPressed_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) lockDeviceAndOpenPasswordBouncer() underTest.onPasswordInputChanged("password") underTest.onAuthenticateKeyPressed() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test fun onAuthenticateKeyPressed_whenWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) lockDeviceAndOpenPasswordBouncer() @@ -132,13 +123,11 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(password).isEqualTo("") assertThat(message?.text).isEqualTo(WRONG_PASSWORD) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @Test fun onAuthenticateKeyPressed_whenEmpty() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) utils.authenticationRepository.setAuthenticationMethod( @@ -147,7 +136,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { utils.deviceEntryRepository.setUnlocked(false) sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) underTest.onShown() // Enter nothing. @@ -155,13 +143,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(password).isEqualTo("") assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @Test fun onAuthenticateKeyPressed_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) lockDeviceAndOpenPasswordBouncer() @@ -171,7 +159,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateKeyPressed() assertThat(password).isEqualTo("") assertThat(message?.text).isEqualTo(WRONG_PASSWORD) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + assertThat(authResult).isFalse() // Enter the correct password: underTest.onPasswordInputChanged("password") @@ -179,7 +167,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateKeyPressed() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 47db4f8faeca..3f5ddba23165 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -19,9 +19,8 @@ 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.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R @@ -49,16 +48,9 @@ class PatternBouncerViewModelTest : SysuiTestCase() { private val testScope = utils.testScope private val authenticationInteractor = utils.authenticationInteractor() private val sceneInteractor = utils.sceneInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = utils.sceneInteractor(), - ) private val bouncerInteractor = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, ) private val bouncerViewModel = utils.bouncerViewModel( @@ -96,8 +88,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(underTest.authenticationMethod) - .isEqualTo(DomainAuthenticationMethodModel.Pattern) + assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Pattern) } @Test @@ -120,7 +111,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) lockDeviceAndOpenPatternBouncer() @@ -150,7 +142,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragEnd() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test @@ -344,7 +336,8 @@ class PatternBouncerViewModelTest : SysuiTestCase() { @Test fun onDragEnd_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) val message by collectLastValue(bouncerViewModel.message) val selectedDots by collectLastValue(underTest.selectedDots) val currentDot by collectLastValue(underTest.currentDot) @@ -356,14 +349,14 @@ class PatternBouncerViewModelTest : SysuiTestCase() { assertThat(selectedDots).isEmpty() assertThat(currentDot).isNull() assertThat(message?.text).isEqualTo(WRONG_PATTERN) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + assertThat(authResult).isFalse() // Enter the correct pattern: CORRECT_PATTERN.forEach(::dragToCoordinate) underTest.onDragEnd() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } private fun dragOverCoordinates(vararg coordinatesDragged: Point) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index e07c0b87bc79..7a9cb6cc18c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -19,9 +19,8 @@ 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.model.AuthenticationMethodModel import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.res.R import com.android.systemui.scene.SceneTestUtils @@ -48,16 +47,9 @@ class PinBouncerViewModelTest : SysuiTestCase() { private val testScope = utils.testScope private val sceneInteractor = utils.sceneInteractor() private val authenticationInteractor = utils.authenticationInteractor() - private val deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = utils.sceneInteractor(), - ) private val bouncerInteractor = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, ) private val bouncerViewModel = utils.bouncerViewModel( @@ -96,8 +88,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(message?.text).ignoringCase().isEqualTo(ENTER_YOUR_PIN) assertThat(pin).isEmpty() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - assertThat(underTest.authenticationMethod) - .isEqualTo(DomainAuthenticationMethodModel.Pin) + assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Pin) } @Test @@ -181,7 +172,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) lockDeviceAndOpenPinBouncer() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -190,7 +182,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test @@ -217,7 +209,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAuthenticateButtonClicked_correctAfterWrong() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) lockDeviceAndOpenPinBouncer() @@ -230,7 +223,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() assertThat(message?.text).ignoringCase().isEqualTo(WRONG_PIN) assertThat(pin).isEmpty() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) + assertThat(authResult).isFalse() // Enter the correct PIN: FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> @@ -240,21 +233,22 @@ class PinBouncerViewModelTest : SysuiTestCase() { underTest.onAuthenticateButtonClicked() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test fun onAutoConfirm_whenCorrect() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + val authResult by + collectLastValue(authenticationInteractor.authenticationChallengeResult) lockDeviceAndOpenPinBouncer() FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> underTest.onPinButtonClicked(digit) } - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + assertThat(authResult).isTrue() } @Test @@ -360,6 +354,18 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden) } + @Test + fun isDigitButtonAnimationEnabled() = + testScope.runTest { + val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled) + + utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true) + assertThat(isAnimationEnabled).isFalse() + + utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false) + assertThat(isAnimationEnabled).isTrue() + } + private fun TestScope.lockDeviceAndOpenPinBouncer() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.deviceEntryRepository.setUnlocked(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index a6c4f1929333..af4bf367c466 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -240,7 +240,7 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun contentOrdering() = + fun ordering_smartspaceBeforeUmoBeforeWidgets() = testScope.runTest { tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt index 2c80035873f0..97ac8c62d69d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt @@ -83,16 +83,27 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { } @Test - fun isInsecureLockscreenEnabled() = + fun isLockscreenEnabled() = testScope.runTest { whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[0].id)).thenReturn(false) whenever(lockPatternUtils.isLockScreenDisabled(USER_INFOS[1].id)).thenReturn(true) userRepository.setSelectedUserInfo(USER_INFOS[0]) - assertThat(underTest.isInsecureLockscreenEnabled()).isTrue() + assertThat(underTest.isLockscreenEnabled()).isTrue() userRepository.setSelectedUserInfo(USER_INFOS[1]) - assertThat(underTest.isInsecureLockscreenEnabled()).isFalse() + assertThat(underTest.isLockscreenEnabled()).isFalse() + } + + @Test + fun reportSuccessfulAuthentication_shouldUpdateIsUnlocked() = + testScope.runTest { + val isUnlocked by collectLastValue(underTest.isUnlocked) + assertThat(isUnlocked).isFalse() + + underTest.reportSuccessfulAuthentication() + + assertThat(isUnlocked).isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index aebadc5b5730..abd9f2846d2f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +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.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository @@ -60,7 +60,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { testScope.runTest { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) utils.deviceEntryRepository.apply { - setInsecureLockscreenEnabled(false) + setLockscreenEnabled(false) // Toggle isUnlocked, twice. // @@ -83,8 +83,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() val isUnlocked by collectLastValue(underTest.isUnlocked) assertThat(isUnlocked).isTrue() @@ -94,8 +93,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun isDeviceEntered_onLockscreenWithSwipe_isFalse() = testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) assertThat(isDeviceEntered).isFalse() @@ -105,8 +103,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun isDeviceEntered_onShadeBeforeDismissingLockscreenWithSwipe_isFalse() = testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Shade) @@ -118,8 +115,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun isDeviceEntered_afterDismissingLockscreenWithSwipe_isTrue() = testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Gone) @@ -131,8 +127,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun isDeviceEntered_onShadeAfterDismissingLockscreenWithSwipe_isTrue() = testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Gone) @@ -148,7 +143,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setLockscreenEnabled(true) switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Bouncer) @@ -160,8 +155,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun canSwipeToEnter_onLockscreenWithSwipe_isTrue() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) @@ -172,7 +166,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun canSwipeToEnter_onLockscreenWithPin_isFalse() = testScope.runTest { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setLockscreenEnabled(true) switchToScene(SceneKey.Lockscreen) val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) @@ -182,8 +176,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun canSwipeToEnter_afterLockscreenDismissedInSwipeMode_isFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + setupSwipeDeviceEntryMethod() switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Gone) @@ -192,6 +185,11 @@ class DeviceEntryInteractorTest : SysuiTestCase() { assertThat(canSwipeToEnter).isFalse() } + private fun setupSwipeDeviceEntryMethod() { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + utils.deviceEntryRepository.setLockscreenEnabled(true) + } + @Test fun canSwipeToEnter_whenTrustedByTrustManager_isTrue() = testScope.runTest { @@ -278,12 +276,68 @@ class DeviceEntryInteractorTest : SysuiTestCase() { } @Test + fun showOrUnlockDevice_notLocked_switchesToGoneScene() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.desiredScene) + switchToScene(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + utils.deviceEntryRepository.setUnlocked(true) + runCurrent() + + underTest.attemptDeviceEntry() + + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + } + + @Test + fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.desiredScene) + switchToScene(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + + underTest.attemptDeviceEntry() + + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + } + + @Test + fun showOrUnlockDevice_authMethodSwipe_switchesToGoneScene() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.desiredScene) + switchToScene(SceneKey.Lockscreen) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + + utils.deviceEntryRepository.setLockscreenEnabled(true) + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + + underTest.attemptDeviceEntry() + + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone)) + } + + @Test fun isBypassEnabled_disabledInRepository_false() = testScope.runTest { utils.deviceEntryRepository.setBypassEnabled(false) assertThat(underTest.isBypassEnabled.value).isFalse() } + @Test + fun successfulAuthenticationChallengeAttempt_updatedIsUnlockedState() = + testScope.runTest { + val isUnlocked by collectLastValue(underTest.isUnlocked) + assertThat(isUnlocked).isFalse() + + utils.authenticationRepository.reportAuthenticationAttempt(true) + + assertThat(isUnlocked).isTrue() + } + private fun switchToScene(sceneKey: SceneKey) { sceneInteractor.changeScene(SceneModel(sceneKey), "reason") } diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt new file mode 100644 index 000000000000..e8eda8096b1e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt @@ -0,0 +1,119 @@ +/* + * 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.deviceentry.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceEntryUdfpsInteractorTest : SysuiTestCase() { + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository + private lateinit var biometricsRepository: FakeBiometricSettingsRepository + + private lateinit var underTest: DeviceEntryUdfpsInteractor + + @Before + fun setUp() { + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() + biometricsRepository = FakeBiometricSettingsRepository() + + underTest = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = fingerprintAuthRepository, + biometricSettingsRepository = biometricsRepository, + ) + } + + @Test + fun udfpsSupported_rearFp_false() = runTest { + val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported) + fingerprintPropertyRepository.supportsRearFps() + assertThat(isUdfpsSupported).isFalse() + } + + @Test + fun udfpsSupoprted() = runTest { + val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported) + fingerprintPropertyRepository.supportsUdfps() + assertThat(isUdfpsSupported).isTrue() + } + + @Test + fun udfpsEnrolledAndEnabled() = runTest { + val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled) + fingerprintPropertyRepository.supportsUdfps() + biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + assertThat(isUdfpsEnrolledAndEnabled).isTrue() + } + + @Test + fun udfpsEnrolledAndEnabled_rearFp_false() = runTest { + val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled) + fingerprintPropertyRepository.supportsRearFps() + biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + assertThat(isUdfpsEnrolledAndEnabled).isFalse() + } + + @Test + fun udfpsEnrolledAndEnabled_notEnrolledOrEnabled_false() = runTest { + val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled) + fingerprintPropertyRepository.supportsUdfps() + biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + assertThat(isUdfpsEnrolledAndEnabled).isFalse() + } + + @Test + fun isListeningForUdfps() = runTest { + val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps) + fingerprintPropertyRepository.supportsUdfps() + fingerprintAuthRepository.setIsRunning(true) + assertThat(isListeningForUdfps).isTrue() + } + + @Test + fun isListeningForUdfps_rearFp_false() = runTest { + val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps) + fingerprintPropertyRepository.supportsRearFps() + fingerprintAuthRepository.setIsRunning(true) + assertThat(isListeningForUdfps).isFalse() + } + + @Test + fun isListeningForUdfps_notRunning_false() = runTest { + val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps) + fingerprintPropertyRepository.supportsUdfps() + fingerprintAuthRepository.setIsRunning(false) + assertThat(isListeningForUdfps).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt index 5eab2fc73fe6..df52265384fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt @@ -84,8 +84,8 @@ class BurnInInteractorTest : SysuiTestCase() { @Test fun udfpsBurnInOffset_updatesOnResolutionScaleChange() = testScope.runTest { - val udfpsBurnInOffsetX by collectLastValue(underTest.udfpsBurnInXOffset) - val udfpsBurnInOffsetY by collectLastValue(underTest.udfpsBurnInYOffset) + val udfpsBurnInOffsetX by collectLastValue(underTest.deviceEntryIconXOffset) + val udfpsBurnInOffsetY by collectLastValue(underTest.deviceEntryIconYOffset) assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset) assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset) @@ -101,7 +101,7 @@ class BurnInInteractorTest : SysuiTestCase() { @Test fun udfpsBurnInProgress_updatesOnDozeTimeTick() = testScope.runTest { - val udfpsBurnInProgress by collectLastValue(underTest.udfpsBurnInProgress) + val udfpsBurnInProgress by collectLastValue(underTest.udfpsProgress) assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress) setBurnInProgress(.88f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt index 3442df62a441..2dfc13258d63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt @@ -123,30 +123,30 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { runCurrent() // THEN burn in offsets are 0 - assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f) - assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0) - assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0) + assertThat(burnInOffsets?.progress).isEqualTo(0f) + assertThat(burnInOffsets?.y).isEqualTo(0) + assertThat(burnInOffsets?.x).isEqualTo(0) // WHEN we're in the middle of the doze amount change keyguardRepository.setDozeAmount(.50f) runCurrent() // THEN burn in is updated (between 0 and the full offset) - assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f) - assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0) - assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0) - assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress) - assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset) - assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset) + assertThat(burnInOffsets?.progress).isGreaterThan(0f) + assertThat(burnInOffsets?.y).isGreaterThan(0) + assertThat(burnInOffsets?.x).isGreaterThan(0) + assertThat(burnInOffsets?.progress).isLessThan(burnInProgress) + assertThat(burnInOffsets?.y).isLessThan(burnInYOffset) + assertThat(burnInOffsets?.x).isLessThan(burnInXOffset) // WHEN we're fully dozing keyguardRepository.setDozeAmount(1f) runCurrent() // THEN burn in offsets are updated to final current values (for the given time) - assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress) - assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset) - assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset) + assertThat(burnInOffsets?.progress).isEqualTo(burnInProgress) + assertThat(burnInOffsets?.y).isEqualTo(burnInYOffset) + assertThat(burnInOffsets?.x).isEqualTo(burnInXOffset) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt index 71313c8a5bf3..75bdcddf516b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt @@ -24,12 +24,14 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.LockIconViewController +import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R @@ -42,6 +44,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Answers import org.mockito.Mock +import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi @@ -77,7 +80,9 @@ class DefaultDeviceEntryIconSectionTest : SysuiTestCase() { notificationPanelView, featureFlags, { lockIconViewController }, - { DeviceEntryIconViewModel() }, + { mock(DeviceEntryIconViewModel::class.java) }, + { mock(DeviceEntryForegroundViewModel::class.java) }, + { mock(DeviceEntryBackgroundViewModel::class.java) }, { falsingManager }, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt new file mode 100644 index 000000000000..f282481ba01c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt @@ -0,0 +1,81 @@ +/* + * 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodToGoneTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: AodToGoneTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = AodToGoneTransitionViewModel(interactor) + } + + @Test + fun deviceEntryParentViewHides() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + value = value, + transitionState = state, + ownerName = "AodToGoneTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt new file mode 100644 index 000000000000..517149c99a62 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt @@ -0,0 +1,135 @@ +/* + * 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodToLockscreenTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: AodToLockscreenTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + underTest = + AodToLockscreenTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = FakeBiometricSettingsRepository(), + ), + ) + } + + @Test + fun deviceEntryParentViewShows() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(1f) } + } + + @Test + fun deviceEntryBackgroundView_udfps_alphaFadeIn() = runTest { + fingerprintPropertyRepository.supportsUdfps() + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // fade in + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(.2f) + + repository.sendTransitionStep(step(0.3f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(.6f) + + repository.sendTransitionStep(step(0.6f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryBackgroundView_rearFp_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + // no updates + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(0.1f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(0.3f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(0.6f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ownerName = "AodToLockscreenTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt new file mode 100644 index 000000000000..96f69462accf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt @@ -0,0 +1,81 @@ +/* + * 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodToOccludedTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: AodToOccludedTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = AodToOccludedTransitionViewModel(interactor) + } + + @Test + fun deviceEntryParentViewHides() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { Truth.assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.OCCLUDED, + value = value, + transitionState = state, + ownerName = "AodToOccludedTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt new file mode 100644 index 000000000000..5dccc3b1d05f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt @@ -0,0 +1,82 @@ +/* + * 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class DozingToLockscreenTransitionViewModelTest : SysuiTestCase() { + private lateinit var testScope: TestScope + private lateinit var underTest: DozingToLockscreenTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + underTest = + DozingToLockscreenTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + ) + } + + @Test + fun deviceEntryParentViewShows() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(1f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.DOZING, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ownerName = "DozingToLockscreenTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index 6d47aed58dac..fd125e099f1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -19,6 +19,12 @@ 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.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState.AOD @@ -35,6 +41,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.util.mockito.mock import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -44,22 +51,34 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { private lateinit var underTest: DreamingToLockscreenTransitionViewModel private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository @Before fun setUp() { repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() val interactor = KeyguardTransitionInteractorFactory.create( scope = TestScope().backgroundScope, repository = repository, ) .keyguardTransitionInteractor - underTest = DreamingToLockscreenTransitionViewModel(interactor, mock()) + underTest = + DreamingToLockscreenTransitionViewModel( + interactor, + mock(), + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = FakeBiometricSettingsRepository(), + ), + ) } @Test @@ -129,6 +148,78 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { } @Test + fun deviceEntryParentViewFadeIn() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.deviceEntryParentViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, STARTED)) + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } + + job.cancel() + } + + @Test + fun deviceEntryBackgroundViewAppear() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.UDFPS_OPTICAL, + sensorLocations = emptyMap(), + ) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, STARTED)) + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + values.forEach { assertThat(it).isEqualTo(1f) } + + job.cancel() + } + + @Test + fun deviceEntryBackground_noUdfps_noUpdates() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.REAR, + sensorLocations = emptyMap(), + ) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, STARTED)) + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(0) // no updates + + job.cancel() + } + + @Test fun lockscreenTranslationY() = runTest(UnconfinedTestDispatcher()) { val values = mutableListOf<Float>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt index 255f4df17244..c1444a55f7d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt @@ -19,7 +19,11 @@ 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.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState @@ -27,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -34,11 +39,14 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class GoneToAodTransitionViewModelTest : SysuiTestCase() { private lateinit var underTest: GoneToAodTransitionViewModel private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository private lateinit var testScope: TestScope @Before @@ -47,13 +55,24 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { testScope = TestScope(testDispatcher) repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = testScope.backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = GoneToAodTransitionViewModel(interactor) + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + + underTest = + GoneToAodTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) } @Test @@ -63,11 +82,11 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { val enterFromTopTranslationY by collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt())) - // The animation should only start > halfway through + // The animation should only start > .4f way through repository.sendTransitionStep(step(0f, TransitionState.STARTED)) assertThat(enterFromTopTranslationY).isEqualTo(pixels) - repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.4f)) assertThat(enterFromTopTranslationY).isEqualTo(pixels) repository.sendTransitionStep(step(.85f)) @@ -83,11 +102,11 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha) - // The animation should only start > halfway through + // The animation should only start > .4f way through repository.sendTransitionStep(step(0f, TransitionState.STARTED)) assertThat(enterFromTopAnimationAlpha).isEqualTo(0f) - repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.4f)) assertThat(enterFromTopAnimationAlpha).isEqualTo(0f) repository.sendTransitionStep(step(.85f)) @@ -97,6 +116,98 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { assertThat(enterFromTopAnimationAlpha).isEqualTo(1f) } + @Test + fun deviceEntryBackgroundViewAlpha() = + testScope.runTest { + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled() = + testScope.runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isIn(Range.closed(.01f, 1f)) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isIn(Range.closed(.99f, 1f)) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFpEnrolled() = + testScope.runTest { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + @Test + fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() = + testScope.runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + private fun step( value: Float, state: TransitionState = TransitionState.RUNNING diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 7de28de4d436..0b3bc9daa8b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -19,7 +19,7 @@ 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.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey @@ -45,7 +45,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setLockscreenEnabled(true) utils.deviceEntryRepository.setUnlocked(true) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt new file mode 100644 index 000000000000..4074851490ab --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt @@ -0,0 +1,251 @@ +/* + * 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.collectValues +import com.android.runCurrent +import com.android.runTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR +import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.google.common.collect.Range +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class LockscreenToAodTransitionViewModelTest : SysuiTestCase() { + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToAodTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val deviceEntryRepository: FakeDeviceEntryRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + val fingerprintPropertyRepository: FakeFingerprintPropertyRepository + val biometricSettingsRepository: FakeBiometricSettingsRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + + fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } + } + + private val testComponent: TestComponent = + DaggerLockscreenToAodTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(FACE_AUTH_REFACTOR, true) + set(FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) + + @Test + fun backgroundViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.3f)) + assertThat(actual).isIn(Range.closed(.1f, .9f)) + + // finish fading out before the end of the full transition + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) + } + + @Test + fun backgroundViewAlpha_shadeExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + shadeExpanded(true) + runCurrent() + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.3f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled_shadeNotExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + shadeExpanded(false) + runCurrent() + + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(.3f), + step(.7f), + step(1f), + ), + testScope = testScope, + ) + // immediately 1f + values.forEach { assertThat(it).isEqualTo(1f) } + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled_shadeExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + shadeExpanded(true) + runCurrent() + + // fade in + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.3f)) + assertThat(actual).isIn(Range.closed(.1f, .9f)) + + // finish fading in before the end of the full transition + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFp_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsRearFps() + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.1f)) + assertThat(actual).isIn(Range.closed(.1f, .9f)) + + // finish fading out before the end of the full transition + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFp_shadeExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsRearFps() + shadeExpanded(true) + runCurrent() + + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(.3f), + step(.7f), + step(1f), + ), + testScope = testScope, + ) + // immediately 0f + values.forEach { assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = value, + transitionState = state, + ownerName = "LockscreenToAodTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index 89a1d2b3011d..5c85357a37a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -18,88 +18,172 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.collectValues +import com.android.runCurrent +import com.android.runTest import com.android.systemui.SysuiTestCase +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher +import dagger.BindsInstance +import dagger.Component import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { - private lateinit var underTest: LockscreenToDreamingTransitionViewModel - private lateinit var repository: FakeKeyguardTransitionRepository - - @Before - fun setUp() { - repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = TestScope().backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = LockscreenToDreamingTransitionViewModel(interactor) + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToDreamingTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + + fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } } + private val testComponent: TestComponent = + DaggerLockscreenToDreamingTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, true) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) @Test fun lockscreenFadeOut() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - - val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) - - // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.1f)) - repository.sendTransitionStep(step(0.2f)) - repository.sendTransitionStep(step(0.3f)) - // ...up to here - repository.sendTransitionStep(step(1f)) + testComponent.runTest { + val values by collectValues(underTest.lockscreenAlpha) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.1f), + step(.2f), + step(.3f), // ...up to here + step(1f), + ), + testScope = testScope, + ) // Only three values should be present, since the dream overlay runs for a small // fraction of the overall animation time assertThat(values.size).isEqualTo(5) values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } - - job.cancel() } @Test fun lockscreenTranslationY() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - + testComponent.runTest { val pixels = 100 - val job = - underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + val values by collectValues(underTest.lockscreenTranslationY(pixels)) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.5f)) - repository.sendTransitionStep(step(1f)) - // And a final reset event on FINISHED - repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.3f), + step(.5f), + step(1f), + step(1f, TransitionState.FINISHED), // Final reset event on FINISHED + ), + testScope = testScope, + ) assertThat(values.size).isEqualTo(6) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } // Validate finished value assertThat(values[5]).isEqualTo(0f) + } - job.cancel() + @Test + fun deviceEntryParentViewAlpha_shadeExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + shadeExpanded(true) + runCurrent() + + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(0f), + step(.3f), + step(.5f), + step(1f), + step(1f, TransitionState.FINISHED), + ), + testScope = testScope, + ) + + // immediately 0f + values.forEach { assertThat(it).isEqualTo(0f) } + } + + @Test + fun deviceEntryParentViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.1f)) + assertThat(actual).isIn(Range.open(.1f, .9f)) + + // alpha is 1f before the full transition starts ending + repository.sendTransitionStep(step(0.8f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) } private fun step( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt new file mode 100644 index 000000000000..1494c92cdb06 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt @@ -0,0 +1,84 @@ +/* + * 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class LockscreenToGoneTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: LockscreenToGoneTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = + LockscreenToGoneTransitionViewModel( + interactor, + ) + } + + @Test + fun deviceEntryParentViewHides() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + value = value, + transitionState = state, + ownerName = "LockscreenToGoneTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index 41f8856d5ca2..4cbefa3d12ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -18,109 +18,185 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.collectValues +import com.android.runCurrent +import com.android.runTest import com.android.systemui.SysuiTestCase +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import org.junit.Before +import dagger.BindsInstance +import dagger.Component import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { - private lateinit var underTest: LockscreenToOccludedTransitionViewModel - private lateinit var repository: FakeKeyguardTransitionRepository - - @Before - fun setUp() { - repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = TestScope().backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = LockscreenToOccludedTransitionViewModel(interactor) + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToOccludedTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + + fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } } + private val testComponent: TestComponent = + DaggerLockscreenToOccludedTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, true) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) + @Test fun lockscreenFadeOut() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - - val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) - - // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.1f)) - repository.sendTransitionStep(step(0.4f)) - repository.sendTransitionStep(step(0.7f)) - // ...up to here - repository.sendTransitionStep(step(1f)) - + testComponent.runTest { + val values by collectValues(underTest.lockscreenAlpha) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.1f), + step(.4f), + step(.7f), // ...up to here + step(1f), + ), + testScope = testScope, + ) // Only 3 values should be present, since the dream overlay runs for a small fraction // of the overall animation time assertThat(values.size).isEqualTo(5) values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } - - job.cancel() } @Test fun lockscreenTranslationY() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - + testComponent.runTest { val pixels = 100 - val job = - underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - - // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.5f)) - repository.sendTransitionStep(step(1f)) - // ...up to here - + val values by collectValues(underTest.lockscreenTranslationY(pixels)) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.3f), + step(.5f), + step(1f), // ...up to here + ), + testScope = testScope, + ) assertThat(values.size).isEqualTo(5) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } - - job.cancel() } @Test fun lockscreenTranslationYIsCanceled() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - + testComponent.runTest { val pixels = 100 - val job = - underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.3f, TransitionState.CANCELED)) - + val values by collectValues(underTest.lockscreenTranslationY(pixels)) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(0f), + step(.3f), + step(0.3f, TransitionState.CANCELED), + ), + testScope = testScope, + ) assertThat(values.size).isEqualTo(4) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } // Cancel will reset the translation assertThat(values[3]).isEqualTo(0) + } + + @Test + fun deviceEntryParentViewAlpha_shadeExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + shadeExpanded(true) + runCurrent() + + // immediately 0f + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(.5f), + step(1f, TransitionState.FINISHED) + ), + testScope = testScope, + ) + + values.forEach { assertThat(it).isEqualTo(0f) } + } + + @Test + fun deviceEntryParentViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.2f)) + assertThat(actual).isIn(Range.open(.1f, .9f)) + + // alpha is 1f before the full transition starts ending + repository.sendTransitionStep(step(0.8f)) + assertThat(actual).isEqualTo(0f) - job.cancel() + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) } private fun step( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt new file mode 100644 index 000000000000..4f564350741d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -0,0 +1,159 @@ +/* + * 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.runCurrent +import com.android.runTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.google.common.collect.Range +import com.google.common.truth.Truth +import dagger.BindsInstance +import dagger.Component +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToPrimaryBouncerTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + + fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } + } + + private val testComponent: TestComponent = + DaggerLockscreenToPrimaryBouncerTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, true) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) + + @Test + fun deviceEntryParentViewAlpha_shadeExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(true) + runCurrent() + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.2f)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(0.8f)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.1f)) + runCurrent() + Truth.assertThat(actual).isIn(Range.open(.1f, .9f)) + + // alpha is 1f before the full transition starts ending + repository.sendTransitionStep(step(0.8f)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING, + ): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + value = value, + transitionState = state, + ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt new file mode 100644 index 000000000000..0eb8ff6ba966 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt @@ -0,0 +1,170 @@ +/* + * 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class OccludedToAodTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: OccludedToAodTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + + underTest = + OccludedToAodTransitionViewModel( + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) + } + + @Test + fun deviceEntryBackgroundViewAlpha() = runTest { + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // immediately 1f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFpEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // no updates + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + @Test + fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // no updates + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.AOD, + value = value, + transitionState = state, + ownerName = "OccludedToAodTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt index ec95cb8c43f1..d0772270ed5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt @@ -19,6 +19,10 @@ 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.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState @@ -26,6 +30,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -35,22 +40,35 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { private lateinit var underTest: OccludedToLockscreenTransitionViewModel private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = TestScope().backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = OccludedToLockscreenTransitionViewModel(interactor) + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + underTest = + OccludedToLockscreenTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) } @Test @@ -113,6 +131,78 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun deviceEntryParentViewFadeIn() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.deviceEntryParentViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + // Should start running here... + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + // ...up to here + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } + + job.cancel() + } + + @Test + fun deviceEntryBackgroundViewShows() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + + values.forEach { assertThat(it).isEqualTo(1f) } + + job.cancel() + } + + @Test + fun deviceEntryBackgroundView_noUdfpsEnrolled_noUpdates() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values).isEmpty() // no updates + + job.cancel() + } + private fun step( value: Float, state: TransitionState = TransitionState.RUNNING diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt new file mode 100644 index 000000000000..350b31008478 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt @@ -0,0 +1,164 @@ +/* + * 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class PrimaryBouncerToAodTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: PrimaryBouncerToAodTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = + PrimaryBouncerToAodTransitionViewModel( + interactor, + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) + } + + @Test + fun deviceEntryBackgroundViewAlpha() = runTest { + fingerprintPropertyRepository.supportsUdfps() + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled_fadeIn() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(.75f)) + repository.sendTransitionStep(step(1f)) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFpEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + @Test + fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.75f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.AOD, + value = value, + transitionState = state, + ownerName = "PrimaryBouncerToAodTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt new file mode 100644 index 000000000000..24e4920c66d6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt @@ -0,0 +1,142 @@ +/* + * 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: PrimaryBouncerToLockscreenTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + + underTest = + PrimaryBouncerToLockscreenTransitionViewModel( + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) + } + + @Test + fun deviceEntryParentViewAlpha() = runTest { + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // immediately 1f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryBackgroundViewAlpha_udfpsEnrolled_show() = runTest { + fingerprintPropertyRepository.supportsUdfps() + val bgViewAlpha by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 1f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(0.1f)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.3f)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.5f)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(bgViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryBackgroundViewAlpha_rearFpEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + val bgViewAlpha by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(.75f)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(bgViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ownerName = "PrimaryBouncerToLockscreenTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt index b101acf3418b..437a35f2fab5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt @@ -38,8 +38,6 @@ import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.PhoneMediaDevice 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.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData @@ -112,7 +110,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var session: MediaSession private lateinit var mediaData: MediaData @JvmField @Rule val mockito = MockitoJUnit.rule() - private val featureFlags = FakeFeatureFlagsClassic() @Before fun setUp() { @@ -131,7 +128,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { fakeFgExecutor, fakeBgExecutor, dumpster, - featureFlags, ) manager.addListener(listener) @@ -150,7 +146,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken) whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller) setupLeAudioConfiguration(false) - featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, false) } @After @@ -463,7 +458,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() { - featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // When the routing session name is null, and is a system session for a PhoneMediaDevice val phoneDevice = mock(PhoneMediaDevice::class.java) whenever(phoneDevice.iconWithoutBackground).thenReturn(icon) @@ -489,7 +483,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() { - featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) // When the routing session does not have a name, and is a system session whenever(route.name).thenReturn(null) whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) @@ -725,101 +718,6 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.showBroadcastButton).isFalse() } - // Duplicates of above tests with MEDIA_DEVICE_NAME_FIX enabled - - @Test - fun loadMediaDataWithNullToken_withNameFix() { - featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) - manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null)) - fakeBgExecutor.runAllReady() - fakeFgExecutor.runAllReady() - val data = captureDeviceData(KEY) - assertThat(data.enabled).isTrue() - assertThat(data.name).isEqualTo(DEVICE_NAME) - } - - @Test - fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress_withNameFix() { - featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) - manager.onMediaDataLoaded(KEY, null, mediaData) - // Run and reset the executors and listeners so we only focus on new events. - fakeBgExecutor.runAllReady() - fakeFgExecutor.runAllReady() - reset(listener) - - // Ensure we'll get device info when using the address - val fullMediaDevice = mock(MediaDevice::class.java) - val address = "fakeAddress" - val nameFromDevice = "nameFromDevice" - val iconFromDevice = mock(Drawable::class.java) - whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice) - whenever(fullMediaDevice.name).thenReturn(nameFromDevice) - whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice) - - // WHEN the about-to-connect device changes to non-null - val deviceCallback = captureCallback() - val nameFromParam = "nameFromParam" - val iconFromParam = mock(Drawable::class.java) - deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam) - assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) - - // THEN the about-to-connect device based on the address is returned - val data = captureDeviceData(KEY) - assertThat(data.enabled).isTrue() - assertThat(data.name).isEqualTo(nameFromDevice) - assertThat(data.name).isNotEqualTo(nameFromParam) - assertThat(data.icon).isEqualTo(iconFromDevice) - assertThat(data.icon).isNotEqualTo(iconFromParam) - } - - @Test - fun deviceNameFromMR2RouteInfo_withNameFix() { - featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) - // GIVEN that MR2Manager returns a valid routing session - whenever(route.name).thenReturn(REMOTE_DEVICE_NAME) - // WHEN a notification is added - manager.onMediaDataLoaded(KEY, null, mediaData) - fakeBgExecutor.runAllReady() - fakeFgExecutor.runAllReady() - // THEN it uses the route name (instead of device name) - val data = captureDeviceData(KEY) - assertThat(data.enabled).isTrue() - assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) - } - - @Test - fun deviceDisabledWhenMR2ReturnsNullRouteInfo_withNameFix() { - featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) - // GIVEN that MR2Manager returns null for routing session - whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) - // WHEN a notification is added - manager.onMediaDataLoaded(KEY, null, mediaData) - fakeBgExecutor.runAllReady() - fakeFgExecutor.runAllReady() - // THEN the device is disabled and name is set to null - val data = captureDeviceData(KEY) - assertThat(data.enabled).isFalse() - assertThat(data.name).isNull() - } - - @Test - fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName_withNameFix() { - featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) - // GIVEN that MR2Manager returns a routing session that does not have a name - whenever(route.name).thenReturn(null) - whenever(route.isSystemSession).thenReturn(false) - // WHEN a notification is added - manager.onMediaDataLoaded(KEY, null, mediaData) - fakeBgExecutor.runAllReady() - fakeFgExecutor.runAllReady() - // THEN the device is enabled and uses the current connected device name - val data = captureDeviceData(KEY) - assertThat(data.name).isEqualTo(DEVICE_NAME) - assertThat(data.enabled).isTrue() - } - - // End duplicate tests - private fun captureCallback(): LocalMediaManager.DeviceCallback { val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java) verify(lmm).registerCallback(captor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt index b5e7e2c14211..92c2d743c262 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt @@ -130,7 +130,7 @@ class QSTileLoggerTest : SysuiTestCase() { "sd=null, " + "svi=None, " + "enabled=ENABLED, " + - "a11y=null" + + "a11y=android.widget.Switch" + "], " + "data=test_data" ) @@ -154,7 +154,7 @@ class QSTileLoggerTest : SysuiTestCase() { "sd=null, " + "svi=None, " + "enabled=ENABLED, " + - "a11y=null], " + + "a11y=android.widget.Switch], " + "data=test_data" ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt index 3b6bfeeb4ca2..3808c7ee926b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt @@ -32,6 +32,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.res.R import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test @@ -67,17 +72,24 @@ class BluetoothTileDialogTest : SysuiTestCase() { private val fakeSystemClock = FakeSystemClock() + private lateinit var scheduler: TestCoroutineScheduler + private lateinit var dispatcher: CoroutineDispatcher + private lateinit var testScope: TestScope private lateinit var icon: Pair<Drawable, String> private lateinit var bluetoothTileDialog: BluetoothTileDialog private lateinit var deviceItem: DeviceItem @Before fun setUp() { + scheduler = TestCoroutineScheduler() + dispatcher = UnconfinedTestDispatcher(scheduler) + testScope = TestScope(dispatcher) bluetoothTileDialog = BluetoothTileDialog( ENABLED, subtitleResId, bluetoothTileDialogCallback, + dispatcher, fakeSystemClock, uiEventLogger, logger, @@ -111,29 +123,33 @@ class BluetoothTileDialogTest : SysuiTestCase() { @Test fun testShowDialog_displayBluetoothDevice() { - bluetoothTileDialog = - BluetoothTileDialog( - ENABLED, - subtitleResId, - bluetoothTileDialogCallback, - fakeSystemClock, - uiEventLogger, - logger, - mContext + testScope.runTest { + bluetoothTileDialog = + BluetoothTileDialog( + ENABLED, + subtitleResId, + bluetoothTileDialogCallback, + dispatcher, + fakeSystemClock, + uiEventLogger, + logger, + mContext + ) + bluetoothTileDialog.show() + fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE) + bluetoothTileDialog.onDeviceItemUpdated( + listOf(deviceItem), + showSeeAll = false, + showPairNewDevice = false ) - bluetoothTileDialog.show() - bluetoothTileDialog.onDeviceItemUpdated( - listOf(deviceItem), - showSeeAll = false, - showPairNewDevice = false - ) - val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) - val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter - assertThat(adapter.itemCount).isEqualTo(1) - assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME) - assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY) - assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon) + val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) + val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter + assertThat(adapter.itemCount).isEqualTo(1) + assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME) + assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY) + assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon) + } } @Test @@ -147,6 +163,7 @@ class BluetoothTileDialogTest : SysuiTestCase() { ENABLED, subtitleResId, bluetoothTileDialogCallback, + dispatcher, fakeSystemClock, uiEventLogger, logger, @@ -173,6 +190,7 @@ class BluetoothTileDialogTest : SysuiTestCase() { ENABLED, subtitleResId, bluetoothTileDialogCallback, + dispatcher, fakeSystemClock, uiEventLogger, logger, @@ -190,33 +208,37 @@ class BluetoothTileDialogTest : SysuiTestCase() { @Test fun testOnDeviceUpdated_hideSeeAll_showPairNew() { - bluetoothTileDialog = - BluetoothTileDialog( - ENABLED, - subtitleResId, - bluetoothTileDialogCallback, - fakeSystemClock, - uiEventLogger, - logger, - mContext + testScope.runTest { + bluetoothTileDialog = + BluetoothTileDialog( + ENABLED, + subtitleResId, + bluetoothTileDialogCallback, + dispatcher, + fakeSystemClock, + uiEventLogger, + logger, + mContext + ) + bluetoothTileDialog.show() + fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE) + bluetoothTileDialog.onDeviceItemUpdated( + listOf(deviceItem), + showSeeAll = false, + showPairNewDevice = true ) - bluetoothTileDialog.show() - bluetoothTileDialog.onDeviceItemUpdated( - listOf(deviceItem), - showSeeAll = false, - showPairNewDevice = true - ) - - val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group) - val pairNewLayout = - bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group) - val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) - val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter - assertThat(seeAllLayout).isNotNull() - assertThat(seeAllLayout.visibility).isEqualTo(GONE) - assertThat(pairNewLayout).isNotNull() - assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE) - assertThat(adapter.itemCount).isEqualTo(1) + val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group) + val pairNewLayout = + bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group) + val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) + val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter + + assertThat(seeAllLayout).isNotNull() + assertThat(seeAllLayout.visibility).isEqualTo(GONE) + assertThat(pairNewLayout).isNotNull() + assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE) + assertThat(adapter.itemCount).isEqualTo(1) + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt index 682b2d0d3983..5eca8caa7d15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt @@ -19,7 +19,9 @@ package com.android.systemui.qs.tiles.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -29,8 +31,8 @@ import org.junit.runner.RunWith class QSTileConfigProviderTest : SysuiTestCase() { private val underTest = - QSTileConfigProviderImpl( - mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = VALID_SPEC }) + createQSTileConfigProviderImpl( + mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = VALID_SPEC }), ) @Test @@ -43,13 +45,31 @@ class QSTileConfigProviderTest : SysuiTestCase() { underTest.getConfig(INVALID_SPEC.spec) } + @Test + fun hasConfigReturnsTrueForValidSpec() { + assertThat(underTest.hasConfig(VALID_SPEC.spec)).isTrue() + } + + @Test + fun hasConfigReturnsFalseForInvalidSpec() { + assertThat(underTest.hasConfig(INVALID_SPEC.spec)).isFalse() + } + @Test(expected = IllegalArgumentException::class) fun validatesSpecUponCreation() { - QSTileConfigProviderImpl( + createQSTileConfigProviderImpl( mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = INVALID_SPEC }) ) } + private fun createQSTileConfigProviderImpl( + configs: Map<String, QSTileConfig> + ): QSTileConfigProviderImpl = + QSTileConfigProviderImpl( + configs, + mock<QsEventLogger>(), + ) + private companion object { val VALID_SPEC = TileSpec.create("valid_tile_spec") diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt deleted file mode 100644 index d3b7daad792e..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt +++ /dev/null @@ -1,120 +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.qs.tiles.viewmodel - -import android.os.UserHandle -import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.MediumTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.classifier.FalsingManagerFake -import com.android.systemui.common.shared.model.ContentDescription -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics -import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor -import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor -import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor -import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper -import com.android.systemui.qs.tiles.base.logging.QSTileLogger -import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl -import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.util.time.FakeSystemClock -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.launchIn -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.mockito.Mock -import org.mockito.MockitoAnnotations - -// TODO(b/299909368): Add more tests -@MediumTest -@RunWith(AndroidJUnit4::class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { - - @Mock private lateinit var qsTileLogger: QSTileLogger - @Mock private lateinit var qsTileAnalytics: QSTileAnalytics - - private val fakeUserRepository = FakeUserRepository() - private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>() - private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>() - private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor() - private val fakeFalsingManager = FalsingManagerFake() - - private val testCoroutineDispatcher = StandardTestDispatcher() - private val testScope = TestScope(testCoroutineDispatcher) - - private lateinit var underTest: QSTileViewModel - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - underTest = createViewModel(testScope) - } - - @Test - fun testDoesntListenStateUntilCreated() = - testScope.runTest { - assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty() - - assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty() - - underTest.state.launchIn(backgroundScope) - runCurrent() - - assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty() - assertThat(fakeQSTileDataInteractor.dataRequests.first()) - .isEqualTo(FakeQSTileDataInteractor.DataRequest(UserHandle.of(0))) - } - - private fun createViewModel( - scope: TestScope, - config: QSTileConfig = TEST_QS_TILE_CONFIG, - ): QSTileViewModel = - QSTileViewModelImpl( - config, - { fakeQSTileUserActionInteractor }, - { fakeQSTileDataInteractor }, - { - object : QSTileDataToStateMapper<Any> { - override fun map(config: QSTileConfig, data: Any): QSTileState = - QSTileState.build( - { Icon.Resource(0, ContentDescription.Resource(0)) }, - "" - ) {} - } - }, - fakeDisabledByPolicyInteractor, - fakeUserRepository, - fakeFalsingManager, - qsTileAnalytics, - qsTileLogger, - FakeSystemClock(), - testCoroutineDispatcher, - scope.backgroundScope, - ) - - private companion object { - - val TEST_QS_TILE_CONFIG = QSTileConfigTestBuilder.build {} - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt new file mode 100644 index 000000000000..3a0ebdbd6a17 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +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.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@MediumTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class QSTileViewModelTest : SysuiTestCase() { + + @Mock private lateinit var qsTileLogger: QSTileLogger + @Mock private lateinit var qsTileAnalytics: QSTileAnalytics + + private val tileConfig = + QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") } + + private val userRepository = FakeUserRepository() + private val tileDataInteractor = FakeQSTileDataInteractor<String>() + private val tileUserActionInteractor = FakeQSTileUserActionInteractor<String>() + private val disabledByPolicyInteractor = FakeDisabledByPolicyInteractor() + private val falsingManager = FalsingManagerFake() + + private val testCoroutineDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testCoroutineDispatcher) + + private lateinit var underTest: QSTileViewModel + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = createViewModel(testScope) + } + + @Test + fun stateReceivedForTheData() = + testScope.runTest { + val testTileData = "test_tile_data" + val states = collectValues(underTest.state) + runCurrent() + + tileDataInteractor.emitData(testTileData) + runCurrent() + + assertThat(states()).isNotEmpty() + assertThat(states().first().label).isEqualTo(testTileData) + verify(qsTileLogger).logInitialRequest(eq(tileConfig.tileSpec)) + } + + @Test + fun doesntListenDataIfStateIsntListened() = + testScope.runTest { + assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(0) + + underTest.state.launchIn(backgroundScope) + runCurrent() + + assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(1) + } + + @Test + fun doesntListenAvailabilityIfAvailabilityIsntListened() = + testScope.runTest { + assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(0) + + underTest.isAvailable.launchIn(backgroundScope) + runCurrent() + + assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(1) + } + + @Test + fun doesntListedDataAfterDestroy() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + underTest.isAvailable.launchIn(backgroundScope) + runCurrent() + + underTest.destroy() + runCurrent() + + assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(0) + assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(0) + } + + @Test + fun forceUpdateTriggersData() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + runCurrent() + + underTest.forceUpdate() + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isInstanceOf(DataUpdateTrigger.ForceUpdate::class.java) + verify(qsTileLogger).logForceUpdate(eq(tileConfig.tileSpec)) + } + + @Test + fun userChangeUpdatesData() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + runCurrent() + + underTest.onUserChanged(USER) + runCurrent() + + assertThat(tileDataInteractor.dataRequests.last()) + .isEqualTo(FakeQSTileDataInteractor.DataRequest(USER)) + } + + @Test + fun userChangeUpdatesAvailability() = + testScope.runTest { + underTest.isAvailable.launchIn(backgroundScope) + runCurrent() + + underTest.onUserChanged(USER) + runCurrent() + + assertThat(tileDataInteractor.availabilityRequests.last()) + .isEqualTo(FakeQSTileDataInteractor.AvailabilityRequest(USER)) + } + + private fun createViewModel( + scope: TestScope, + config: QSTileConfig = tileConfig, + ): QSTileViewModel = + QSTileViewModelImpl( + config, + { tileUserActionInteractor }, + { tileDataInteractor }, + { + object : QSTileDataToStateMapper<String> { + override fun map(config: QSTileConfig, data: String): QSTileState = + QSTileState.build( + { Icon.Resource(0, ContentDescription.Resource(0)) }, + data + ) {} + } + }, + disabledByPolicyInteractor, + userRepository, + falsingManager, + qsTileAnalytics, + qsTileLogger, + FakeSystemClock(), + testCoroutineDispatcher, + scope.backgroundScope, + ) + + private companion object { + + val USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt new file mode 100644 index 000000000000..ea8acc714f3a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import androidx.test.filters.MediumTest +import com.android.settingslib.RestrictedLockUtils +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +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.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +/** Tests all possible [QSTileUserAction]s. If you need */ +@MediumTest +@RunWith(Parameterized::class) +@OptIn(ExperimentalCoroutinesApi::class) +class QSTileViewModelUserInputTest : SysuiTestCase() { + + @Mock private lateinit var qsTileLogger: QSTileLogger + @Mock private lateinit var qsTileAnalytics: QSTileAnalytics + + @Parameter lateinit var userAction: QSTileUserAction + + private val tileConfig = + QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") } + + private val userRepository = FakeUserRepository() + private val tileDataInteractor = FakeQSTileDataInteractor<String>() + private val tileUserActionInteractor = FakeQSTileUserActionInteractor<String>() + private val disabledByPolicyInteractor = FakeDisabledByPolicyInteractor() + private val falsingManager = FalsingManagerFake() + + private val testCoroutineDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testCoroutineDispatcher) + + private lateinit var underTest: QSTileViewModel + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = createViewModel(testScope) + } + + @Test + fun userInputTriggersData() = + testScope.runTest { + tileDataInteractor.emitData("initial_data") + underTest.state.launchIn(backgroundScope) + runCurrent() + + underTest.onActionPerformed(userAction) + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isInstanceOf(DataUpdateTrigger.UserInput::class.java) + verify(qsTileLogger) + .logUserAction(eq(userAction), eq(tileConfig.tileSpec), eq(true), eq(true)) + verify(qsTileLogger) + .logUserActionPipeline( + eq(tileConfig.tileSpec), + eq(userAction), + any(), + eq("initial_data") + ) + verify(qsTileAnalytics).trackUserAction(eq(tileConfig), eq(userAction)) + } + + @Test + fun disabledByPolicyUserInputIsSkipped() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + disabledByPolicyInteractor.policyResult = + DisabledByPolicyInteractor.PolicyResult.TileDisabled( + RestrictedLockUtils.EnforcedAdmin() + ) + runCurrent() + + underTest.onActionPerformed(userAction) + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java) + verify(qsTileLogger) + .logUserActionRejectedByPolicy(eq(userAction), eq(tileConfig.tileSpec)) + verify(qsTileAnalytics, never()).trackUserAction(any(), any()) + } + + @Test + fun falsedUserInputIsSkipped() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + falsingManager.setFalseLongTap(true) + falsingManager.setFalseTap(true) + runCurrent() + + underTest.onActionPerformed(userAction) + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java) + verify(qsTileLogger) + .logUserActionRejectedByFalsing(eq(userAction), eq(tileConfig.tileSpec)) + verify(qsTileAnalytics, never()).trackUserAction(any(), any()) + } + + @Test + fun userInputIsThrottled() = + testScope.runTest { + val inputCount = 100 + underTest.state.launchIn(backgroundScope) + + repeat(inputCount) { underTest.onActionPerformed(userAction) } + runCurrent() + + assertThat(tileDataInteractor.triggers.size).isLessThan(inputCount) + } + + private fun createViewModel(scope: TestScope): QSTileViewModel = + QSTileViewModelImpl( + tileConfig, + { tileUserActionInteractor }, + { tileDataInteractor }, + { + object : QSTileDataToStateMapper<String> { + override fun map(config: QSTileConfig, data: String): QSTileState = + QSTileState.build( + { Icon.Resource(0, ContentDescription.Resource(0)) }, + data + ) {} + } + }, + disabledByPolicyInteractor, + userRepository, + falsingManager, + qsTileAnalytics, + qsTileLogger, + FakeSystemClock(), + testCoroutineDispatcher, + scope.backgroundScope, + ) + + companion object { + + @JvmStatic + @Parameterized.Parameters + fun data(): Iterable<QSTileUserAction> = + listOf( + QSTileUserAction.Click(null), + QSTileUserAction.LongClick(null), + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index a9f82392c7e5..d582b9e8da5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.qs.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.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags @@ -90,13 +90,8 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { underTest = QuickSettingsSceneViewModel( - bouncerInteractor = - utils.bouncerInteractor( - deviceEntryInteractor = - utils.deviceEntryInteractor( - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), + deviceEntryInteractor = + utils.deviceEntryInteractor( authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ), diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index e84d274b4763..d1db9c19cd2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -26,7 +26,7 @@ import com.android.internal.R import com.android.internal.util.EmergencyAffordanceManager import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel @@ -39,7 +39,6 @@ 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.Companion.toDataLayer import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey @@ -60,6 +59,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage +import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -72,7 +72,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -135,9 +134,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val bouncerInteractor = utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, ) private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository @@ -234,7 +231,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ShadeSceneViewModel( applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, - bouncerInteractor = bouncerInteractor, shadeHeaderViewModel = shadeHeaderViewModel, ) @@ -247,7 +243,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, sceneInteractor = sceneInteractor, deviceEntryInteractor = deviceEntryInteractor, - authenticationInteractor = authenticationInteractor, keyguardInteractor = keyguardInteractor, flags = utils.sceneContainerFlags, sysUiState = sysUiState, @@ -255,6 +250,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneLogger = mock(), falsingCollector = utils.falsingCollector(), powerInteractor = powerInteractor, + bouncerInteractor = bouncerInteractor, ) startable.start() @@ -298,7 +294,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun swipeUpOnLockscreen_withAuthMethodSwipe_dismissesLockscreen() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe) + setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) @@ -312,7 +308,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = testScope.runTest { val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey) - setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe) + setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) assertCurrentScene(SceneKey.Lockscreen) // Emulate a user swipe to the shade scene. @@ -329,7 +325,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() = testScope.runTest { val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey) - setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe) + setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) + assertTrue(deviceEntryInteractor.canSwipeToEnter.value) assertCurrentScene(SceneKey.Lockscreen) // Emulate a user swipe to dismiss the lockscreen. @@ -349,7 +346,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun withAuthMethodNone_deviceWakeUp_skipsLockscreen() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.None) + setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = false) putDeviceToSleep(instantlyLockDevice = false) assertCurrentScene(SceneKey.Lockscreen) @@ -360,7 +357,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe) + setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) putDeviceToSleep(instantlyLockDevice = false) assertCurrentScene(SceneKey.Lockscreen) @@ -428,7 +425,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.Password) + setAuthMethod(AuthenticationMethodModel.Password) val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer) @@ -445,7 +442,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun bouncerActionButtonClick_opensEmergencyServicesDialer() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.Password) + setAuthMethod(AuthenticationMethodModel.Password) val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer) @@ -464,7 +461,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun bouncerActionButtonClick_duringCall_returnsToCall() = testScope.runTest { - setAuthMethod(DomainLayerAuthenticationMethodModel.Password) + setAuthMethod(AuthenticationMethodModel.Password) startPhoneCall() val upDestinationSceneKey by collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) @@ -508,19 +505,19 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { /** Updates the current authentication method and related states in the data layer. */ private fun TestScope.setAuthMethod( - authMethod: DomainLayerAuthenticationMethodModel, + authMethod: AuthenticationMethodModel, + enableLockscreen: Boolean = true ) { + if (authMethod.isSecure) { + assert(enableLockscreen) { + "Lockscreen cannot be disabled with a secure authentication method." + } + } // 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.setInsecureLockscreenEnabled( - authMethod !is DomainLayerAuthenticationMethodModel.None - ) - utils.authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer()) - if (!authMethod.isSecure) { - // When the auth method is not secure, the device is never considered locked. - utils.deviceEntryRepository.setUnlocked(true) - } + utils.deviceEntryRepository.setLockscreenEnabled(enableLockscreen) + utils.authenticationRepository.setAuthenticationMethod(authMethod) runCurrent() } @@ -648,6 +645,9 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { emulateUserDrivenTransition(SceneKey.Bouncer) enterPin() + // This repository state is not changed by the AuthInteractor, it relies on + // KeyguardStateController. + utils.deviceEntryRepository.setUnlocked(true) emulateUiSceneTransition( expectedVisible = false, ) @@ -707,7 +707,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } /** Emulates the dismissal of the IME (soft keyboard). */ - private fun TestScope.dismissIme( + private suspend fun TestScope.dismissIme( showImeBeforeDismissing: Boolean = true, ) { bouncerViewModel.authMethodViewModel.value?.apply { diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index f6362fe528ed..2f654e22aec6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -24,16 +24,15 @@ 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.domain.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testScope +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository 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.SceneTestUtils.Companion.toDataLayer import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -63,8 +62,12 @@ class SceneContainerStartableTest : SysuiTestCase() { private val sceneInteractor = utils.sceneInteractor() private val sceneContainerFlags = utils.sceneContainerFlags private val authenticationInteractor = utils.authenticationInteractor() + private val bouncerInteractor = + utils.bouncerInteractor(authenticationInteractor = authenticationInteractor) + private val faceAuthRepository = FakeDeviceEntryFaceAuthRepository() private val deviceEntryInteractor = utils.deviceEntryInteractor( + faceAuthRepository = faceAuthRepository, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, ) @@ -78,7 +81,6 @@ class SceneContainerStartableTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, sceneInteractor = sceneInteractor, deviceEntryInteractor = deviceEntryInteractor, - authenticationInteractor = authenticationInteractor, keyguardInteractor = keyguardInteractor, flags = sceneContainerFlags, sysUiState = sysUiState, @@ -86,6 +88,7 @@ class SceneContainerStartableTest : SysuiTestCase() { sceneLogger = mock(), falsingCollector = falsingCollector, powerInteractor = powerInteractor, + bouncerInteractor = bouncerInteractor, ) @Before @@ -198,12 +201,55 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() + // Authenticate using a passive auth method like face auth while bypass is disabled. + faceAuthRepository.isAuthenticated.value = true utils.deviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @Test + fun stayOnCurrentSceneWhenDeviceIsUnlockedAndUserIsNotOnLockscreen() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + val transitionStateFlowValue = + prepareState( + isBypassEnabled = true, + authenticationMethod = AuthenticationMethodModel.Pin, + initialSceneKey = SceneKey.Lockscreen, + ) + underTest.start() + runCurrent() + + sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "switch to shade") + transitionStateFlowValue.value = ObservableTransitionState.Idle(SceneKey.Shade) + assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + + utils.deviceEntryRepository.setUnlocked(true) + runCurrent() + + assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) + } + + @Test + fun switchToGoneWhenDeviceIsUnlockedAndUserIsOnBouncerWithBypassDisabled() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + prepareState( + isBypassEnabled = false, + initialSceneKey = SceneKey.Bouncer, + ) + assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) + underTest.start() + + // Authenticate using a passive auth method like face auth while bypass is disabled. + faceAuthRepository.isAuthenticated.value = true + utils.deviceEntryRepository.setUnlocked(true) + + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + } + + @Test fun switchToLockscreenWhenDeviceSleepsLocked() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) @@ -255,6 +301,7 @@ class SceneContainerStartableTest : SysuiTestCase() { prepareState( initialSceneKey = SceneKey.Lockscreen, authenticationMethod = AuthenticationMethodModel.None, + isLockscreenEnabled = false, ) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() @@ -269,7 +316,8 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( initialSceneKey = SceneKey.Lockscreen, - authenticationMethod = AuthenticationMethodModel.Swipe, + authenticationMethod = AuthenticationMethodModel.None, + isLockscreenEnabled = true, ) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() @@ -406,6 +454,24 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + fun bouncerImeHidden_shouldTransitionBackToLockscreen() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + prepareState( + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Password, + isDeviceUnlocked = false, + ) + underTest.start() + runCurrent() + + bouncerInteractor.onImeHidden() + runCurrent() + + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + } + + @Test fun collectFalsingSignals_screenOnAndOff_aodUnavailable() = testScope.runTest { utils.keyguardRepository.setAodAvailable(false) @@ -526,8 +592,14 @@ class SceneContainerStartableTest : SysuiTestCase() { isBypassEnabled: Boolean = false, initialSceneKey: SceneKey? = null, authenticationMethod: AuthenticationMethodModel? = null, + isLockscreenEnabled: Boolean = true, startsAwake: Boolean = true, ): MutableStateFlow<ObservableTransitionState> { + if (authenticationMethod?.isSecure == true) { + assert(isLockscreenEnabled) { + "Lockscreen cannot be disabled while having a secure authentication method" + } + } sceneContainerFlags.enabled = true utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked) utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled) @@ -542,11 +614,9 @@ class SceneContainerStartableTest : SysuiTestCase() { sceneInteractor.onSceneChanged(SceneModel(it), "reason") } authenticationMethod?.let { - utils.authenticationRepository.setAuthenticationMethod( - authenticationMethod.toDataLayer() - ) - utils.deviceEntryRepository.setInsecureLockscreenEnabled( - authenticationMethod != AuthenticationMethodModel.None + utils.authenticationRepository.setAuthenticationMethod(authenticationMethod) + utils.deviceEntryRepository.setLockscreenEnabled( + isLockscreenEnabled = isLockscreenEnabled ) } if (startsAwake) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt index 4c8b56227efa..5969bd82c361 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.flags.ReleasedFlag import com.android.systemui.flags.ResourceBooleanFlag import com.android.systemui.flags.UnreleasedFlag +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule @@ -68,6 +69,7 @@ internal class SceneContainerFlagsTest( listOf( AconfigFlags.FLAG_SCENE_CONTAINER, AconfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, + KeyguardShadeMigrationNssl.FLAG_NAME, ) .forEach { flagToken -> setFlagsRule.enableFlags(flagToken) 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 f4c05e0272eb..0f9000768783 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -100,6 +100,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteracto import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; @@ -371,12 +372,14 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false); mFeatureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false); - mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false); mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false); - mFeatureFlags.set(Flags.MIGRATE_NSSL, false); mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false); mFeatureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false); mFeatureFlags.set(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT, false); + + mSetFlagsRule.disableFlags(KeyguardShadeMigrationNssl.FLAG_NAME); + mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); + mMainDispatcher = getMainDispatcher(); KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps = KeyguardInteractorFactory.create(); @@ -423,7 +426,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mDozeParameters, mScreenOffAnimationController, mKeyguardLogger, - mFeatureFlags, mInteractionJankMonitor, mKeyguardInteractor, mKeyguardTransitionInteractor, @@ -770,7 +772,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mAccessibilityManager, mLockscreenGestureLogger, mMetricsLogger, - mFeatureFlags, mInteractionJankMonitor, mShadeLog, mDumpManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index d5737643e0d4..722fb2c75081 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -59,6 +59,7 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.FaceAuthApiRequestReason; import com.android.systemui.DejankUtils; import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; @@ -1105,7 +1106,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test public void nsslFlagEnabled_allowOnlyExternalTouches() { - mFeatureFlags.set(Flags.MIGRATE_NSSL, true); + mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME); // This sets the dozing state that is read when onMiddleClicked is eventually invoked. mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 5459779121c9..2dd0af78cf17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -53,7 +53,6 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags.ALTERNATE_BOUNCER_VIEW import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED -import com.android.systemui.flags.Flags.MIGRATE_NSSL import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON @@ -67,6 +66,7 @@ import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepo import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger @@ -198,7 +198,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true) featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) - featureFlagsClassic.set(MIGRATE_NSSL, false) featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false) mCommunalRepository = FakeCommunalRepository() @@ -468,7 +467,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // AND the lock icon wants the touch whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true) - featureFlagsClassic.set(MIGRATE_NSSL, true) + mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse() @@ -487,7 +486,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) .thenReturn(false) - featureFlagsClassic.set(MIGRATE_NSSL, true) + mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() @@ -506,7 +505,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) .thenReturn(true) - featureFlagsClassic.set(MIGRATE_NSSL, true) + mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index a6ab6a51bfa7..4b6290619192 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -192,7 +192,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) - featureFlags.set(Flags.MIGRATE_NSSL, false) featureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false) testScope = TestScope() controller = diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt index 778cfa67cad7..88a47eb81bde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt @@ -65,7 +65,10 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -/** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */ +/** + * Uses Flags.KEYGUARD_STATUS_VIEW_MIGRATE_NSSL set to false. If all goes well, this set of tests + * will be deleted. + */ @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest @@ -101,11 +104,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) fakeSystemClock = FakeSystemClock() delayableExecutor = FakeExecutor(fakeSystemClock) - featureFlags = - FakeFeatureFlags().apply { - set(Flags.MIGRATE_NSSL, false) - set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false) - } + featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false) } mContext.ensureTestableResources() whenever(view.context).thenReturn(mContext) whenever(view.resources).thenReturn(mContext.resources) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt index 23420037e233..1f37ca09dc26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.fragments.FragmentService +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener import com.android.systemui.plugins.qs.QS @@ -100,11 +101,8 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) fakeSystemClock = FakeSystemClock() delayableExecutor = FakeExecutor(fakeSystemClock) - featureFlags = - FakeFeatureFlags().apply { - set(Flags.MIGRATE_NSSL, true) - set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true) - } + mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) + featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true) } mContext.ensureTestableResources() whenever(view.context).thenReturn(mContext) whenever(view.resources).thenReturn(mContext.resources) 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 6d0488706133..d050c856c2e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -355,7 +355,6 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { mAccessibilityManager, mLockscreenGestureLogger, mMetricsLogger, - mFeatureFlags, mInteractionJankMonitor, mShadeLogger, mDumpManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt index e920687753fd..20b19fd16f4f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt @@ -158,6 +158,15 @@ class ShadeRepositoryImplTest : SysuiTestCase() { } @Test + fun updateLegacyLockscreenShadeTracking() = + testScope.runTest { + assertThat(underTest.legacyLockscreenShadeTracking.value).isEqualTo(false) + + underTest.setLegacyLockscreenShadeTracking(true) + assertThat(underTest.legacyLockscreenShadeTracking.value).isEqualTo(true) + } + + @Test fun updateLegacyQsTracking() = testScope.runTest { assertThat(underTest.legacyQsTracking.value).isEqualTo(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 589f9aeba19f..fa849fe806c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -19,7 +19,7 @@ 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.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags @@ -96,12 +96,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() { ShadeSceneViewModel( applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, - bouncerInteractor = - utils.bouncerInteractor( - deviceEntryInteractor = deviceEntryInteractor, - authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, - ), shadeHeaderViewModel = shadeHeaderViewModel, ) } @@ -130,7 +124,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setLockscreenEnabled(true) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason") @@ -142,7 +136,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.deviceEntryRepository.setInsecureLockscreenEnabled(true) + utils.deviceEntryRepository.setLockscreenEnabled(true) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason") diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt index b8fe2f911b50..cb83e7c7adbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt @@ -20,10 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.smartspace.config.BcSmartspaceConfigProvider -import com.android.systemui.util.mockito.whenever -import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -45,16 +42,7 @@ class BcSmartspaceConfigProviderTest : SysuiTestCase() { } @Test - fun isDefaultDateWeatherDisabled_flagIsTrue_returnsTrue() { - whenever(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) - + fun isDefaultDateWeatherDisabled_returnsTrue() { assertTrue(configProvider.isDefaultDateWeatherDisabled) } - - @Test - fun isDefaultDateWeatherDisabled_flagIsFalse_returnsFalse() { - whenever(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) - - assertFalse(configProvider.isDefaultDateWeatherDisabled) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index a1425f811a84..ae3214267ff5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -323,6 +323,20 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test + public void testCurrentUserPrivateNotificationsNullChannel() { + // GIVEN current user allows private notifications to show + mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, + mCurrentUser.id); + changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + + mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking()) + .setChannel(null) + .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); + // THEN the notification is not redacted + assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); + } + + @Test public void testWorkPrivateNotificationsRedacted() { // GIVEN work profile doesn't private notifications to show mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index 9036f22a792a..8440e00a89f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -38,7 +38,6 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceConfigPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin @@ -205,10 +204,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - // Todo(b/261760571): flip the flag value here when feature is launched, and update relevant - // tests. - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) - `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING)) .thenReturn(fakePrivateLockscreenSettingUri) `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING)) @@ -260,17 +255,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { deviceProvisionedListener = deviceProvisionedCaptor.value } - @Test(expected = RuntimeException::class) - fun testBuildAndConnectWeatherView_throwsIfDecouplingDisabled() { - // GIVEN the feature flag is disabled - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) - - // WHEN we try to build the view - controller.buildAndConnectWeatherView(fakeParent) - - // THEN an exception is thrown - } - @Test fun testBuildAndConnectView_connectsOnlyAfterDeviceIsProvisioned() { // GIVEN an unprovisioned device and an attempt to connect @@ -332,6 +316,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { clearInvocations(plugin) // WHEN the session is closed + controller.stateChangeListener.onViewDetachedFromWindow(dateSmartspaceView as View) + controller.stateChangeListener.onViewDetachedFromWindow(weatherSmartspaceView as View) controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View) controller.disconnect() @@ -376,20 +362,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { configChangeListener.onThemeChanged() // We update the new text color to match the wallpaper color - verify(smartspaceView).setPrimaryTextColor(anyInt()) - } - - @Test - fun testThemeChange_ifDecouplingEnabled_updatesTextColor() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) - - // GIVEN a connected smartspace session - connectSession() - - // WHEN the theme changes - configChangeListener.onThemeChanged() - - // We update the new text color to match the wallpaper color verify(dateSmartspaceView).setPrimaryTextColor(anyInt()) verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) verify(smartspaceView).setPrimaryTextColor(anyInt()) @@ -404,20 +376,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f) // We pass that along to the view - verify(smartspaceView).setDozeAmount(0.7f) - } - - @Test - fun testDozeAmountChange_ifDecouplingEnabled_updatesViews() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) - - // GIVEN a connected smartspace session - connectSession() - - // WHEN the doze amount changes - statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f) - - // We pass that along to the view verify(dateSmartspaceView).setDozeAmount(0.7f) verify(weatherSmartspaceView).setDozeAmount(0.7f) verify(smartspaceView).setDozeAmount(0.7f) @@ -472,7 +430,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testAllTargetsAreFilteredExceptWeatherWhenNotificationsAreDisabled() { + fun testAllTargetsAreFilteredInclWeatherWhenNotificationsAreDisabled() { // GIVEN the active user doesn't allow any notifications on lockscreen setShowNotifications(userHandlePrimary, false) connectSession() @@ -488,7 +446,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { sessionListener.onTargetsAvailable(targets) // THEN all non-sensitive content is still shown - verify(plugin).onTargetsAvailable(eq(listOf(targets[3]))) + verify(plugin).onTargetsAvailable(emptyList()) } @Test @@ -519,8 +477,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testSessionListener_ifDecouplingEnabled_weatherTargetIsFilteredOut() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) + fun testSessionListener_weatherTargetIsFilteredOut() { connectSession() // WHEN we receive a list of targets @@ -670,8 +627,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testSessionListener_ifDecouplingEnabled_weatherDataUpdates() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) + fun testSessionListener_weatherDataUpdates() { connectSession() clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) @@ -699,33 +655,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testSessionListener_ifDecouplingDisabled_weatherDataUpdates() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) - connectSession() - - clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) - // WHEN we receive a list of targets - val targets = listOf( - makeWeatherTargetWithExtras( - id = 1, - userHandle = userHandlePrimary, - description = "Sunny", - state = WeatherData.WeatherStateIcon.SUNNY.id, - temperature = "32", - useCelsius = false), - makeTarget(2, userHandlePrimary, isSensitive = true) - ) - - sessionListener.onTargetsAvailable(targets) - - verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> - w.description == "Sunny" && - w.state == WeatherData.WeatherStateIcon.SUNNY && - w.temperature == 32 && !w.useCelsius - }) - } - - @Test fun testSettingsAreReloaded() { // GIVEN a connected session where the privacy settings later flip to false connectSession() @@ -781,6 +710,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { connectSession() // WHEN we are told to cleanup + controller.stateChangeListener.onViewDetachedFromWindow(dateSmartspaceView as View) + controller.stateChangeListener.onViewDetachedFromWindow(weatherSmartspaceView as View) controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View) controller.disconnect() @@ -816,16 +747,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testWeatherViewUsesSameSession() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) - // GIVEN a connected session - connectSession() - - // No checks is needed here, since connectSession() already checks internally that - // createSmartspaceSession is invoked only once. - } - - @Test fun testViewGetInitializedWithBypassEnabledState() { // GIVEN keyguard bypass is enabled. `when`(keyguardBypassController.bypassEnabled).thenReturn(true) @@ -853,31 +774,29 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } private fun connectSession() { - if (controller.isDateWeatherDecoupled()) { - val dateView = controller.buildAndConnectDateView(fakeParent) - dateSmartspaceView = dateView as SmartspaceView - fakeParent.addView(dateView) - controller.stateChangeListener.onViewAttachedToWindow(dateView) - - verify(dateSmartspaceView).setUiSurface( - BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) - verify(dateSmartspaceView).registerDataProvider(datePlugin) - - verify(dateSmartspaceView).setPrimaryTextColor(anyInt()) - verify(dateSmartspaceView).setDozeAmount(0.5f) - - val weatherView = controller.buildAndConnectWeatherView(fakeParent) - weatherSmartspaceView = weatherView as SmartspaceView - fakeParent.addView(weatherView) - controller.stateChangeListener.onViewAttachedToWindow(weatherView) - - verify(weatherSmartspaceView).setUiSurface( - BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) - verify(weatherSmartspaceView).registerDataProvider(weatherPlugin) - - verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) - verify(weatherSmartspaceView).setDozeAmount(0.5f) - } + val dateView = controller.buildAndConnectDateView(fakeParent) + dateSmartspaceView = dateView as SmartspaceView + fakeParent.addView(dateView) + controller.stateChangeListener.onViewAttachedToWindow(dateView) + + verify(dateSmartspaceView).setUiSurface( + BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) + verify(dateSmartspaceView).registerDataProvider(datePlugin) + + verify(dateSmartspaceView).setPrimaryTextColor(anyInt()) + verify(dateSmartspaceView).setDozeAmount(0.5f) + + val weatherView = controller.buildAndConnectWeatherView(fakeParent) + weatherSmartspaceView = weatherView as SmartspaceView + fakeParent.addView(weatherView) + controller.stateChangeListener.onViewAttachedToWindow(weatherView) + + verify(weatherSmartspaceView).setUiSurface( + BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) + verify(weatherSmartspaceView).registerDataProvider(weatherPlugin) + + verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) + verify(weatherSmartspaceView).setDozeAmount(0.5f) val view = controller.buildAndConnectView(fakeParent) smartspaceView = view as SmartspaceView @@ -918,10 +837,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { verify(smartspaceView).setPrimaryTextColor(anyInt()) verify(smartspaceView).setDozeAmount(0.5f) - if (controller.isDateWeatherDecoupled()) { - clearInvocations(dateSmartspaceView) - clearInvocations(weatherSmartspaceView) - } + clearInvocations(dateSmartspaceView) + clearInvocations(weatherSmartspaceView) clearInvocations(smartspaceView) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index 1a04a3ea291a..87e9735394f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -389,4 +389,31 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1") assertThat(isolatedIcon?.isAnimating).isFalse() } + + @Test + fun isolatedIcon_updateWhenIconDataChanges() = + testComponent.runTest { + val icon: Icon = mock() + val isolatedIcon by collectLastValue(underTest.isolatedIcon) + runCurrent() + + headsUpViewStateRepository.isolatedNotification.value = "notif1" + runCurrent() + + activeNotificationsRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + groupKey = "group", + statusBarIcon = icon + ) + ) + } + .build() + runCurrent() + + assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1") + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt index 1d2055ec2e30..e1581eade4ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt @@ -44,7 +44,7 @@ class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecision statusBarStateController, keyguardStateController, headsUpManager, - logger, + oldLogger, mainHandler, flags, keyguardNotificationVisibilityProvider, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index 80d941a40cb5..1064475c744c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt @@ -32,10 +32,12 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro VisualInterruptionDecisionProviderImpl( ambientDisplayConfiguration, batteryController, + deviceProvisionedController, globalSettings, headsUpManager, keyguardNotificationVisibilityProvider, - logger, + keyguardStateController, + newLogger, mainHandler, powerManager, statusBarStateController, @@ -50,6 +52,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseNotSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -59,6 +62,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseNotSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -68,6 +72,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekSuppressed() assertPulseNotSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -77,6 +82,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekSuppressed() assertPulseNotSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -86,6 +92,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -95,6 +102,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseSuppressed() assertBubbleNotSuppressed() + assertFsiNotSuppressed() } } @@ -104,6 +112,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseNotSuppressed() assertBubbleSuppressed() + assertFsiNotSuppressed() } } @@ -113,6 +122,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertPeekNotSuppressed() assertPulseNotSuppressed() assertBubbleSuppressed() + assertFsiNotSuppressed() } } @@ -193,6 +203,10 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro assertShouldBubble(buildBubbleEntry()) } + private fun assertFsiNotSuppressed() { + forEachFsiState { assertShouldFsi(buildFsiEntry()) } + } + private fun withCondition(condition: VisualInterruptionCondition, block: () -> Unit) { provider.addCondition(condition) block() @@ -208,14 +222,14 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro private class TestCondition( types: Set<VisualInterruptionType>, val onShouldSuppress: () -> Boolean - ) : VisualInterruptionCondition(types = types, reason = "") { + ) : VisualInterruptionCondition(types = types, reason = "test condition") { override fun shouldSuppress(): Boolean = onShouldSuppress() } private class TestFilter( types: Set<VisualInterruptionType>, val onShouldSuppress: (NotificationEntry) -> Boolean = { true } - ) : VisualInterruptionFilter(types = types, reason = "") { + ) : VisualInterruptionFilter(types = types, reason = "test filter") { override fun shouldSuppress(entry: NotificationEntry) = onShouldSuppress(entry) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt index 7f12b22f2b4e..5e811561682e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt @@ -20,6 +20,9 @@ import android.app.ActivityManager import android.app.Notification import android.app.Notification.BubbleMetadata import android.app.Notification.FLAG_BUBBLE +import android.app.Notification.FLAG_FOREGROUND_SERVICE +import android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED +import android.app.Notification.FLAG_USER_INITIATED_JOB import android.app.Notification.GROUP_ALERT_ALL import android.app.Notification.GROUP_ALERT_CHILDREN import android.app.Notification.GROUP_ALERT_SUMMARY @@ -29,6 +32,7 @@ import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.IMPORTANCE_LOW import android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT +import android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT import android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE import android.app.PendingIntent @@ -45,6 +49,9 @@ import android.provider.Settings.Global.HEADS_UP_OFF import android.provider.Settings.Global.HEADS_UP_ON import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.statusbar.FakeStatusBarStateController @@ -56,36 +63,53 @@ import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS -import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.statusbar.policy.FakeDeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager -import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.settings.FakeGlobalSettings import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.utils.leaks.FakeBatteryController +import com.android.systemui.utils.leaks.FakeKeyguardStateController import com.android.systemui.utils.leaks.LeakCheckedTest import com.android.systemui.utils.os.FakeHandler import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.mockito.Mockito.`when` as whenever abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { + private val fakeLogBuffer = + LogBuffer( + name = "FakeLog", + maxSize = 1, + logcatEchoTracker = + object : LogcatEchoTracker { + override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = + true + + override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true + }, + systrace = false + ) + private val leakCheck = LeakCheckedTest.SysuiLeakCheck() protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context) protected val batteryController = FakeBatteryController(leakCheck) - protected val deviceProvisionedController: DeviceProvisionedController = mock() + protected val deviceProvisionedController = FakeDeviceProvisionedController() protected val flags: NotifPipelineFlags = mock() - protected val globalSettings = FakeGlobalSettings() + protected val globalSettings = + FakeGlobalSettings().also { it.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON) } protected val headsUpManager: HeadsUpManager = mock() protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider = mock() - protected val keyguardStateController: KeyguardStateController = mock() - protected val logger: NotificationInterruptLogger = mock() + protected val keyguardStateController = FakeKeyguardStateController(leakCheck) protected val mainHandler = FakeHandler(Looper.getMainLooper()) + protected val newLogger = VisualInterruptionDecisionLogger(fakeLogBuffer) + protected val oldLogger = NotificationInterruptLogger(fakeLogBuffer) protected val powerManager: PowerManager = mock() protected val statusBarStateController = FakeStatusBarStateController() protected val systemClock = FakeSystemClock() @@ -113,14 +137,9 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { @Before fun setUp() { - globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON) - val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0) userTracker.set(listOf(user), /* currentUserIndex = */ 0) - whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any())) - .thenReturn(false) - provider.start() } @@ -137,15 +156,18 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test - fun testShouldNotPeek_packageSnoozed() { + fun testShouldNotPeek_packageSnoozed_withoutFsi() { ensurePeekState { hunSnoozed = true } assertShouldNotHeadsUp(buildPeekEntry()) } @Test - fun testShouldPeek_packageSnoozedButFsi() { - ensurePeekState { hunSnoozed = true } - assertShouldHeadsUp(buildFsiEntry()) + fun testShouldPeek_packageSnoozed_withFsi() { + val entry = buildFsiEntry() + forEachPeekableFsiState { + ensurePeekState { hunSnoozed = true } + assertShouldHeadsUp(entry) + } } @Test @@ -197,82 +219,96 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test - fun testShouldPeek_notQuiteOldEnoughWhen() { + fun testShouldPeek_oldWhen_now() { + ensurePeekState() + assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(0) }) + } + + @Test + fun testShouldPeek_oldWhen_notOldEnough() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS - 1) }) } @Test - fun testShouldPeek_zeroWhen() { + fun testShouldPeek_oldWhen_zeroWhen() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = 0L }) } @Test - fun testShouldPeek_oldWhenButFsi() { + fun testShouldPeek_oldWhen_negativeWhen() { ensurePeekState() - assertShouldHeadsUp(buildFsiEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) }) + assertShouldHeadsUp(buildPeekEntry { whenMs = -1L }) } @Test - fun testShouldPeek_defaultLegacySuppressor() { + fun testShouldPeek_oldWhen_fullScreenIntent() { ensurePeekState() - provider.addLegacySuppressor(neverSuppresses) - assertShouldHeadsUp(buildPeekEntry()) + assertShouldHeadsUp(buildFsiEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) }) } @Test - fun testShouldNotPeek_legacySuppressInterruptions() { + fun testShouldPeek_oldWhen_foregroundService() { ensurePeekState() - provider.addLegacySuppressor(alwaysSuppressesInterruptions) - assertShouldNotHeadsUp(buildPeekEntry()) + assertShouldHeadsUp( + buildPeekEntry { + whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) + isForegroundService = true + } + ) } @Test - fun testShouldNotPeek_legacySuppressAwakeInterruptions() { + fun testShouldPeek_oldWhen_userInitiatedJob() { ensurePeekState() - provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions) - assertShouldNotHeadsUp(buildPeekEntry()) + assertShouldHeadsUp( + buildPeekEntry { + whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) + isUserInitiatedJob = true + } + ) } @Test - fun testShouldNotPeek_legacySuppressAwakeHeadsUp() { - ensurePeekState() - provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp) + fun testShouldNotPeek_hiddenOnKeyguard() { + ensurePeekState({ keyguardShouldHideNotification = true }) assertShouldNotHeadsUp(buildPeekEntry()) } @Test - fun testShouldPulse() { - ensurePulseState() - assertShouldHeadsUp(buildPulseEntry()) + fun testShouldPeek_defaultLegacySuppressor() { + ensurePeekState() + withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPeekEntry()) } } @Test - fun testShouldPulse_defaultLegacySuppressor() { - ensurePulseState() - provider.addLegacySuppressor(neverSuppresses) - assertShouldHeadsUp(buildPulseEntry()) + fun testShouldNotPeek_legacySuppressInterruptions() { + ensurePeekState() + withLegacySuppressor(alwaysSuppressesInterruptions) { + assertShouldNotHeadsUp(buildPeekEntry()) + } } @Test - fun testShouldNotPulse_legacySuppressInterruptions() { - ensurePulseState() - provider.addLegacySuppressor(alwaysSuppressesInterruptions) - assertShouldNotHeadsUp(buildPulseEntry()) + fun testShouldNotPeek_legacySuppressAwakeInterruptions() { + ensurePeekState() + withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { + assertShouldNotHeadsUp(buildPeekEntry()) + } } @Test - fun testShouldPulse_legacySuppressAwakeInterruptions() { - ensurePulseState() - provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions) - assertShouldHeadsUp(buildPulseEntry()) + fun testShouldNotPeek_legacySuppressAwakeHeadsUp() { + ensurePeekState() + withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { + assertShouldNotHeadsUp(buildPeekEntry()) + } } @Test - fun testShouldPulse_legacySuppressAwakeHeadsUp() { + fun testShouldPulse() { ensurePulseState() - provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp) assertShouldHeadsUp(buildPulseEntry()) } @@ -308,74 +344,66 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { assertShouldNotHeadsUp(buildPulseEntry { importance = IMPORTANCE_LOW }) } - private fun withPeekAndPulseEntry( - extendEntry: EntryBuilder.() -> Unit, - block: (NotificationEntry) -> Unit - ) { - ensurePeekState() - block(buildPeekEntry(extendEntry)) + @Test + fun testShouldNotPulse_hiddenOnKeyguard() { + ensurePulseState({ keyguardShouldHideNotification = true }) + assertShouldNotHeadsUp(buildPulseEntry()) + } + @Test + fun testShouldPulse_defaultLegacySuppressor() { ensurePulseState() - block(buildPulseEntry(extendEntry)) + withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) } } @Test - fun testShouldHeadsUp_groupedSummaryNotif_groupAlertAll() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_ALL - }) { - assertShouldHeadsUp(it) + fun testShouldNotPulse_legacySuppressInterruptions() { + ensurePulseState() + withLegacySuppressor(alwaysSuppressesInterruptions) { + assertShouldNotHeadsUp(buildPulseEntry()) } } @Test - fun testShouldHeadsUp_groupedSummaryNotif_groupAlertSummary() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_SUMMARY - }) { - assertShouldHeadsUp(it) + fun testShouldPulse_legacySuppressAwakeInterruptions() { + ensurePulseState() + withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { + assertShouldHeadsUp(buildPulseEntry()) } } @Test - fun testShouldNotHeadsUp_groupedSummaryNotif_groupAlertChildren() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_CHILDREN - }) { - assertShouldNotHeadsUp(it) + fun testShouldPulse_legacySuppressAwakeHeadsUp() { + ensurePulseState() + withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { + assertShouldHeadsUp(buildPulseEntry()) } } - @Test - fun testShouldHeadsUp_ungroupedSummaryNotif_groupAlertChildren() { - withPeekAndPulseEntry({ - isGrouped = false - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_CHILDREN - }) { - assertShouldHeadsUp(it) - } + private fun withPeekAndPulseEntry( + extendEntry: EntryBuilder.() -> Unit, + block: (NotificationEntry) -> Unit + ) { + ensurePeekState() + block(buildPeekEntry(extendEntry)) + + ensurePulseState() + block(buildPulseEntry(extendEntry)) } @Test - fun testShouldHeadsUp_groupedChildNotif_groupAlertAll() { + fun testShouldNotHeadsUp_suppressiveGroupAlertBehavior() { withPeekAndPulseEntry({ isGrouped = true isGroupSummary = false - groupAlertBehavior = GROUP_ALERT_ALL + groupAlertBehavior = GROUP_ALERT_SUMMARY }) { - assertShouldHeadsUp(it) + assertShouldNotHeadsUp(it) } } @Test - fun testShouldHeadsUp_groupedChildNotif_groupAlertChildren() { + fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notSuppressive() { withPeekAndPulseEntry({ isGrouped = true isGroupSummary = false @@ -386,18 +414,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test - fun testShouldNotHeadsUp_groupedChildNotif_groupAlertSummary() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = false - groupAlertBehavior = GROUP_ALERT_SUMMARY - }) { - assertShouldNotHeadsUp(it) - } - } - - @Test - fun testShouldHeadsUp_ungroupedChildNotif_groupAlertSummary() { + fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notGrouped() { withPeekAndPulseEntry({ isGrouped = false isGroupSummary = false @@ -425,58 +442,178 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test - fun testShouldNotBubble_notAllowed() { + fun testShouldBubble_suppressiveGroupAlertBehavior() { ensureBubbleState() - assertShouldNotBubble(buildBubbleEntry { canBubble = false }) + assertShouldBubble( + buildBubbleEntry { + isGrouped = true + isGroupSummary = false + groupAlertBehavior = GROUP_ALERT_SUMMARY + } + ) } @Test - fun testShouldNotBubble_noBubbleMetadata() { + fun testShouldNotBubble_notABubble() { + ensureBubbleState() + assertShouldNotBubble( + buildBubbleEntry { + isBubble = false + hasBubbleMetadata = false + } + ) + } + + @Test + fun testShouldNotBubble_missingBubbleMetadata() { ensureBubbleState() assertShouldNotBubble(buildBubbleEntry { hasBubbleMetadata = false }) } @Test + fun testShouldNotBubble_notAllowedToBubble() { + ensureBubbleState() + assertShouldNotBubble(buildBubbleEntry { canBubble = false }) + } + + @Test fun testShouldBubble_defaultLegacySuppressor() { ensureBubbleState() - provider.addLegacySuppressor(neverSuppresses) - assertShouldBubble(buildBubbleEntry()) + withLegacySuppressor(neverSuppresses) { assertShouldBubble(buildBubbleEntry()) } } @Test fun testShouldNotBubble_legacySuppressInterruptions() { ensureBubbleState() - provider.addLegacySuppressor(alwaysSuppressesInterruptions) - assertShouldNotBubble(buildBubbleEntry()) + withLegacySuppressor(alwaysSuppressesInterruptions) { + assertShouldNotBubble(buildBubbleEntry()) + } } @Test fun testShouldNotBubble_legacySuppressAwakeInterruptions() { ensureBubbleState() - provider.addLegacySuppressor(alwaysSuppressesAwakeInterruptions) - assertShouldNotBubble(buildBubbleEntry()) + withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { + assertShouldNotBubble(buildBubbleEntry()) + } } @Test fun testShouldBubble_legacySuppressAwakeHeadsUp() { ensureBubbleState() - provider.addLegacySuppressor(alwaysSuppressesAwakeHeadsUp) - assertShouldBubble(buildBubbleEntry()) + withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { + assertShouldBubble(buildBubbleEntry()) + } } @Test - fun testShouldNotAlert_hiddenOnKeyguard() { - ensurePeekState({ keyguardShouldHideNotification = true }) - assertShouldNotHeadsUp(buildPeekEntry()) - - ensurePulseState({ keyguardShouldHideNotification = true }) - assertShouldNotHeadsUp(buildPulseEntry()) - + fun testShouldNotBubble_hiddenOnKeyguard() { ensureBubbleState({ keyguardShouldHideNotification = true }) assertShouldNotBubble(buildBubbleEntry()) } @Test + fun testShouldNotFsi_noFullScreenIntent() { + forEachFsiState { assertShouldNotFsi(buildFsiEntry { hasFsi = false }) } + } + + @Test + fun testShouldNotFsi_showStickyHun() { + forEachFsiState { + assertShouldNotFsi( + buildFsiEntry { + hasFsi = false + isStickyAndNotDemoted = true + } + ) + } + } + + @Test + fun testShouldNotFsi_onlyDnd() { + forEachFsiState { + assertShouldNotFsi( + buildFsiEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT }, + expectWouldInterruptWithoutDnd = true + ) + } + } + + @Test + fun testShouldNotFsi_notImportantEnough() { + forEachFsiState { assertShouldNotFsi(buildFsiEntry { importance = IMPORTANCE_DEFAULT }) } + } + + @Test + fun testShouldNotFsi_notOnlyDnd() { + forEachFsiState { + assertShouldNotFsi( + buildFsiEntry { + suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT + importance = IMPORTANCE_DEFAULT + }, + expectWouldInterruptWithoutDnd = false + ) + } + } + + @Test + fun testShouldNotFsi_suppressiveGroupAlertBehavior() { + forEachFsiState { + assertShouldNotFsi( + buildFsiEntry { + isGrouped = true + isGroupSummary = true + groupAlertBehavior = GROUP_ALERT_CHILDREN + } + ) + } + } + + @Test + fun testShouldFsi_suppressiveGroupAlertBehavior_notGrouped() { + forEachFsiState { + assertShouldFsi( + buildFsiEntry { + isGrouped = false + isGroupSummary = true + groupAlertBehavior = GROUP_ALERT_CHILDREN + } + ) + } + } + + @Test + fun testShouldFsi_suppressiveGroupAlertBehavior_notSuppressive() { + forEachFsiState { + assertShouldFsi( + buildFsiEntry { + isGrouped = true + isGroupSummary = true + groupAlertBehavior = GROUP_ALERT_ALL + } + ) + } + } + + @Test + fun testShouldNotFsi_suppressiveBubbleMetadata() { + forEachFsiState { + assertShouldNotFsi( + buildFsiEntry { + hasBubbleMetadata = true + bubbleSuppressesNotification = true + } + ) + } + } + + @Test + fun testShouldNotFsi_packageSuspended() { + forEachFsiState { assertShouldNotFsi(buildFsiEntry { packageSuspended = true }) } + } + + @Test fun testShouldFsi_notInteractive() { ensureNotInteractiveFsiState() assertShouldFsi(buildFsiEntry()) @@ -494,6 +631,76 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { assertShouldFsi(buildFsiEntry()) } + @Test + fun testShouldNotFsi_expectedToHun() { + forEachPeekableFsiState { + ensurePeekState() + assertShouldNotFsi(buildFsiEntry()) + } + } + + @Test + fun testShouldNotFsi_expectedToHun_hunSnoozed() { + forEachPeekableFsiState { + ensurePeekState { hunSnoozed = true } + assertShouldNotFsi(buildFsiEntry()) + } + } + + @Test + fun testShouldFsi_lockedShade() { + ensureLockedShadeFsiState() + assertShouldFsi(buildFsiEntry()) + } + + @Test + fun testShouldFsi_keyguardOccluded() { + ensureKeyguardOccludedFsiState() + assertShouldFsi(buildFsiEntry()) + } + + @Test + fun testShouldFsi_deviceNotProvisioned() { + ensureDeviceNotProvisionedFsiState() + assertShouldFsi(buildFsiEntry()) + } + + @Test + fun testShouldNotFsi_noHunOrKeyguard() { + ensureNoHunOrKeyguardFsiState() + assertShouldNotFsi(buildFsiEntry()) + } + + @Test + fun testShouldFsi_defaultLegacySuppressor() { + forEachFsiState { + withLegacySuppressor(neverSuppresses) { assertShouldFsi(buildFsiEntry()) } + } + } + + @Test + fun testShouldFsi_suppressInterruptions() { + forEachFsiState { + withLegacySuppressor(alwaysSuppressesInterruptions) { assertShouldFsi(buildFsiEntry()) } + } + } + + @Test + fun testShouldFsi_suppressAwakeInterruptions() { + forEachFsiState { + withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { + assertShouldFsi(buildFsiEntry()) + } + } + } + + @Test + fun testShouldFsi_suppressAwakeHeadsUp() { + forEachFsiState { + withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { assertShouldFsi(buildFsiEntry()) } + } + } + protected data class State( var hunSettingEnabled: Boolean? = null, var hunSnoozed: Boolean? = null, @@ -505,6 +712,9 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { var keyguardShouldHideNotification: Boolean? = null, var pulseOnNotificationsEnabled: Boolean? = null, var statusBarState: Int? = null, + var keyguardIsShowing: Boolean = false, + var keyguardIsOccluded: Boolean = false, + var deviceProvisioned: Boolean = true ) protected fun setState(state: State): Unit = @@ -536,6 +746,11 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } statusBarState?.let { statusBarStateController.state = it } + + keyguardStateController.isOccluded = keyguardIsOccluded + keyguardStateController.isShowing = keyguardIsShowing + + deviceProvisionedController.deviceProvisioned = deviceProvisioned } protected fun ensureState(block: State.() -> Unit) = @@ -565,33 +780,111 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { protected fun ensureBubbleState(block: State.() -> Unit = {}) = ensureState(block) protected fun ensureNotInteractiveFsiState(block: State.() -> Unit = {}) = ensureState { - isDreaming = false isInteractive = false - statusBarState = SHADE run(block) } protected fun ensureDreamingFsiState(block: State.() -> Unit = {}) = ensureState { + isInteractive = true isDreaming = true + run(block) + } + + protected fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState { + isInteractive = true + isDreaming = false + statusBarState = KEYGUARD + run(block) + } + + protected fun ensureLockedShadeFsiState(block: State.() -> Unit = {}) = ensureState { + // It is assumed *but not checked in the code* that statusBarState is SHADE_LOCKED. isInteractive = true + isDreaming = false statusBarState = SHADE + hunSettingEnabled = false + keyguardIsShowing = true + keyguardIsOccluded = false run(block) } - protected fun ensureKeyguardFsiState(block: State.() -> Unit = {}) = ensureState { + protected fun ensureKeyguardOccludedFsiState(block: State.() -> Unit = {}) = ensureState { + isInteractive = true isDreaming = false + statusBarState = SHADE + hunSettingEnabled = false + keyguardIsShowing = true + keyguardIsOccluded = true + run(block) + } + + protected fun ensureDeviceNotProvisionedFsiState(block: State.() -> Unit = {}) = ensureState { isInteractive = true - statusBarState = KEYGUARD + isDreaming = false + statusBarState = SHADE + hunSettingEnabled = false + keyguardIsShowing = false + deviceProvisioned = false + run(block) + } + + protected fun ensureNoHunOrKeyguardFsiState(block: State.() -> Unit = {}) = ensureState { + isInteractive = true + isDreaming = false + statusBarState = SHADE + hunSettingEnabled = false + keyguardIsShowing = false + deviceProvisioned = true run(block) } + protected fun forEachFsiState(block: () -> Unit) { + ensureNotInteractiveFsiState() + block() + + ensureDreamingFsiState() + block() + + ensureKeyguardFsiState() + block() + + ensureLockedShadeFsiState() + block() + + ensureKeyguardOccludedFsiState() + block() + + ensureDeviceNotProvisionedFsiState() + block() + } + + private fun forEachPeekableFsiState(extendState: State.() -> Unit = {}, block: () -> Unit) { + ensureLockedShadeFsiState(extendState) + block() + + ensureKeyguardOccludedFsiState(extendState) + block() + + ensureDeviceNotProvisionedFsiState(extendState) + block() + } + + protected fun withLegacySuppressor( + suppressor: NotificationInterruptSuppressor, + block: () -> Unit + ) { + provider.addLegacySuppressor(suppressor) + block() + provider.removeLegacySuppressor(suppressor) + } + protected fun assertShouldHeadsUp(entry: NotificationEntry) = - provider.makeUnloggedHeadsUpDecision(entry).let { + provider.makeAndLogHeadsUpDecision(entry).let { assertTrue("unexpected suppressed HUN: ${it.logReason}", it.shouldInterrupt) } protected fun assertShouldNotHeadsUp(entry: NotificationEntry) = - provider.makeUnloggedHeadsUpDecision(entry).let { + provider.makeAndLogHeadsUpDecision(entry).let { assertFalse("unexpected unsuppressed HUN: ${it.logReason}", it.shouldInterrupt) } @@ -607,30 +900,57 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { protected fun assertShouldFsi(entry: NotificationEntry) = provider.makeUnloggedFullScreenIntentDecision(entry).let { + provider.logFullScreenIntentDecision(it) assertTrue("unexpected suppressed FSI: ${it.logReason}", it.shouldInterrupt) } - protected fun assertShouldNotFsi(entry: NotificationEntry) = + protected fun assertShouldNotFsi( + entry: NotificationEntry, + expectWouldInterruptWithoutDnd: Boolean? = null + ) = provider.makeUnloggedFullScreenIntentDecision(entry).let { + provider.logFullScreenIntentDecision(it) assertFalse("unexpected unsuppressed FSI: ${it.logReason}", it.shouldInterrupt) + if (expectWouldInterruptWithoutDnd != null) { + assertEquals( + "unexpected wouldInterruptWithoutDnd for FSI: ${it.logReason}", + expectWouldInterruptWithoutDnd, + it.wouldInterruptWithoutDnd + ) + } } protected class EntryBuilder(val context: Context) { - var importance = IMPORTANCE_DEFAULT - var suppressedVisualEffects: Int? = null - var whenMs: Long? = null - var visibilityOverride: Int? = null - var hasFsi = false - var canBubble: Boolean? = null - var isBubble = false - var hasBubbleMetadata = false + // Set on BubbleMetadata: var bubbleIsShortcut = false - var bubbleSuppressesNotification: Boolean? = null + var bubbleSuppressesNotification = false + + // Set on Notification.Builder: + var whenMs: Long? = null var isGrouped = false - var isGroupSummary: Boolean? = null + var isGroupSummary = false var groupAlertBehavior: Int? = null + var hasBubbleMetadata = false + var hasFsi = false + + // Set on Notification: + var isForegroundService = false + var isUserInitiatedJob = false + var isBubble = false + var isStickyAndNotDemoted = false + + // Set on NotificationEntryBuilder: + var importance = IMPORTANCE_DEFAULT + var canBubble: Boolean? = null + + // Set on NotificationEntry: var hasJustLaunchedFsi = false + // Set on ModifiedRankingBuilder: + var packageSuspended = false + var visibilityOverride: Int? = null + var suppressedVisualEffects: Int? = null + private fun buildBubbleMetadata(): BubbleMetadata { val builder = if (bubbleIsShortcut) { @@ -647,62 +967,87 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { ) } - bubbleSuppressesNotification?.let { builder.setSuppressNotification(it) } + if (bubbleSuppressesNotification) { + builder.setSuppressNotification(true) + } return builder.build() } fun build() = Notification.Builder(context, TEST_CHANNEL_ID) - .apply { - setContentTitle(TEST_CONTENT_TITLE) - setContentText(TEST_CONTENT_TEXT) + .also { nb -> + nb.setContentTitle(TEST_CONTENT_TITLE) + nb.setContentText(TEST_CONTENT_TEXT) - if (hasFsi) { - setFullScreenIntent(mock(), /* highPriority = */ true) + whenMs?.let { nb.setWhen(it) } + + if (isGrouped) { + nb.setGroup(TEST_GROUP_KEY) + } + + if (isGroupSummary) { + nb.setGroupSummary(true) } - whenMs?.let { setWhen(it) } + groupAlertBehavior?.let { nb.setGroupAlertBehavior(it) } if (hasBubbleMetadata) { - setBubbleMetadata(buildBubbleMetadata()) + nb.setBubbleMetadata(buildBubbleMetadata()) } - if (isGrouped) { - setGroup(TEST_GROUP_KEY) + if (hasFsi) { + nb.setFullScreenIntent(mock(), /* highPriority = */ true) } - - isGroupSummary?.let { setGroupSummary(it) } - - groupAlertBehavior?.let { setGroupAlertBehavior(it) } } .build() - .apply { + .also { n -> + if (isForegroundService) { + n.flags = n.flags or FLAG_FOREGROUND_SERVICE + } + + if (isUserInitiatedJob) { + n.flags = n.flags or FLAG_USER_INITIATED_JOB + } + if (isBubble) { - flags = flags or FLAG_BUBBLE + n.flags = n.flags or FLAG_BUBBLE + } + + if (isStickyAndNotDemoted) { + n.flags = n.flags or FLAG_FSI_REQUESTED_BUT_DENIED } } .let { NotificationEntryBuilder().setNotification(it) } - .apply { - setPkg(TEST_PACKAGE) - setOpPkg(TEST_PACKAGE) - setTag(TEST_TAG) - - setImportance(importance) - setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)) + .also { neb -> + neb.setPkg(TEST_PACKAGE) + neb.setOpPkg(TEST_PACKAGE) + neb.setTag(TEST_TAG) + + neb.setImportance(importance) + neb.setChannel( + NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance) + ) - canBubble?.let { setCanBubble(it) } + canBubble?.let { neb.setCanBubble(it) } } .build()!! - .also { + .also { ne -> if (hasJustLaunchedFsi) { - it.notifyFullScreenIntentLaunched() + ne.notifyFullScreenIntentLaunched() + } + + if (isStickyAndNotDemoted) { + assertFalse(ne.isDemoted) } - modifyRanking(it) - .apply { - suppressedVisualEffects?.let { setSuppressedVisualEffects(it) } - visibilityOverride?.let { setVisibilityOverride(it) } + modifyRanking(ne) + .also { mrb -> + if (packageSuspended) { + mrb.setSuspended(true) + } + visibilityOverride?.let { mrb.setVisibilityOverride(it) } + suppressedVisualEffects?.let { mrb.setSuppressedVisualEffects(it) } } .build() } @@ -723,6 +1068,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } protected fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry { + isBubble = true canBubble = true hasBubbleMetadata = true run(block) 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 6203531cabab..e91d6d724a73 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 @@ -83,6 +83,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -906,6 +907,20 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { assertEquals(bottomImeInset, mStackScrollerInternal.mBottomInset); } + @Test + public void testSetMaxDisplayedNotifications_notifiesListeners() { + ExpandableView.OnHeightChangedListener listener = + mock(ExpandableView.OnHeightChangedListener.class); + Runnable runnable = mock(Runnable.class); + mStackScroller.setOnHeightChangedListener(listener); + mStackScroller.setOnHeightChangedRunnable(runnable); + + mStackScroller.setMaxDisplayedNotifications(50); + + verify(listener).onHeightChanged(mNotificationShelf, false); + verify(runnable).run(); + } + 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. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 22553dfc4cb1..db8f21714964 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -40,13 +40,8 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.res.R import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController -import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.user.domain.UserDomainLayerModule -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component @@ -84,25 +79,13 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } } - private val notificationStackSizeCalculator: NotificationStackSizeCalculator = mock() - private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController = - mock { - whenever(view).thenReturn(mock()) - whenever(shelfHeight).thenReturn(0) - } - private val testComponent: TestComponent = DaggerSharedNotificationContainerViewModelTest_TestComponent.factory() .create( test = this, featureFlags = FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, - mocks = - TestMocksModule( - notificationStackSizeCalculator = notificationStackSizeCalculator, - notificationStackScrollLayoutController = - notificationStackScrollLayoutController, - ) + mocks = TestMocksModule(), ) @Test @@ -336,17 +319,31 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun maxNotificationsOnLockscreen() = testComponent.runTest { - whenever( - notificationStackSizeCalculator.computeMaxKeyguardNotifications( - any(), - any(), - any(), - any() - ) - ) - .thenReturn(10) + var notificationCount = 10 + val maxNotifications by + collectLastValue(underTest.getMaxNotifications { notificationCount }) + + showLockscreen() + + overrideResource(R.bool.config_use_split_notification_shade, false) + configurationRepository.onAnyConfigurationChange() + keyguardInteractor.sharedNotificationContainerPosition.value = + SharedNotificationContainerPosition(top = 1f, bottom = 2f) + + assertThat(maxNotifications).isEqualTo(10) + + // Also updates when directly requested (as it would from NotificationStackScrollLayout) + notificationCount = 25 + sharedNotificationContainerInteractor.notificationStackChanged() + assertThat(maxNotifications).isEqualTo(25) + } - val maxNotifications by collectLastValue(underTest.maxNotifications) + @Test + fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() = + testComponent.runTest { + var notificationCount = 10 + val maxNotifications by + collectLastValue(underTest.getMaxNotifications { notificationCount }) showLockscreen() @@ -356,21 +353,30 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { SharedNotificationContainerPosition(top = 1f, bottom = 2f) assertThat(maxNotifications).isEqualTo(10) + + // Shade expanding... still 10 + shadeRepository.setLockscreenShadeExpansion(0.5f) + assertThat(maxNotifications).isEqualTo(10) + + notificationCount = 25 + + // When shade is expanding by user interaction + shadeRepository.setLegacyLockscreenShadeTracking(true) + + // Should still be 10, since the user is interacting + assertThat(maxNotifications).isEqualTo(10) + + shadeRepository.setLegacyLockscreenShadeTracking(false) + shadeRepository.setLockscreenShadeExpansion(0f) + + // Stopped tracking, show 25 + assertThat(maxNotifications).isEqualTo(25) } @Test fun maxNotificationsOnShade() = testComponent.runTest { - whenever( - notificationStackSizeCalculator.computeMaxKeyguardNotifications( - any(), - any(), - any(), - any() - ) - ) - .thenReturn(10) - val maxNotifications by collectLastValue(underTest.maxNotifications) + val maxNotifications by collectLastValue(underTest.getMaxNotifications { 10 }) // Show lockscreen with shade expanded showLockscreenWithShadeExpanded() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 9aafee4770de..6a0375d55a72 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import android.os.Handler import android.os.PowerManager import android.testing.AndroidTestingRunner @@ -82,14 +80,9 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var handler: Handler - private lateinit var featureFlags: FakeFeatureFlags - @Before fun setUp() { MockitoAnnotations.initMocks(this) - featureFlags = FakeFeatureFlags().apply { - set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false) - } controller = UnlockedScreenOffAnimationController( context, wakefulnessLifecycle, @@ -102,7 +95,6 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { interactionJankMonitor, powerManager, handler = handler, - featureFlags, ) controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index d1b9b8aae70e..0b87fe8da184 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; @@ -703,6 +704,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mKeyguardStateController, mShadeViewController, mStatusBarStateController, + mock(StatusBarIconViewBindingFailureTracker.class), mCommandQueue, mCarrierConfigTracker, new CollapsedStatusBarFragmentLogger( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt index d33806e131d5..1250228e2d37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.policy -import com.android.systemui.flags.FakeFeatureFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.ViewUtils @@ -78,13 +77,11 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { private lateinit var view: FrameLayout private lateinit var testableLooper: TestableLooper private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController - private lateinit var featureFlags: FakeFeatureFlags @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) - featureFlags = FakeFeatureFlags() view = LayoutInflater.from(context) .inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout @@ -101,7 +98,6 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { dozeParameters, screenOffAnimationController, userSwitchDialogController, - featureFlags, uiEventLogger) ViewUtils.attachView(view) diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 7456e00e948d..8c823b2376c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -20,7 +20,6 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; -import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; @@ -68,7 +67,6 @@ import com.android.systemui.Prefs; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialogController; @@ -149,14 +147,13 @@ public class VolumeDialogImplTest extends SysuiTestCase { } }; - private FakeFeatureFlags mFeatureFlags; private int mLongestHideShowAnimationDuration = 250; private FakeSettings mSecureSettings; @Rule public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); - @Before + @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); @@ -179,8 +176,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mConfigurationController = new FakeConfigurationController(); - mFeatureFlags = new FakeFeatureFlags(); - mSecureSettings = new FakeSettings(); when(mLazySecureSettings.get()).thenReturn(mSecureSettings); @@ -200,7 +195,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mPostureController, mTestableLooper.getLooper(), mDumpManager, - mFeatureFlags, mLazySecureSettings); mDialog.init(0, null); State state = createShellState(); @@ -328,7 +322,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Test public void testVibrateOnRingerChangedToVibrate() { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialSilentState = new State(); initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT; @@ -349,30 +342,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - public void testControllerDoesNotVibrateOnRingerChangedToVibrate_OnewayAPI_On() { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - final State initialSilentState = new State(); - initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT; - - final State vibrateState = new State(); - vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; - - // change ringer to silent - mDialog.onStateChangedH(initialSilentState); - - // expected: shouldn't call vibrate yet - verify(mVolumeDialogController, never()).vibrate(any()); - - // changed ringer to vibrate - mDialog.onStateChangedH(vibrateState); - - // expected: vibrate method of controller is not used - verify(mVolumeDialogController, never()).vibrate(any()); - } - - @Test public void testNoVibrateOnRingerInitialization() { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = -1; @@ -390,29 +360,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - public void testControllerDoesNotVibrateOnRingerInitialization_OnewayAPI_On() { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - final State initialUnsetState = new State(); - initialUnsetState.ringerModeInternal = -1; - - // ringer not initialized yet: - mDialog.onStateChangedH(initialUnsetState); - - final State vibrateState = new State(); - vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; - - // changed ringer to vibrate - mDialog.onStateChangedH(vibrateState); - - // shouldn't call vibrate on the controller either - verify(mVolumeDialogController, never()).vibrate(any()); - } - - @Test public void testSelectVibrateFromDrawer() { assumeHasDrawer(); - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; mDialog.onStateChangedH(initialUnsetState); @@ -426,27 +376,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - public void testSelectVibrateFromDrawer_OnewayAPI_On() { - assumeHasDrawer(); - - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - final State initialUnsetState = new State(); - initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL; - mDialog.onStateChangedH(initialUnsetState); - - mActiveRinger.performClick(); - mDrawerVibrate.performClick(); - - // Make sure we've actually changed the ringer mode. - verify(mVolumeDialogController, times(1)).setRingerMode( - AudioManager.RINGER_MODE_VIBRATE, false); - } - - @Test public void testSelectMuteFromDrawer() { assumeHasDrawer(); - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; mDialog.onStateChangedH(initialUnsetState); @@ -460,27 +392,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - public void testSelectMuteFromDrawer_OnewayAPI_On() { - assumeHasDrawer(); - - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - final State initialUnsetState = new State(); - initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL; - mDialog.onStateChangedH(initialUnsetState); - - mActiveRinger.performClick(); - mDrawerMute.performClick(); - - // Make sure we've actually changed the ringer mode. - verify(mVolumeDialogController, times(1)).setRingerMode( - AudioManager.RINGER_MODE_SILENT, false); - } - - @Test public void testSelectNormalFromDrawer() { assumeHasDrawer(); - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; mDialog.onStateChangedH(initialUnsetState); @@ -493,23 +407,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { AudioManager.RINGER_MODE_NORMAL, false); } - @Test - public void testSelectNormalFromDrawer_OnewayAPI_On() { - assumeHasDrawer(); - - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - final State initialUnsetState = new State(); - initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; - mDialog.onStateChangedH(initialUnsetState); - - mActiveRinger.performClick(); - mDrawerNormal.performClick(); - - // Make sure we've actually changed the ringer mode. - verify(mVolumeDialogController, times(1)).setRingerMode( - RINGER_MODE_NORMAL, false); - } - /** * Ideally we would look at the ringer ImageView and check its assigned drawable id, but that * API does not exist. So we do the next best thing; we check the cached icon id. @@ -682,7 +579,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { State state = createShellState(); state.ringerModeInternal = ringerMode; - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); mDialog.onStateChangedH(state); mDialog.show(SHOW_REASON_UNKNOWN); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index 81c5d9c248f3..af1930ef143e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt @@ -20,16 +20,16 @@ import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel.SecurityMode -import com.android.systemui.authentication.data.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationResultModel import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository import dagger.Binds import dagger.Module import dagger.Provides import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -37,13 +37,13 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.currentTime class FakeAuthenticationRepository( - private val deviceEntryRepository: FakeDeviceEntryRepository, private val currentTime: () -> Long, ) : AuthenticationRepository { private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false) override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = _isAutoConfirmFeatureEnabled.asStateFlow() + override val authenticationChallengeResult = MutableSharedFlow<Boolean>() override val hintedPinLength: Int = HINTING_PIN_LENGTH @@ -60,6 +60,10 @@ class FakeAuthenticationRepository( override val minPatternLength: Int = 4 + private val _isPinEnhancedPrivacyEnabled = MutableStateFlow(false) + override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + _isPinEnhancedPrivacyEnabled.asStateFlow() + private var failedAttemptCount = 0 private var throttlingEndTimestamp = 0L private var credentialOverride: List<Any>? = null @@ -80,7 +84,7 @@ class FakeAuthenticationRepository( override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) { failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1 - deviceEntryRepository.setUnlocked(isSuccessful) + authenticationChallengeResult.emit(isSuccessful) } override suspend fun getPinLength(): Int { @@ -138,6 +142,10 @@ class FakeAuthenticationRepository( } } + fun setPinEnhancedPrivacyEnabled(isEnabled: Boolean) { + _isPinEnhancedPrivacyEnabled.value = isEnabled + } + private fun getExpectedCredential(securityMode: SecurityMode): List<Any> { return when (val credentialType = getCurrentCredentialType(securityMode)) { LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN @@ -216,9 +224,8 @@ object FakeAuthenticationRepositoryModule { @Provides @SysUISingleton fun provideFake( - deviceEntryRepository: FakeDeviceEntryRepository, scope: TestScope, - ) = FakeAuthenticationRepository(deviceEntryRepository, currentTime = { scope.currentTime }) + ) = FakeAuthenticationRepository(currentTime = { scope.currentTime }) @Module interface Bindings { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt index 0c5e43809fab..005cac490d89 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt @@ -19,10 +19,15 @@ package com.android.systemui.biometrics.data.repository import android.hardware.biometrics.SensorLocationInternal import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -class FakeFingerprintPropertyRepository : FingerprintPropertyRepository { +@SysUISingleton +class FakeFingerprintPropertyRepository @Inject constructor() : FingerprintPropertyRepository { private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1) override val sensorId = _sensorId.asStateFlow() @@ -50,4 +55,29 @@ class FakeFingerprintPropertyRepository : FingerprintPropertyRepository { _sensorType.value = sensorType _sensorLocations.value = sensorLocations } + + /** setProperties as if the device supports UDFPS_OPTICAL. */ + fun supportsUdfps() { + setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.UDFPS_OPTICAL, + sensorLocations = emptyMap(), + ) + } + + /** setProperties as if the device supports the rear fingerprint sensor. */ + fun supportsRearFps() { + setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.REAR, + sensorLocations = emptyMap(), + ) + } +} + +@Module +interface FakeFingerprintPropertyRepositoryModule { + @Binds fun bindFake(fake: FakeFingerprintPropertyRepository): FingerprintPropertyRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt index 44286b715abb..8ff04a63802a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt @@ -15,17 +15,23 @@ package com.android.systemui.deviceentry.data +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepositoryModule import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepositoryModule import com.android.systemui.keyguard.data.repository.FakeTrustRepositoryModule import dagger.Module @Module( includes = [ + FakeBiometricSettingsRepositoryModule::class, FakeDeviceEntryRepositoryModule::class, - FakeTrustRepositoryModule::class, FakeDeviceEntryFaceAuthRepositoryModule::class, + FakeDeviceEntryFingerprintAuthRepositoryModule::class, + FakeFingerprintPropertyRepositoryModule::class, + FakeTrustRepositoryModule::class, ] ) object FakeDeviceEntryDataLayerModule diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt index f0293489cb87..ba70d46fd954 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt @@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.asStateFlow @SysUISingleton class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository { - private var isInsecureLockscreenEnabled = true + private var isLockscreenEnabled = true private val _isBypassEnabled = MutableStateFlow(false) override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled @@ -35,16 +35,20 @@ class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository { private val _isUnlocked = MutableStateFlow(false) override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() - override suspend fun isInsecureLockscreenEnabled(): Boolean { - return isInsecureLockscreenEnabled + override suspend fun isLockscreenEnabled(): Boolean { + return isLockscreenEnabled + } + + override fun reportSuccessfulAuthentication() { + _isUnlocked.value = true } fun setUnlocked(isUnlocked: Boolean) { _isUnlocked.value = isUnlocked } - fun setInsecureLockscreenEnabled(isLockscreenEnabled: Boolean) { - this.isInsecureLockscreenEnabled = isLockscreenEnabled + fun setLockscreenEnabled(isLockscreenEnabled: Boolean) { + this.isLockscreenEnabled = isLockscreenEnabled } fun setBypassEnabled(isBypassEnabled: Boolean) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt index 852611230623..df31a12b8415 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt @@ -18,13 +18,18 @@ package com.android.systemui.keyguard.data.repository import com.android.internal.widget.LockPatternUtils +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.AuthenticationFlags +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map -class FakeBiometricSettingsRepository : BiometricSettingsRepository { +@SysUISingleton +class FakeBiometricSettingsRepository @Inject constructor() : BiometricSettingsRepository { private val _isFingerprintEnrolledAndEnabled = MutableStateFlow(false) override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> get() = _isFingerprintEnrolledAndEnabled @@ -97,3 +102,8 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository { } } } + +@Module +interface FakeBiometricSettingsRepositoryModule { + @Binds fun bindFake(fake: FakeBiometricSettingsRepository): BiometricSettingsRepository +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt index 38791caf5bfc..c9160efb75a4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt @@ -17,14 +17,20 @@ package com.android.systemui.keyguard.data.repository +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull -class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository { +@SysUISingleton +class FakeDeviceEntryFingerprintAuthRepository @Inject constructor() : + DeviceEntryFingerprintAuthRepository { private val _isLockedOut = MutableStateFlow(false) override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow() fun setLockedOut(lockedOut: Boolean) { @@ -52,3 +58,11 @@ class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepos _authenticationStatus.value = status } } + +@Module +interface FakeDeviceEntryFingerprintAuthRepositoryModule { + @Binds + fun bindFake( + fake: FakeDeviceEntryFingerprintAuthRepository + ): DeviceEntryFingerprintAuthRepository +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index b90ad8cd8745..3674244926e8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -151,6 +151,17 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio _transitions.emit(step) } + suspend fun sendTransitionSteps( + steps: List<TransitionStep>, + testScope: TestScope, + validateStep: Boolean = true + ) { + steps.forEach { + sendTransitionStep(it, validateStep = validateStep) + testScope.testScheduler.runCurrent() + } + } + override fun startTransition(info: TransitionInfo): UUID? { return if (info.animator == null) UUID.randomUUID() else null } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt index 1efa74b0551a..62765d10486c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt @@ -20,7 +20,6 @@ import android.os.UserHandle class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor { - var handleResult: Boolean = false var policyResult: DisabledByPolicyInteractor.PolicyResult = DisabledByPolicyInteractor.PolicyResult.TileEnabled @@ -31,5 +30,9 @@ class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor { override fun handlePolicyResult( policyResult: DisabledByPolicyInteractor.PolicyResult - ): Boolean = handleResult + ): Boolean = + when (policyResult) { + is DisabledByPolicyInteractor.PolicyResult.TileEnabled -> false + is DisabledByPolicyInteractor.PolicyResult.TileDisabled -> true + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt index 2b3330f3a33b..3fcf8a93dc87 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt @@ -17,16 +17,21 @@ package com.android.systemui.qs.tiles.base.interactor import android.os.UserHandle -import javax.annotation.CheckReturnValue import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.flatMapLatest -class FakeQSTileDataInteractor<T>( - private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = Int.MAX_VALUE), - private val availabilityFlow: MutableSharedFlow<Boolean> = - MutableSharedFlow(replay = Int.MAX_VALUE), -) : QSTileDataInteractor<T> { +class FakeQSTileDataInteractor<T> : QSTileDataInteractor<T> { + + private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = 1) + val dataSubscriptionCount + get() = dataFlow.subscriptionCount + private val availabilityFlow: MutableSharedFlow<Boolean> = MutableSharedFlow(replay = 1) + val availabilitySubscriptionCount + get() = availabilityFlow.subscriptionCount + + private val mutableTriggers = mutableListOf<DataUpdateTrigger>() + val triggers: List<DataUpdateTrigger> = mutableTriggers private val mutableDataRequests = mutableListOf<DataRequest>() val dataRequests: List<DataRequest> = mutableDataRequests @@ -34,14 +39,17 @@ class FakeQSTileDataInteractor<T>( private val mutableAvailabilityRequests = mutableListOf<AvailabilityRequest>() val availabilityRequests: List<AvailabilityRequest> = mutableAvailabilityRequests - @CheckReturnValue fun emitData(data: T): Boolean = dataFlow.tryEmit(data) + suspend fun emitData(data: T): Unit = dataFlow.emit(data) fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable) suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable) override fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<T> { mutableDataRequests.add(DataRequest(user)) - return triggers.flatMapLatest { dataFlow } + return triggers.flatMapLatest { + mutableTriggers.add(it) + dataFlow + } } override fun availability(user: UserHandle): Flow<Boolean> { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt index de72a7dc30d7..d231d63a3906 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt @@ -24,6 +24,8 @@ class FakeQSTileConfigProvider : QSTileConfigProvider { override fun getConfig(tileSpec: String): QSTileConfig = configs.getValue(tileSpec) + override fun hasConfig(tileSpec: String): Boolean = configs.containsKey(tileSpec) + fun putConfig(tileSpec: TileSpec, config: QSTileConfig) { configs[tileSpec.spec] = config } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 36ec18fc4de4..72cc08f03dac 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -26,11 +26,9 @@ import android.telecom.TelecomManager import com.android.internal.logging.MetricsLogger import com.android.internal.util.EmergencyAffordanceManager import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel import com.android.systemui.authentication.data.repository.AuthenticationRepository import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository @@ -110,7 +108,6 @@ class SceneTestUtils( val deviceEntryRepository: FakeDeviceEntryRepository by lazy { FakeDeviceEntryRepository() } val authenticationRepository: FakeAuthenticationRepository by lazy { FakeAuthenticationRepository( - deviceEntryRepository = deviceEntryRepository, currentTime = { testScope.currentTime }, ) } @@ -181,6 +178,7 @@ class SceneTestUtils( sceneInteractor = sceneInteractor, deviceEntryFaceAuthRepository = faceAuthRepository, trustRepository = trustRepository, + flags = FakeSceneContainerFlags(enabled = true) ) } @@ -192,7 +190,6 @@ class SceneTestUtils( repository = repository, backgroundDispatcher = testDispatcher, userRepository = userRepository, - deviceEntryRepository = deviceEntryRepository, clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } } ) } @@ -221,17 +218,13 @@ class SceneTestUtils( } fun bouncerInteractor( - deviceEntryInteractor: DeviceEntryInteractor, authenticationInteractor: AuthenticationInteractor, - sceneInteractor: SceneInteractor, ): BouncerInteractor { return BouncerInteractor( applicationScope = applicationScope(), applicationContext = context, repository = BouncerRepository(featureFlags), - deviceEntryInteractor = deviceEntryInteractor, authenticationInteractor = authenticationInteractor, - sceneInteractor = sceneInteractor, flags = sceneContainerFlags, falsingInteractor = falsingInteractor(), ) @@ -345,19 +338,4 @@ class SceneTestUtils( dozeLogger = dozeLogger, ) } - - companion object { - fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel { - return when (this) { - DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None - DomainLayerAuthenticationMethodModel.Swipe -> - DataLayerAuthenticationMethodModel.None - DomainLayerAuthenticationMethodModel.Pin -> DataLayerAuthenticationMethodModel.Pin - DomainLayerAuthenticationMethodModel.Password -> - DataLayerAuthenticationMethodModel.Password - DomainLayerAuthenticationMethodModel.Pattern -> - DataLayerAuthenticationMethodModel.Pattern - } - } - } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt index 800593fe61a1..02318abe8488 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -59,6 +59,8 @@ class FakeShadeRepository @Inject constructor() : ShadeRepository { private val _legacyIsQsExpanded = MutableStateFlow(false) @Deprecated("Use ShadeInteractor instead") override val legacyIsQsExpanded = _legacyIsQsExpanded + override val legacyLockscreenShadeTracking = MutableStateFlow(false) + @Deprecated("Use ShadeInteractor instead") override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) { _legacyIsQsExpanded.value = legacyIsQsExpanded @@ -81,6 +83,11 @@ class FakeShadeRepository @Inject constructor() : ShadeRepository { _legacyShadeTracking.value = tracking } + @Deprecated("Should only be called by NPVC and tests") + override fun setLegacyLockscreenShadeTracking(tracking: Boolean) { + legacyLockscreenShadeTracking.value = tracking + } + fun setShadeModel(model: ShadeModel) { _shadeModel.value = model } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt new file mode 100644 index 000000000000..0c2b115a8af5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeDeviceProvisionedController.kt @@ -0,0 +1,31 @@ +package com.android.systemui.statusbar.policy + +class FakeDeviceProvisionedController : DeviceProvisionedController { + @JvmField var deviceProvisioned = true + + override fun addCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { + TODO("Not yet implemented") + } + + override fun removeCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { + TODO("Not yet implemented") + } + + override fun isDeviceProvisioned() = deviceProvisioned + + override fun getCurrentUser(): Int { + TODO("Not yet implemented") + } + + override fun isUserSetup(user: Int): Boolean { + TODO("Not yet implemented") + } + + override fun isCurrentUserSetup(): Boolean { + TODO("Not yet implemented") + } + + override fun isFrpActive(): Boolean { + TODO("Not yet implemented") + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java index bdf1aff7f985..d45281005309 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java @@ -24,6 +24,9 @@ public class FakeKeyguardStateController implements KeyguardStateController { private final BaseLeakChecker<Callback> mCallbackController; + private boolean mIsShowing = false; + private boolean mIsOccluded = false; + public FakeKeyguardStateController(LeakCheck test) { mCallbackController = new BaseLeakChecker<Callback>(test, "keyguard"); } @@ -45,7 +48,11 @@ public class FakeKeyguardStateController implements KeyguardStateController { @Override public boolean isShowing() { - return false; + return mIsShowing; + } + + public void setShowing(boolean showing) { + mIsShowing = showing; } @Override @@ -60,7 +67,11 @@ public class FakeKeyguardStateController implements KeyguardStateController { @Override public boolean isOccluded() { - return false; + return mIsOccluded; + } + + public void setOccluded(boolean occluded) { + mIsOccluded = occluded; } @Override diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index ec12d2171a93..fc4ed1d4d527 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -28,9 +28,8 @@ java_library { name: "ravenwood-junit", srcs: ["junit-src/**/*.java"], libs: [ + "framework-minus-apex.ravenwood", "junit", ], - sdk_version: "core_current", - host_supported: true, visibility: ["//visibility:public"], } diff --git a/ravenwood/README-ravenwood+mockito.md b/ravenwood/README-ravenwood+mockito.md new file mode 100644 index 000000000000..6adb61441bb1 --- /dev/null +++ b/ravenwood/README-ravenwood+mockito.md @@ -0,0 +1,24 @@ +# Ravenwood and Mockito + +Last update: 2023-11-13 + +- As of 2023-11-13, `external/mockito` is based on version 2.x. +- Mockito didn't support static mocking before 3.4.0. + See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48 + +- Latest Mockito is 5.*. According to https://github.com/mockito/mockito: + `Mockito 3 does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. Mockito 4 removes deprecated API. Mockito 5 switches the default mockmaker to mockito-inline, and now requires Java 11.` + +- Mockito now supports Android natively. + See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.1 + - But it's unclear at this point to omakoto@ how the `mockito-android` module is built. + +- Potential plan: + - Ideal option: + - If we can update `external/mockito`, that'd be great, but it may not work because + Mockito has removed the deprecated APIs. + - Second option: + - Import the latest mockito as `external/mockito-new`, and require ravenwood + to use this one. + - The latest mockito needs be exposed to all of 1) device tests, 2) host tests, and 3) ravenwood tests. + - This probably will require the latest `bytebuddy` and `objenesis`.
\ No newline at end of file diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java index 76964a72dd3e..7dc197e6bdfd 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.TYPE; diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java index ddf65dc2c5ac..1d315798d647 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepWholeClass.java index d7ef7f55921b..d2c77c1b8566 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepWholeClass.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; @@ -35,5 +35,5 @@ import java.lang.annotation.Target; */ @Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) @Retention(RetentionPolicy.CLASS) -public @interface RavenwoodWholeClassKeep { +public @interface RavenwoodKeepWholeClass { } diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodNativeSubstitutionClass.java index 8cdc1ff91081..4b9cf85e16fa 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodNativeSubstitutionClass.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.TYPE; diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java index 759c918c4a66..6727327c99be 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java index 5a0a8f4f5aae..a920f63152fb 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.METHOD; @@ -31,8 +31,5 @@ import java.lang.annotation.Target; */ @Target({METHOD}) @Retention(RetentionPolicy.CLASS) -public @interface RavenwoodSubstitute { - // TODO We should add "_host" as default. We're not doing it yet, because extractign the default - // value with ASM doesn't seem trivial. (? not sure.) - String suffix(); +public @interface RavenwoodReplace { } diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java index de3dd0465c59..a234a9b6fc7c 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.METHOD; diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt index 48c0a2de8f86..692d598ac2bb 100644 --- a/ravenwood/framework-minus-apex-ravenwood-policies.txt +++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt @@ -76,21 +76,13 @@ class android.util.Patterns stubclass class android.util.UtilConfig stubclass # Internals -class com.android.internal.util.ArrayUtils stubclass - method newUnpaddedByteArray (I)[B @newUnpaddedByteArray$ravenwood - method newUnpaddedCharArray (I)[C @newUnpaddedCharArray$ravenwood - method newUnpaddedIntArray (I)[I @newUnpaddedIntArray$ravenwood - method newUnpaddedBooleanArray (I)[Z @newUnpaddedBooleanArray$ravenwood - method newUnpaddedLongArray (I)[J @newUnpaddedLongArray$ravenwood - method newUnpaddedFloatArray (I)[F @newUnpaddedFloatArray$ravenwood - method newUnpaddedObjectArray (I)[Ljava/lang/Object; @newUnpaddedObjectArray$ravenwood - method newUnpaddedArray (Ljava/lang/Class;I)[Ljava/lang/Object; @newUnpaddedArray$ravenwood - class com.android.internal.util.GrowingArrayUtils stubclass class com.android.internal.util.LineBreakBufferedWriter stubclass class com.android.internal.util.Preconditions stubclass class com.android.internal.util.StringPool stubclass +class com.android.internal.os.SomeArgs stubclass + # Parcel class android.os.Parcel stubclass method writeException (Ljava/lang/Exception;)V @writeException$ravenwood @@ -102,14 +94,16 @@ class android.os.ParcelFormatException stubclass class android.os.BadParcelableException stubclass class android.os.BadTypeParcelableException stubclass -# Binder: just enough to construct, no further functionality -class android.os.Binder stub - method <init> ()V stub - method <init> (Ljava/lang/String;)V stub - method isDirectlyHandlingTransaction ()Z stub - method isDirectlyHandlingTransactionNative ()Z @isDirectlyHandlingTransactionNative$ravenwood - method getNativeBBinderHolder ()J @getNativeBBinderHolder$ravenwood +# Binder +class android.os.DeadObjectException stubclass +class android.os.DeadSystemException stubclass +class android.os.RemoteException stubclass +class android.os.TransactionTooLargeException stubclass # Containers class android.os.BaseBundle stubclass class android.os.Bundle stubclass + +# Misc +class android.os.PatternMatcher stubclass +class android.os.ParcelUuid stubclass diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index a6b3f668efa6..bffd0cdc9412 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -16,6 +16,7 @@ package android.platform.test.ravenwood; +import android.os.Process; import android.platform.test.annotations.IgnoreUnderRavenwood; import org.junit.Assume; @@ -23,6 +24,8 @@ import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; +import java.util.concurrent.atomic.AtomicInteger; + /** * THIS RULE IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY * QUESTIONS ABOUT IT. @@ -30,20 +33,84 @@ import org.junit.runners.model.Statement; * @hide */ public class RavenwoodRule implements TestRule { + private static AtomicInteger sNextPid = new AtomicInteger(100); + + /** + * Unless the test author requests differently, run as "nobody", and give each collection of + * tests its own unique PID. + */ + private int mUid = android.os.Process.NOBODY_UID; + private int mPid = sNextPid.getAndIncrement(); + + public RavenwoodRule() { + } + + public static class Builder { + private RavenwoodRule mRule = new RavenwoodRule(); + + public Builder() { + } + + /** + * Configure the identity of this process to be the system UID for the duration of the + * test. Has no effect under non-Ravenwood environments. + */ + public Builder setProcessSystem() { + mRule.mUid = android.os.Process.SYSTEM_UID; + return this; + } + + /** + * Configure the identity of this process to be an app UID for the duration of the + * test. Has no effect under non-Ravenwood environments. + */ + public Builder setProcessApp() { + mRule.mUid = android.os.Process.FIRST_APPLICATION_UID; + return this; + } + + public RavenwoodRule build() { + return mRule; + } + } + + /** + * Return if the current process is running under a Ravenwood test environment. + */ public boolean isUnderRavenwood() { // TODO: give ourselves a better environment signal return System.getProperty("java.class.path").contains("ravenwood"); } + private void init() { + android.os.Process.init$ravenwood(mUid, mPid); + android.os.Binder.init$ravenwood(); + } + + private void reset() { + android.os.Process.reset$ravenwood(); + android.os.Binder.reset$ravenwood(); + } + @Override public Statement apply(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { + final boolean isUnderRavenwood = isUnderRavenwood(); if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) { - Assume.assumeFalse(isUnderRavenwood()); + Assume.assumeFalse(isUnderRavenwood); + } + if (isUnderRavenwood) { + init(); + } + try { + base.evaluate(); + } finally { + if (isUnderRavenwood) { + reset(); + } } - base.evaluate(); } }; } diff --git a/ravenwood/mockito/Android.bp b/ravenwood/mockito/Android.bp new file mode 100644 index 000000000000..4135022d2bc9 --- /dev/null +++ b/ravenwood/mockito/Android.bp @@ -0,0 +1,72 @@ +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"], +} + +// Ravenwood tests run on the hostside, so we need mockit of the host variant. +// But we need to use it in modules of the android variant, so we "wash" the variant with it. +java_host_for_device { + name: "mockito_ravenwood", + libs: [ + "mockito", + "objenesis", + ], +} + +android_ravenwood_test { + name: "RavenwoodMockitoTest", + + srcs: [ + "test/**/*.java", + ], + static_libs: [ + "junit", + "truth", + + "mockito_ravenwood", + ], + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + auto_gen_config: true, +} + +android_test { + name: "RavenwoodMockitoTest_device", + + srcs: [ + "test/**/*.java", + ], + static_libs: [ + "junit", + "truth", + + "androidx.test.rules", + + "ravenwood-junit", + + "mockito-target-extended-minus-junit4", + ], + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + jni_libs: [ + // Required by mockito + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + test_suites: [ + "device-tests", + ], + optimize: { + enabled: false, + }, +} diff --git a/ravenwood/mockito/AndroidManifest.xml b/ravenwood/mockito/AndroidManifest.xml new file mode 100644 index 000000000000..15f0a2934b5f --- /dev/null +++ b/ravenwood/mockito/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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.ravenwood.mockitotest"> + + <application android:debuggable="true" > + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.ravenwood.mockitotest" + /> +</manifest> diff --git a/ravenwood/mockito/AndroidTest.xml b/ravenwood/mockito/AndroidTest.xml new file mode 100644 index 000000000000..96bc2752fe95 --- /dev/null +++ b/ravenwood/mockito/AndroidTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs Frameworks Services Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="RavenwoodMockitoTest_device.apk" /> + </target_preparer> + + <option name="test-tag" value="FrameworksMockingServicesTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.ravenwood.mockitotest" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java new file mode 100644 index 000000000000..36fa3dd94e29 --- /dev/null +++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java @@ -0,0 +1,106 @@ +/* + * 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.ravenwood.mockito; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + +import org.junit.Rule; +import org.junit.Test; + +public class RavenwoodMockitoTest { + @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + +// Use this to mock static methods, which isn't supported by mockito 2. +// Mockito supports static mocking since 3.4.0: +// See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48 + +// private MockitoSession mMockingSession; +// +// @Before +// public void setUp() { +// mMockingSession = mockitoSession() +// .strictness(Strictness.LENIENT) +// .mockStatic(RavenwoodMockitoTest.class) +// .startMocking(); +// } +// +// @After +// public void tearDown() { +// if (mMockingSession != null) { +// mMockingSession.finishMocking(); +// } +// } + + @Test + public void testMockJdkClass() { + Process object = mock(Process.class); + + when(object.exitValue()).thenReturn(42); + + assertThat(object.exitValue()).isEqualTo(42); + } + + /* + - Intent can't be mocked because of the dependency to `org.xmlpull.v1.XmlPullParser`. + (The error says "Mockito can only mock non-private & non-final classes", but that's likely a + red-herring.) + +STACKTRACE: +org.mockito.exceptions.base.MockitoException: +Mockito cannot mock this class: class android.content.Intent. + + : + +Underlying exception : java.lang.IllegalArgumentException: Could not create type + at com.android.ravenwood.mockito.RavenwoodMockitoTest.testMockAndroidClass1 + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + + : + +Caused by: java.lang.ClassNotFoundException: org.xmlpull.v1.XmlPullParser + at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) + at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) + at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) + ... 54 more + */ + @Test + @IgnoreUnderRavenwood + public void testMockAndroidClass1() { + Intent object = mock(Intent.class); + + when(object.getAction()).thenReturn("ACTION_RAVENWOOD"); + + assertThat(object.getAction()).isEqualTo("ACTION_RAVENWOOD"); + } + + @Test + public void testMockAndroidClass2() { + Context object = mock(Context.class); + + when(object.getPackageName()).thenReturn("android"); + + assertThat(object.getPackageName()).isEqualTo("android"); + } +} diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt index 0811f90a504c..776a19a68a31 100644 --- a/ravenwood/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/ravenwood-annotation-allowed-classes.txt @@ -1,2 +1,9 @@ # Only classes listed here can use the Ravenwood annotations. +com.android.internal.util.ArrayUtils + +android.os.Binder +android.os.Binder$IdentitySupplier +android.os.IBinder +android.os.Process +android.os.SystemClock diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt index 6e1384f368b8..4b07ef6e35a8 100644 --- a/ravenwood/ravenwood-standard-options.txt +++ b/ravenwood/ravenwood-standard-options.txt @@ -16,22 +16,22 @@ # Standard annotations. # Note, each line is a single argument, so we need newlines after each `--xxx-annotation`. --keep-annotation - android.ravenwood.annotations.RavenwoodKeep + android.ravenwood.annotation.RavenwoodKeep --keep-class-annotation - android.ravenwood.annotations.RavenwoodWholeClassKeep + android.ravenwood.annotation.RavenwoodKeepWholeClass --throw-annotation - android.ravenwood.annotations.RavenwoodThrow + android.ravenwood.annotation.RavenwoodThrow --remove-annotation - android.ravenwood.annotations.RavenwoodRemove + android.ravenwood.annotation.RavenwoodRemove --substitute-annotation - android.ravenwood.annotations.RavenwoodSubstitute + android.ravenwood.annotation.RavenwoodReplace --native-substitute-annotation - android.ravenwood.annotations.RavenwoodNativeSubstitutionClass + android.ravenwood.annotation.RavenwoodNativeSubstitutionClass --class-load-hook-annotation - android.ravenwood.annotations.RavenwoodClassLoadHook + android.ravenwood.annotation.RavenwoodClassLoadHook diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 2c608930b391..b9c269c91651 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -522,7 +522,8 @@ public class CompanionDeviceManagerService extends SystemService { private void notifyListeners( @UserIdInt int userId, @NonNull List<AssociationInfo> associations) { mListeners.broadcast((listener, callbackUserId) -> { - if ((int) callbackUserId == userId) { + int listenerUserId = (int) callbackUserId; + if (listenerUserId == userId || listenerUserId == UserHandle.USER_ALL) { try { listener.onAssociationsChanged(associations); } catch (RemoteException ignored) { @@ -660,6 +661,9 @@ public class CompanionDeviceManagerService extends SystemService { enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId); + if (userId == UserHandle.USER_ALL) { + return List.copyOf(mAssociationStore.getAssociations()); + } return mAssociationStore.getAssociationsForUser(userId); } diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS index 83143a431406..5295ec82e3c3 100644 --- a/services/companion/java/com/android/server/companion/virtual/OWNERS +++ b/services/companion/java/com/android/server/companion/virtual/OWNERS @@ -1,3 +1,5 @@ +# Bug component: 1171888 + set noparent ogunwale@google.com diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 84fec32e024e..a392e78aa217 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -304,7 +304,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid()); mContext = context.createContextAsUser(ownerUserHandle, 0); mAssociationInfo = associationInfo; - mPersistentDeviceId = PERSISTENT_ID_PREFIX_CDM_ASSOCIATION + associationInfo.getId(); + mPersistentDeviceId = createPersistentDeviceId(associationInfo.getId()); mService = service; mPendingTrampolineCallback = pendingTrampolineCallback; mActivityListener = activityListener; @@ -380,6 +380,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return mSensorController; } + static String createPersistentDeviceId(int associationId) { + return PERSISTENT_ID_PREFIX_CDM_ASSOCIATION + associationId; + } + /** * Returns the flags that should be added to any virtual displays created on this virtual * device. diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 92af68bc40a3..215970eedff1 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -27,6 +27,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.app.ActivityOptions; import android.companion.AssociationInfo; +import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; @@ -94,6 +95,11 @@ public class VirtualDeviceManagerService extends SystemService { private static final String VIRTUAL_DEVICE_NATIVE_SERVICE = "virtualdevice_native"; + private static final List<String> VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES = Arrays.asList( + AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, + AssociationRequest.DEVICE_PROFILE_APP_STREAMING, + AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING); + private final Object mVirtualDeviceManagerLock = new Object(); private final VirtualDeviceManagerImpl mImpl; private final VirtualDeviceManagerNativeImpl mNativeImpl; @@ -105,6 +111,9 @@ public class VirtualDeviceManagerService extends SystemService { private static AtomicInteger sNextUniqueIndex = new AtomicInteger( Context.DEVICE_ID_DEFAULT + 1); + @GuardedBy("mVirtualDeviceManagerLock") + private List<AssociationInfo> mActiveAssociations = new ArrayList<>(); + private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener = new CompanionDeviceManager.OnAssociationsChangedListener() { @Override @@ -161,6 +170,7 @@ public class VirtualDeviceManagerService extends SystemService { }; @Override + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) public void onStart() { publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl); if (Flags.enableNativeVdm()) { @@ -172,6 +182,21 @@ public class VirtualDeviceManagerService extends SystemService { activityTaskManagerInternal.registerActivityStartInterceptor( VIRTUAL_DEVICE_SERVICE_ORDERED_ID, mActivityInterceptorCallback); + + if (Flags.persistentDeviceIdApi()) { + CompanionDeviceManager cdm = + getContext().getSystemService(CompanionDeviceManager.class); + if (cdm != null) { + synchronized (mVirtualDeviceManagerLock) { + mActiveAssociations = cdm.getAllAssociations(UserHandle.USER_ALL); + } + cdm.addOnAssociationsChangedListener(getContext().getMainExecutor(), + this::onCdmAssociationsChanged, UserHandle.USER_ALL); + } else { + Slog.e(TAG, "Failed to find CompanionDeviceManager. No CDM association info " + + " will be available."); + } + } } void onCameraAccessBlocked(int appUid) { @@ -264,9 +289,11 @@ public class VirtualDeviceManagerService extends SystemService { try { getContext().sendBroadcastAsUser(i, UserHandle.ALL); - synchronized (mVirtualDeviceManagerLock) { - if (mVirtualDevices.size() == 0) { - unregisterCdmAssociationListener(); + if (!Flags.persistentDeviceIdApi()) { + synchronized (mVirtualDeviceManagerLock) { + if (mVirtualDevices.size() == 0) { + unregisterCdmAssociationListener(); + } } } } finally { @@ -316,6 +343,45 @@ public class VirtualDeviceManagerService extends SystemService { cdm.removeOnAssociationsChangedListener(mCdmAssociationListener); } + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + void onCdmAssociationsChanged(List<AssociationInfo> associations) { + Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>(); + Set<String> removedPersistentDeviceIds = new HashSet<>(); + synchronized (mVirtualDeviceManagerLock) { + Set<Integer> activeAssociationIds = new HashSet<>(associations.size()); + for (int i = 0; i < associations.size(); ++i) { + activeAssociationIds.add(associations.get(i).getId()); + } + + for (int i = 0; i < mActiveAssociations.size(); ++i) { + AssociationInfo associationInfo = mActiveAssociations.get(i); + if (!activeAssociationIds.contains(associationInfo.getId()) + && VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains( + associationInfo.getDeviceProfile())) { + removedPersistentDeviceIds.add( + VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId())); + } + } + + for (int i = 0; i < mVirtualDevices.size(); i++) { + VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i); + if (!activeAssociationIds.contains(virtualDevice.getAssociationId())) { + virtualDevicesToRemove.add(virtualDevice); + } + } + + mActiveAssociations = associations; + } + + for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) { + virtualDevice.close(); + } + + if (!removedPersistentDeviceIds.isEmpty()) { + mLocalService.onPersistentDeviceIdsRemoved(removedPersistentDeviceIds); + } + } + private ArrayList<VirtualDeviceImpl> getVirtualDevicesSnapshot() { synchronized (mVirtualDeviceManagerLock) { ArrayList<VirtualDeviceImpl> virtualDevices = new ArrayList<>(mVirtualDevices.size()); @@ -393,7 +459,7 @@ public class VirtualDeviceManagerService extends SystemService { } synchronized (mVirtualDeviceManagerLock) { - if (mVirtualDevices.size() == 0) { + if (!Flags.persistentDeviceIdApi() && mVirtualDevices.size() == 0) { final long callindId = Binder.clearCallingIdentity(); try { registerCdmAssociationListener(); @@ -441,10 +507,8 @@ public class VirtualDeviceManagerService extends SystemService { + " is not the owner of the supplied VirtualDevice"); } - int displayId = virtualDeviceImpl.createVirtualDisplay(virtualDisplayConfig, callback, - packageName); - mLocalService.onVirtualDisplayCreated(displayId); - return displayId; + return virtualDeviceImpl.createVirtualDisplay( + virtualDisplayConfig, callback, packageName); } @Override // Binder call @@ -625,11 +689,12 @@ public class VirtualDeviceManagerService extends SystemService { private final class LocalService extends VirtualDeviceManagerInternal { @GuardedBy("mVirtualDeviceManagerLock") - private final ArrayList<VirtualDisplayListener> - mVirtualDisplayListeners = new ArrayList<>(); + private final ArrayList<AppsOnVirtualDeviceListener> mAppsOnVirtualDeviceListeners = + new ArrayList<>(); @GuardedBy("mVirtualDeviceManagerLock") - private final ArrayList<AppsOnVirtualDeviceListener> - mAppsOnVirtualDeviceListeners = new ArrayList<>(); + private final ArrayList<Consumer<String>> mPersistentDeviceIdRemovedListeners = + new ArrayList<>(); + @GuardedBy("mVirtualDeviceManagerLock") private final ArraySet<Integer> mAllUidsOnVirtualDevice = new ArraySet<>(); @@ -665,35 +730,15 @@ public class VirtualDeviceManagerService extends SystemService { } @Override - public void onVirtualDisplayCreated(int displayId) { - final VirtualDisplayListener[] listeners; - synchronized (mVirtualDeviceManagerLock) { - listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]); - } - mHandler.post(() -> { - for (VirtualDisplayListener listener : listeners) { - listener.onVirtualDisplayCreated(displayId); - } - }); - } - - @Override public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) { - final VirtualDisplayListener[] listeners; VirtualDeviceImpl virtualDeviceImpl; synchronized (mVirtualDeviceManagerLock) { - listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]); virtualDeviceImpl = mVirtualDevices.get( ((VirtualDeviceImpl) virtualDevice).getDeviceId()); } if (virtualDeviceImpl != null) { virtualDeviceImpl.onVirtualDisplayRemoved(displayId); } - mHandler.post(() -> { - for (VirtualDisplayListener listener : listeners) { - listener.onVirtualDisplayRemoved(displayId); - } - }); } @Override @@ -725,6 +770,22 @@ public class VirtualDeviceManagerService extends SystemService { } @Override + public void onPersistentDeviceIdsRemoved(Set<String> removedPersistentDeviceIds) { + final List<Consumer<String>> persistentDeviceIdRemovedListeners; + synchronized (mVirtualDeviceManagerLock) { + persistentDeviceIdRemovedListeners = List.copyOf( + mPersistentDeviceIdRemovedListeners); + } + mHandler.post(() -> { + for (String persistentDeviceId : removedPersistentDeviceIds) { + for (Consumer<String> listener : persistentDeviceIdRemovedListeners) { + listener.accept(persistentDeviceId); + } + } + }); + } + + @Override public void onAuthenticationPrompt(int uid) { synchronized (mVirtualDeviceManagerLock) { for (int i = 0; i < mVirtualDevices.size(); i++) { @@ -791,6 +852,10 @@ public class VirtualDeviceManagerService extends SystemService { @Override public @Nullable String getPersistentIdForDevice(int deviceId) { + if (deviceId == Context.DEVICE_ID_DEFAULT) { + return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; + } + VirtualDeviceImpl virtualDevice; synchronized (mVirtualDeviceManagerLock) { virtualDevice = mVirtualDevices.get(deviceId); @@ -799,34 +864,34 @@ public class VirtualDeviceManagerService extends SystemService { } @Override - public void registerVirtualDisplayListener( - @NonNull VirtualDisplayListener listener) { + public void registerAppsOnVirtualDeviceListener( + @NonNull AppsOnVirtualDeviceListener listener) { synchronized (mVirtualDeviceManagerLock) { - mVirtualDisplayListeners.add(listener); + mAppsOnVirtualDeviceListeners.add(listener); } } @Override - public void unregisterVirtualDisplayListener( - @NonNull VirtualDisplayListener listener) { + public void unregisterAppsOnVirtualDeviceListener( + @NonNull AppsOnVirtualDeviceListener listener) { synchronized (mVirtualDeviceManagerLock) { - mVirtualDisplayListeners.remove(listener); + mAppsOnVirtualDeviceListeners.remove(listener); } } @Override - public void registerAppsOnVirtualDeviceListener( - @NonNull AppsOnVirtualDeviceListener listener) { + public void registerPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener) { synchronized (mVirtualDeviceManagerLock) { - mAppsOnVirtualDeviceListeners.add(listener); + mPersistentDeviceIdRemovedListeners.add(persistentDeviceIdRemovedListener); } } @Override - public void unregisterAppsOnVirtualDeviceListener( - @NonNull AppsOnVirtualDeviceListener listener) { + public void unregisterPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener) { synchronized (mVirtualDeviceManagerLock) { - mAppsOnVirtualDeviceListeners.remove(listener); + mPersistentDeviceIdRemovedListeners.remove(persistentDeviceIdRemovedListener); } } } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 065a447a7cd1..4b004340f923 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -43,6 +43,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.util.function.pooled.PooledLambda; import com.android.permission.persistence.RuntimePermissionsState; import com.android.server.pm.Installer.LegacyDexoptDisabledException; @@ -54,7 +55,6 @@ import com.android.server.pm.permission.LegacyPermissionSettings; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import com.android.server.pm.snapshot.PackageDataSnapshot; diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 15fc2dc15d02..f6835feeea16 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3651,7 +3651,26 @@ class StorageManagerService extends IStorageManager.Stub Watchdog.getInstance().pauseWatchingMonitorsFor( SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#close might be slow"); if (mMounted) { - mVold.unmountAppFuse(uid, mountId); + BackgroundThread.getHandler().post(() -> { + try { + // We need to run the unmount on a separate thread to + // prevent a possible deadlock, where: + // 1. AppFuseThread (this thread) tries to call into vold + // 2. the vold lock is held by another thread, which called: + // mVold.openAppFuseFile() + // as part of that call, vold calls open() on the + // underlying file, which is a call that needs to be + // handled by the AppFuseThread, which is stuck waiting + // for the vold lock (see 1.) + // It is safe to do the unmount asynchronously, because the mount + // path we use is never reused during the current boot cycle; + // see mNextAppFuseName. Also,we have anyway stopped serving + // requests at this point. + mVold.unmountAppFuse(uid, mountId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + }); mMounted = false; } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b99a98fe6e8b..f92af6780883 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -599,6 +599,9 @@ public class ActivityManagerService extends IActivityManager.Stub private static final String INTENT_REMOTE_BUGREPORT_FINISHED = "com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED"; + public static final String DATA_FILE_PATH_HEADER = "Data File: "; + public static final String DATA_FILE_PATH_FOOTER = "End Data File\n"; + // If set, we will push process association information in to procstats. static final boolean TRACK_PROCSTATS_ASSOCIATIONS = true; @@ -9595,17 +9598,33 @@ public class ActivityManagerService extends IActivityManager.Stub : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0); int dropboxMaxSize = Settings.Global.getInt( mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE); - int maxDataFileSize = dropboxMaxSize - sb.length() - - lines * RESERVED_BYTES_PER_LOGCAT_LINE; - if (dataFile != null && maxDataFileSize > 0) { - try { - sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize, - "\n\n[[TRUNCATED]]")); - } catch (IOException e) { - Slog.e(TAG, "Error reading " + dataFile, e); + if (dataFile != null) { + // Attach the stack traces file to the report so collectors can load them + // by file if they have access. + sb.append(DATA_FILE_PATH_HEADER) + .append(dataFile.getAbsolutePath()).append('\n'); + + int maxDataFileSize = dropboxMaxSize + - sb.length() + - lines * RESERVED_BYTES_PER_LOGCAT_LINE + - DATA_FILE_PATH_FOOTER.length(); + + if (maxDataFileSize > 0) { + // Inline dataFile contents if there is room. + try { + sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize, + "\n\n[[TRUNCATED]]\n")); + } catch (IOException e) { + Slog.e(TAG, "Error reading " + dataFile, e); + } } + + // Always append the footer, even there wasn't enough space to inline the + // dataFile contents. + sb.append(DATA_FILE_PATH_FOOTER); } + if (crashInfo != null && crashInfo.stackTrace != null) { sb.append(crashInfo.stackTrace); } diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index e41b6ae28e80..3ce92bc2737e 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -265,13 +265,6 @@ class BroadcastProcessQueue { @Nullable public BroadcastRecord enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex, @NonNull BroadcastConsumer deferredStatesApplyConsumer) { - // When updateDeferredStates() has already applied a deferred state to - // all pending items, apply to this new broadcast too - if (mLastDeferredStates && record.deferUntilActive - && (record.getDeliveryState(recordIndex) == BroadcastRecord.DELIVERY_PENDING)) { - deferredStatesApplyConsumer.accept(record, recordIndex); - } - // Ignore FLAG_RECEIVER_REPLACE_PENDING if the sender specified the policy using the // BroadcastOptions delivery group APIs. if (record.isReplacePending() @@ -294,6 +287,13 @@ class BroadcastProcessQueue { // with implicit responsiveness expectations. getQueueForBroadcast(record).addLast(newBroadcastArgs); onBroadcastEnqueued(record, recordIndex); + + // When updateDeferredStates() has already applied a deferred state to + // all pending items, apply to this new broadcast too + if (mLastDeferredStates && shouldBeDeferred() + && (record.getDeliveryState(recordIndex) == BroadcastRecord.DELIVERY_PENDING)) { + deferredStatesApplyConsumer.accept(record, recordIndex); + } return null; } @@ -1235,32 +1235,45 @@ class BroadcastProcessQueue { } /** - * Update {@link BroadcastRecord.DELIVERY_DEFERRED} states of all our + * Update {@link BroadcastRecord#DELIVERY_DEFERRED} states of all our * pending broadcasts, when needed. */ void updateDeferredStates(@NonNull BroadcastConsumer applyConsumer, @NonNull BroadcastConsumer clearConsumer) { // When all we have pending is deferred broadcasts, and we're cached, // then we want everything to be marked deferred - final boolean wantDeferredStates = (mCountDeferred > 0) - && (mCountDeferred == mCountEnqueued) && mProcessFreezable; + final boolean wantDeferredStates = shouldBeDeferred(); if (mLastDeferredStates != wantDeferredStates) { mLastDeferredStates = wantDeferredStates; if (wantDeferredStates) { forEachMatchingBroadcast((r, i) -> { - return r.deferUntilActive - && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_PENDING); + return (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_PENDING); }, applyConsumer, false); } else { forEachMatchingBroadcast((r, i) -> { - return r.deferUntilActive - && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED); + return (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED); }, clearConsumer, false); } } } + void clearDeferredStates(@NonNull BroadcastConsumer clearConsumer) { + if (mLastDeferredStates) { + mLastDeferredStates = false; + forEachMatchingBroadcast((r, i) -> { + return (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED); + }, clearConsumer, false); + } + } + + @VisibleForTesting + boolean shouldBeDeferred() { + if (mRunnableAtInvalidated) updateRunnableAt(); + return mRunnableAtReason == REASON_CACHED + || mRunnableAtReason == REASON_CACHED_INFINITE_DEFER; + } + /** * Check overall health, confirming things are in a reasonable state and * that we're not wedged. diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index b48169788180..5b54561c2164 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -479,6 +479,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { break; } + // Clear the deferred state of broadcasts in this queue as we are just about to + // deliver broadcasts to this process. + queue.clearDeferredStates(mBroadcastConsumerDeferClear); + // We might not have heard about a newly running process yet, so // consider refreshing if we think we're cold updateWarmProcess(queue); @@ -1567,12 +1571,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.resultExtras = null; }; - private final BroadcastConsumer mBroadcastConsumerDeferApply = (r, i) -> { + @VisibleForTesting + final BroadcastConsumer mBroadcastConsumerDeferApply = (r, i) -> { setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_DEFERRED, "mBroadcastConsumerDeferApply"); }; - private final BroadcastConsumer mBroadcastConsumerDeferClear = (r, i) -> { + @VisibleForTesting + final BroadcastConsumer mBroadcastConsumerDeferClear = (r, i) -> { setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_PENDING, "mBroadcastConsumerDeferClear"); }; diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index a0a7b2b48725..d0ab287785e3 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -1472,10 +1472,13 @@ public final class CachedAppOptimizer { } return; } + boolean processFreezableChangeReported = false; if (opt.isPendingFreeze()) { // Remove pending DO_FREEZE message mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app); opt.setPendingFreeze(false); + reportProcessFreezableChangedLocked(app); + processFreezableChangeReported = true; if (DEBUG_FREEZER) { Slog.d(TAG_AM, "Cancel freezing " + pid + " " + app.processName); } @@ -1524,7 +1527,9 @@ public final class CachedAppOptimizer { if (processKilled) { return; } - reportProcessFreezableChangedLocked(app); + if (!processFreezableChangeReported) { + reportProcessFreezableChangedLocked(app); + } long freezeTime = opt.getFreezeUnfreezeTime(); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 9bba08ad1bfd..ddccce5963b3 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2230,8 +2230,10 @@ public class OomAdjuster { || now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) { // This service has seen some activity within // recent memory, so we will keep its process ahead - // of the background processes. - if (adj > SERVICE_ADJ) { + // of the background processes. This does not apply + // to the SDK sandbox process since it should never + // be more important than its corresponding app. + if (!app.isSdkSandbox && adj > SERVICE_ADJ) { adj = SERVICE_ADJ; state.setAdjType("started-services"); if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index e5f763722bf1..7292ea6b19cb 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -148,6 +148,7 @@ import com.android.internal.app.IAppOpsStartedCallback; import com.android.internal.app.MessageSamplingConfig; import com.android.internal.compat.IPlatformCompat; import com.android.internal.os.Clock; +import com.android.internal.pm.pkg.component.ParsedAttribution; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; @@ -165,7 +166,6 @@ import com.android.server.pm.PackageManagerLocal; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; -import com.android.server.pm.pkg.component.ParsedAttribution; import com.android.server.policy.AppOpsPolicy; import dalvik.annotation.optimization.NeverCompile; diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java index 51cb9505ed4f..5c8dd0d427f9 100644 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; import android.media.AudioManager; +import android.media.Utils; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -167,8 +168,9 @@ import java.util.Objects; public String toString() { return "type: " + mDeviceType + " internal type: 0x" + Integer.toHexString(mInternalDeviceType) - + " addr: " + mDeviceAddress + " bt audio type: " - + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory) + + " addr: " + Utils.anonymizeBluetoothAddress(mInternalDeviceType, mDeviceAddress) + + " bt audio type: " + + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory) + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled; } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 7ba0827f2016..e9b102bc67b8 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -48,6 +48,7 @@ import android.media.IStrategyNonDefaultDevicesDispatcher; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.MediaMetrics; import android.media.MediaRecorder.AudioSource; +import android.media.Utils; import android.media.audiopolicy.AudioProductStrategy; import android.media.permission.ClearCallingIdentityContext; import android.media.permission.SafeCloseable; @@ -477,7 +478,7 @@ public class AudioDeviceInventory { return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType) + " (" + AudioSystem.getDeviceName(mDeviceType) + ") name:" + mDeviceName - + " addr:" + mDeviceAddress + + " addr:" + Utils.anonymizeBluetoothAddress(mDeviceType, mDeviceAddress) + " codec: " + Integer.toHexString(mDeviceCodecFormat) + " peer addr:" + mPeerDeviceAddress + " group:" + mGroupId @@ -532,7 +533,7 @@ public class AudioDeviceInventory { mApmConnectedDevices.forEach((keyType, valueAddress) -> { pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType) + " (" + AudioSystem.getDeviceName(keyType) - + ") addr:" + valueAddress); }); + + ") addr:" + Utils.anonymizeBluetoothAddress(keyType, valueAddress)); }); pw.println("\n" + prefix + "Preferred devices for capture preset:"); mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> { pw.println(" " + prefix + "capturePreset:" + capturePreset @@ -1789,7 +1790,8 @@ public class AudioDeviceInventory { // TODO: return; } else { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "A2DP device addr=" + address + " now available").printLog(TAG)); + "A2DP device addr=" + Utils.anonymizeBluetoothAddress(address) + + " now available").printLog(TAG)); } // Reset A2DP suspend state each time a new sink is connected @@ -2027,7 +2029,8 @@ public class AudioDeviceInventory { .equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) { // removing A2DP device not currently used by AudioPolicy, log but don't act on it AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( - "A2DP device " + address + " made unavailable, was not used")).printLog(TAG)); + "A2DP device " + Utils.anonymizeBluetoothAddress(address) + + " made unavailable, was not used")).printLog(TAG)); mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2DP device made unavailable, was not used") .record(); @@ -2043,13 +2046,15 @@ public class AudioDeviceInventory { if (res != AudioSystem.AUDIO_STATUS_OK) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "APM failed to make unavailable A2DP device addr=" + address + "APM failed to make unavailable A2DP device addr=" + + Utils.anonymizeBluetoothAddress(address) + " error=" + res).printLog(TAG)); // TODO: failed to disconnect, stop here // TODO: return; } else { AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( - "A2DP device addr=" + address + " made unavailable")).printLog(TAG)); + "A2DP device addr=" + Utils.anonymizeBluetoothAddress(address) + + " made unavailable")).printLog(TAG)); } mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); @@ -2238,7 +2243,8 @@ public class AudioDeviceInventory { // TODO: return; } else { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "LE Audio device addr=" + address + " now available").printLog(TAG)); + "LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address) + + " now available").printLog(TAG)); } // Reset LEA suspend state each time a new sink is connected mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */); @@ -2282,7 +2288,8 @@ public class AudioDeviceInventory { // TODO: return; } else { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "LE Audio device addr=" + address + " made unavailable").printLog(TAG)); + "LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address) + + " made unavailable").printLog(TAG)); } mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index b2ee6101efcd..1e38c0f25157 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -136,6 +136,7 @@ import android.media.MediaMetrics; import android.media.MediaRecorder.AudioSource; import android.media.PlayerBase; import android.media.Spatializer; +import android.media.Utils; import android.media.VolumeInfo; import android.media.VolumePolicy; import android.media.audiofx.AudioEffect; @@ -7470,7 +7471,7 @@ public class AudioService extends IAudioService.Stub sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:" + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:" - + device.getAddress() + " behavior:" + + Utils.anonymizeBluetoothAddress(device.getAddress()) + " behavior:" + AudioDeviceVolumeManager.volumeBehaviorName(deviceVolumeBehavior) + " pack:" + pkgName).printLog(TAG)); if (pkgName == null) { @@ -9641,7 +9642,7 @@ public class AudioService extends IAudioService.Stub private void avrcpSupportsAbsoluteVolume(String address, boolean support) { // address is not used for now, but may be used when multiple a2dp devices are supported sVolumeLogger.enqueue(new EventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr=" - + address + " support=" + support).printLog(TAG)); + + Utils.anonymizeBluetoothAddress(address) + " support=" + support).printLog(TAG)); mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support); setAvrcpAbsoluteVolumeSupported(support); } @@ -10539,11 +10540,11 @@ public class AudioService extends IAudioService.Stub AudioDeviceAttributes retrieveBluetoothAddressUncheked(@NonNull AudioDeviceAttributes ada) { Objects.requireNonNull(ada); if (AudioSystem.isBluetoothDevice(ada.getInternalType())) { - String anonymizedAddress = anonymizeBluetoothAddress(ada.getAddress()); + String anonymizedAddress = Utils.anonymizeBluetoothAddress(ada.getAddress()); for (AdiDeviceState ads : mDeviceBroker.getImmutableDeviceInventory()) { if (!(AudioSystem.isBluetoothDevice(ads.getInternalDeviceType()) && (ada.getInternalType() == ads.getInternalDeviceType()) - && anonymizedAddress.equals(anonymizeBluetoothAddress( + && anonymizedAddress.equals(Utils.anonymizeBluetoothAddress( ads.getDeviceAddress())))) { continue; } @@ -10554,19 +10555,6 @@ public class AudioService extends IAudioService.Stub return ada; } - /** - * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app - * Must match the implementation of BluetoothUtils.toAnonymizedAddress() - * @param address Mac address to be anonymized - * @return anonymized mac address - */ - static String anonymizeBluetoothAddress(String address) { - if (address == null || address.length() != "AA:BB:CC:DD:EE:FF".length()) { - return null; - } - return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length()); - } - private List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesList( List<AudioDeviceAttributes> devices) { if (isBluetoothPrividged()) { @@ -10590,7 +10578,7 @@ public class AudioService extends IAudioService.Stub return ada; } AudioDeviceAttributes res = new AudioDeviceAttributes(ada); - res.setAddress(anonymizeBluetoothAddress(ada.getAddress())); + res.setAddress(Utils.anonymizeBluetoothAddress(ada.getAddress())); return res; } diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index 283353ddc25d..c629b2b91603 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -25,6 +25,7 @@ import android.os.LocaleList; import android.util.ArraySet; import java.util.Set; +import java.util.function.Consumer; /** * Virtual device manager local service interface. @@ -32,29 +33,12 @@ import java.util.Set; */ public abstract class VirtualDeviceManagerInternal { - /** Interface to listen to the creation and destruction of virtual displays. */ - public interface VirtualDisplayListener { - /** Notifies that a virtual display was created. */ - void onVirtualDisplayCreated(int displayId); - - /** Notifies that a virtual display was removed. */ - void onVirtualDisplayRemoved(int displayId); - } - /** Interface to listen to the changes on the list of app UIDs running on any virtual device. */ public interface AppsOnVirtualDeviceListener { /** Notifies that running apps on any virtual device has changed */ void onAppsOnAnyVirtualDeviceChanged(Set<Integer> allRunningUids); } - /** Register a listener for the creation and destruction of virtual displays. */ - public abstract void registerVirtualDisplayListener( - @NonNull VirtualDisplayListener listener); - - /** Unregister a listener for the creation and destruction of virtual displays. */ - public abstract void unregisterVirtualDisplayListener( - @NonNull VirtualDisplayListener listener); - /** Register a listener for changes of running app UIDs on any virtual device. */ public abstract void registerAppsOnVirtualDeviceListener( @NonNull AppsOnVirtualDeviceListener listener); @@ -63,6 +47,14 @@ public abstract class VirtualDeviceManagerInternal { public abstract void unregisterAppsOnVirtualDeviceListener( @NonNull AppsOnVirtualDeviceListener listener); + /** Register a listener for removal of persistent device IDs. */ + public abstract void registerPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener); + + /** Unregister a listener for the removal of persistent device IDs. */ + public abstract void unregisterPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener); + /** * Notifies that the set of apps running on virtual devices has changed. * This method only notifies the listeners when the union of running UIDs on all virtual devices @@ -76,6 +68,11 @@ public abstract class VirtualDeviceManagerInternal { public abstract void onAuthenticationPrompt(int uid); /** + * Notifies the given persistent device IDs have been removed. + */ + public abstract void onPersistentDeviceIdsRemoved(Set<String> removedPersistentDeviceIds); + + /** * Gets the owner uid for a deviceId. * * @param deviceId which device we're asking about @@ -104,13 +101,6 @@ public abstract class VirtualDeviceManagerInternal { public abstract @NonNull ArraySet<Integer> getDeviceIdsForUid(int uid); /** - * Notifies that a virtual display is created. - * - * @param displayId The display id of the created virtual display. - */ - public abstract void onVirtualDisplayCreated(int displayId); - - /** * Notifies that a virtual display is removed. * * @param virtualDevice The virtual device where the virtual display located. diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 53fbe8f37046..a12243b8e4fa 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -22,7 +22,6 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO; @@ -45,12 +44,10 @@ import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -113,7 +110,6 @@ import android.os.Binder; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.CancellationSignal; -import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; import android.os.INetworkManagementService; @@ -152,7 +148,6 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; -import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.BinderUtils; import com.android.net.module.util.LinkPropertiesUtils; import com.android.net.module.util.NetdUtils; @@ -202,7 +197,6 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; /** * @hide @@ -1063,8 +1057,6 @@ public class Vpn { // Store mPackage since it might be reset or might be replaced with the other VPN app. final String oldPackage = mPackage; final boolean isPackageChanged = !Objects.equals(packageName, oldPackage); - // TODO: Remove "SdkLevel.isAtLeastT()" check once VpnManagerService is decoupled from - // ConnectivityServiceTest. // Only notify VPN apps that were already always-on, and only if the always-on provider // changed, or the lockdown mode changed. final boolean shouldNotifyOldPkg = isVpnApp(oldPackage) && mAlwaysOn @@ -1078,12 +1070,6 @@ public class Vpn { saveAlwaysOnPackage(); - // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from - // ConnectivityServiceTest. - if (!SdkLevel.isAtLeastT()) { - return true; - } - if (shouldNotifyOldPkg) { // If both of shouldNotifyOldPkg & isPackageChanged are true, that means the // always-on of old package is disabled or the old package is replaced with the new @@ -1984,9 +1970,7 @@ public class Vpn { for (String app : packageNames) { int uid = getAppUid(mContext, app, userId); if (uid != -1) uids.add(uid); - // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from - // ConnectivityServiceTest. - if (Process.isApplicationUid(uid) && SdkLevel.isAtLeastT()) { + if (Process.isApplicationUid(uid)) { uids.add(Process.toSdkSandboxUid(uid)); } } @@ -2297,15 +2281,6 @@ public class Vpn { private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() { @Override - public void interfaceStatusChanged(String interfaze, boolean up) { - synchronized (Vpn.this) { - if (!up && mVpnRunner != null && mVpnRunner instanceof LegacyVpnRunner) { - ((LegacyVpnRunner) mVpnRunner).exitIfOuterInterfaceIs(interfaze); - } - } - } - - @Override public void interfaceRemoved(String interfaze) { synchronized (Vpn.this) { if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { @@ -2556,17 +2531,6 @@ public class Vpn { private native boolean jniAddAddress(String interfaze, String address, int prefixLen); private native boolean jniDelAddress(String interfaze, String address, int prefixLen); - private static RouteInfo findIPv4DefaultRoute(LinkProperties prop) { - for (RouteInfo route : prop.getAllRoutes()) { - // Currently legacy VPN only works on IPv4. - if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) { - return route; - } - } - - throw new IllegalStateException("Unable to find IPv4 default gateway"); - } - private void enforceNotRestrictedUser() { final long token = Binder.clearCallingIdentity(); try { @@ -2665,10 +2629,6 @@ public class Vpn { throw new SecurityException("Restricted users cannot establish VPNs"); } - final RouteInfo ipv4DefaultRoute = findIPv4DefaultRoute(egress); - final String gateway = ipv4DefaultRoute.getGateway().getHostAddress(); - final String iface = ipv4DefaultRoute.getInterface(); - // Load certificates. String privateKey = ""; String userCert = ""; @@ -2700,8 +2660,6 @@ public class Vpn { throw new IllegalStateException("Cannot load credentials"); } - // Prepare arguments for racoon. - String[] racoon = null; switch (profile.type) { case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // Secret key is still just the alias (not the actual private key). The private key @@ -2731,109 +2689,9 @@ public class Vpn { // profile. startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN); return; - case VpnProfile.TYPE_L2TP_IPSEC_PSK: - racoon = new String[] { - iface, profile.server, "udppsk", profile.ipsecIdentifier, - profile.ipsecSecret, "1701", - }; - break; - case VpnProfile.TYPE_L2TP_IPSEC_RSA: - racoon = new String[] { - iface, profile.server, "udprsa", makeKeystoreEngineGrantString(privateKey), - userCert, caCert, serverCert, "1701", - }; - break; - case VpnProfile.TYPE_IPSEC_XAUTH_PSK: - racoon = new String[] { - iface, profile.server, "xauthpsk", profile.ipsecIdentifier, - profile.ipsecSecret, profile.username, profile.password, "", gateway, - }; - break; - case VpnProfile.TYPE_IPSEC_XAUTH_RSA: - racoon = new String[] { - iface, profile.server, "xauthrsa", makeKeystoreEngineGrantString(privateKey), - userCert, caCert, serverCert, profile.username, profile.password, "", gateway, - }; - break; - case VpnProfile.TYPE_IPSEC_HYBRID_RSA: - racoon = new String[] { - iface, profile.server, "hybridrsa", - caCert, serverCert, profile.username, profile.password, "", gateway, - }; - break; - } - - // Prepare arguments for mtpd. MTU/MRU calculated conservatively. Only IPv4 supported - // because LegacyVpn. - // 1500 - 60 (Carrier-internal IPv6 + UDP + GTP) - 10 (PPP) - 16 (L2TP) - 8 (UDP) - // - 77 (IPsec w/ SHA-2 512, 256b trunc-len, AES-CBC) - 8 (UDP encap) - 20 (IPv4) - // - 28 (464xlat) - String[] mtpd = null; - switch (profile.type) { - case VpnProfile.TYPE_PPTP: - mtpd = new String[] { - iface, "pptp", profile.server, "1723", - "name", profile.username, "password", profile.password, - "linkname", "vpn", "refuse-eap", "nodefaultroute", - "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270", - (profile.mppe ? "+mppe" : "nomppe"), - }; - if (profile.mppe) { - // Disallow PAP authentication when MPPE is requested, as MPPE cannot work - // with PAP anyway, and users may not expect PAP (plain text) to be used when - // MPPE was requested. - mtpd = Arrays.copyOf(mtpd, mtpd.length + 1); - mtpd[mtpd.length - 1] = "-pap"; - } - break; - case VpnProfile.TYPE_L2TP_IPSEC_PSK: - case VpnProfile.TYPE_L2TP_IPSEC_RSA: - mtpd = new String[] { - iface, "l2tp", profile.server, "1701", profile.l2tpSecret, - "name", profile.username, "password", profile.password, - "linkname", "vpn", "refuse-eap", "nodefaultroute", - "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270", - }; - break; } - VpnConfig config = new VpnConfig(); - config.legacy = true; - config.user = profile.key; - config.interfaze = iface; - config.session = profile.name; - config.isMetered = false; - config.proxyInfo = profile.proxy; - if (underlying != null) { - config.underlyingNetworks = new Network[] { underlying }; - } - - config.addLegacyRoutes(profile.routes); - if (!profile.dnsServers.isEmpty()) { - config.dnsServers = Arrays.asList(profile.dnsServers.split(" +")); - } - if (!profile.searchDomains.isEmpty()) { - config.searchDomains = Arrays.asList(profile.searchDomains.split(" +")); - } - startLegacyVpn(config, racoon, mtpd, profile); - } - - private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd, - VpnProfile profile) { - stopVpnRunnerPrivileged(); - - // Prepare for the new request. - prepareInternal(VpnConfig.LEGACY_VPN); - updateState(DetailedState.CONNECTING, "startLegacyVpn"); - - // Start a new LegacyVpnRunner and we are done! - mVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile); - startLegacyVpnRunner(); - } - - @VisibleForTesting - protected void startLegacyVpnRunner() { - mVpnRunner.start(); + throw new UnsupportedOperationException("Legacy VPN is deprecated"); } /** @@ -2851,17 +2709,7 @@ public class Vpn { return; } - final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner; mVpnRunner.exit(); - - // LegacyVpn uses daemons that must be shut down before new ones are brought up. - // The same limitation does not apply to Platform VPNs. - if (isLegacyVpn) { - synchronized (LegacyVpnRunner.TAG) { - // wait for old thread to completely finish before spinning up - // new instance, otherwise state updates can be out of order. - } - } } /** @@ -4143,9 +3991,7 @@ public class Vpn { // Ignore stale runner. if (mVpnRunner != this) return; - // TODO(b/230548427): Remove SDK check once VPN related stuff are - // decoupled from ConnectivityServiceTest. - if (SdkLevel.isAtLeastT() && category != null && isVpnApp(mPackage)) { + if (category != null && isVpnApp(mPackage)) { sendEventToVpnManagerApp(category, errorClass, errorCode, getPackage(), mSessionKey, makeVpnProfileStateLocked(), mActiveNetwork, @@ -4256,343 +4102,6 @@ public class Vpn { } } - /** - * Bringing up a VPN connection takes time, and that is all this thread - * does. Here we have plenty of time. The only thing we need to take - * care of is responding to interruptions as soon as possible. Otherwise - * requests will pile up. This could be done in a Handler as a state - * machine, but it is much easier to read in the current form. - */ - private class LegacyVpnRunner extends VpnRunner { - private static final String TAG = "LegacyVpnRunner"; - - private final String[] mDaemons; - private final String[][] mArguments; - private final LocalSocket[] mSockets; - private final String mOuterInterface; - private final AtomicInteger mOuterConnection = - new AtomicInteger(ConnectivityManager.TYPE_NONE); - private final VpnProfile mProfile; - - private long mBringupStartTime = -1; - - /** - * Watch for the outer connection (passing in the constructor) going away. - */ - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (!mEnableTeardown) return; - - if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - if (intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, - ConnectivityManager.TYPE_NONE) == mOuterConnection.get()) { - NetworkInfo info = (NetworkInfo)intent.getExtra( - ConnectivityManager.EXTRA_NETWORK_INFO); - if (info != null && !info.isConnectedOrConnecting()) { - try { - mObserver.interfaceStatusChanged(mOuterInterface, false); - } catch (RemoteException e) {} - } - } - } - } - }; - - // GuardedBy("Vpn.this") (annotation can't be applied to constructor) - LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) { - super(TAG); - if (racoon == null && mtpd == null) { - throw new IllegalArgumentException( - "Arguments to racoon and mtpd must not both be null"); - } - mConfig = config; - mDaemons = new String[] {"racoon", "mtpd"}; - // TODO: clear arguments from memory once launched - mArguments = new String[][] {racoon, mtpd}; - mSockets = new LocalSocket[mDaemons.length]; - - // This is the interface which VPN is running on, - // mConfig.interfaze will change to point to OUR - // internal interface soon. TODO - add inner/outer to mconfig - // TODO - we have a race - if the outer iface goes away/disconnects before we hit this - // we will leave the VPN up. We should check that it's still there/connected after - // registering - mOuterInterface = mConfig.interfaze; - - mProfile = profile; - - if (!TextUtils.isEmpty(mOuterInterface)) { - for (Network network : mConnectivityManager.getAllNetworks()) { - final LinkProperties lp = mConnectivityManager.getLinkProperties(network); - if (lp != null && lp.getAllInterfaceNames().contains(mOuterInterface)) { - final NetworkInfo netInfo = mConnectivityManager.getNetworkInfo(network); - if (netInfo != null) { - mOuterConnection.set(netInfo.getType()); - break; - } - } - } - } - - IntentFilter filter = new IntentFilter(); - filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - mContext.registerReceiver(mBroadcastReceiver, filter); - } - - /** - * Checks if the parameter matches the underlying interface - * - * <p>If the underlying interface is torn down, the LegacyVpnRunner also should be. It has - * no ability to migrate between interfaces (or Networks). - */ - public void exitIfOuterInterfaceIs(String interfaze) { - if (interfaze.equals(mOuterInterface)) { - Log.i(TAG, "Legacy VPN is going down with " + interfaze); - exitVpnRunner(); - } - } - - /** Tears down this LegacyVpn connection */ - @Override - public void exitVpnRunner() { - // We assume that everything is reset after stopping the daemons. - interrupt(); - - // Always disconnect. This may be called again in cleanupVpnStateLocked() if - // exitVpnRunner() was called from exit(), but it will be a no-op. - agentDisconnect(); - try { - mContext.unregisterReceiver(mBroadcastReceiver); - } catch (IllegalArgumentException e) {} - } - - @Override - public void run() { - // Wait for the previous thread since it has been interrupted. - Log.v(TAG, "Waiting"); - synchronized (TAG) { - Log.v(TAG, "Executing"); - try { - bringup(); - waitForDaemonsToStop(); - interrupted(); // Clear interrupt flag if execute called exit. - } catch (InterruptedException e) { - } finally { - for (LocalSocket socket : mSockets) { - IoUtils.closeQuietly(socket); - } - // This sleep is necessary for racoon to successfully complete sending delete - // message to server. - try { - Thread.sleep(50); - } catch (InterruptedException e) { - } - for (String daemon : mDaemons) { - mDeps.stopService(daemon); - } - } - agentDisconnect(); - } - } - - private void checkInterruptAndDelay(boolean sleepLonger) throws InterruptedException { - long now = SystemClock.elapsedRealtime(); - if (now - mBringupStartTime <= 60000) { - Thread.sleep(sleepLonger ? 200 : 1); - } else { - updateState(DetailedState.FAILED, "checkpoint"); - throw new IllegalStateException("VPN bringup took too long"); - } - } - - private void checkAndFixupArguments(@NonNull final InetAddress endpointAddress) { - final String endpointAddressString = endpointAddress.getHostAddress(); - // Perform some safety checks before inserting the address in place. - // Position 0 in mDaemons and mArguments must be racoon, and position 1 must be mtpd. - if (!"racoon".equals(mDaemons[0]) || !"mtpd".equals(mDaemons[1])) { - throw new IllegalStateException("Unexpected daemons order"); - } - - // Respectively, the positions at which racoon and mtpd take the server address - // argument are 1 and 2. Not all types of VPN require both daemons however, and - // in that case the corresponding argument array is null. - if (mArguments[0] != null) { - if (!mProfile.server.equals(mArguments[0][1])) { - throw new IllegalStateException("Invalid server argument for racoon"); - } - mArguments[0][1] = endpointAddressString; - } - - if (mArguments[1] != null) { - if (!mProfile.server.equals(mArguments[1][2])) { - throw new IllegalStateException("Invalid server argument for mtpd"); - } - mArguments[1][2] = endpointAddressString; - } - } - - private void bringup() { - // Catch all exceptions so we can clean up a few things. - try { - // resolve never returns null. If it does because of some bug, it will be - // caught by the catch() block below and cleanup gracefully. - final InetAddress endpointAddress = mDeps.resolve(mProfile.server); - - // Big hack : dynamically replace the address of the server in the arguments - // with the resolved address. - checkAndFixupArguments(endpointAddress); - - // Initialize the timer. - mBringupStartTime = SystemClock.elapsedRealtime(); - - // Wait for the daemons to stop. - for (String daemon : mDaemons) { - while (!mDeps.isServiceStopped(daemon)) { - checkInterruptAndDelay(true); - } - } - - // Clear the previous state. - final File state = mDeps.getStateFile(); - state.delete(); - if (state.exists()) { - throw new IllegalStateException("Cannot delete the state"); - } - new File("/data/misc/vpn/abort").delete(); - - updateState(DetailedState.CONNECTING, "execute"); - - // Start the daemon with arguments. - for (int i = 0; i < mDaemons.length; ++i) { - String[] arguments = mArguments[i]; - if (arguments == null) { - continue; - } - - // Start the daemon. - String daemon = mDaemons[i]; - mDeps.startService(daemon); - - // Wait for the daemon to start. - while (!mDeps.isServiceRunning(daemon)) { - checkInterruptAndDelay(true); - } - - // Create the control socket. - mSockets[i] = new LocalSocket(); - - // Wait for the socket to connect and send over the arguments. - mDeps.sendArgumentsToDaemon(daemon, mSockets[i], arguments, - this::checkInterruptAndDelay); - } - - // Wait for the daemons to create the new state. - while (!state.exists()) { - // Check if a running daemon is dead. - for (int i = 0; i < mDaemons.length; ++i) { - String daemon = mDaemons[i]; - if (mArguments[i] != null && !mDeps.isServiceRunning(daemon)) { - throw new IllegalStateException(daemon + " is dead"); - } - } - checkInterruptAndDelay(true); - } - - // Now we are connected. Read and parse the new state. - String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1); - if (parameters.length != 7) { - throw new IllegalStateException("Cannot parse the state: '" - + String.join("', '", parameters) + "'"); - } - - // Set the interface and the addresses in the config. - synchronized (Vpn.this) { - mConfig.interfaze = parameters[0].trim(); - - mConfig.addLegacyAddresses(parameters[1]); - // Set the routes if they are not set in the config. - if (mConfig.routes == null || mConfig.routes.isEmpty()) { - mConfig.addLegacyRoutes(parameters[2]); - } - - // Set the DNS servers if they are not set in the config. - if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) { - String dnsServers = parameters[3].trim(); - if (!dnsServers.isEmpty()) { - mConfig.dnsServers = Arrays.asList(dnsServers.split(" ")); - } - } - - // Set the search domains if they are not set in the config. - if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) { - String searchDomains = parameters[4].trim(); - if (!searchDomains.isEmpty()) { - mConfig.searchDomains = Arrays.asList(searchDomains.split(" ")); - } - } - - // Add a throw route for the VPN server endpoint, if one was specified. - if (endpointAddress instanceof Inet4Address) { - mConfig.routes.add(new RouteInfo( - new IpPrefix(endpointAddress, 32), null /*gateway*/, - null /*iface*/, RTN_THROW)); - } else if (endpointAddress instanceof Inet6Address) { - mConfig.routes.add(new RouteInfo( - new IpPrefix(endpointAddress, 128), null /*gateway*/, - null /*iface*/, RTN_THROW)); - } else { - Log.e(TAG, "Unknown IP address family for VPN endpoint: " - + endpointAddress); - } - - // Here is the last step and it must be done synchronously. - // Set the start time - mConfig.startTime = SystemClock.elapsedRealtime(); - - // Check if the thread was interrupted while we were waiting on the lock. - checkInterruptAndDelay(false); - - // Check if the interface is gone while we are waiting. - if (!mDeps.isInterfacePresent(Vpn.this, mConfig.interfaze)) { - throw new IllegalStateException(mConfig.interfaze + " is gone"); - } - - // Now INetworkManagementEventObserver is watching our back. - mInterface = mConfig.interfaze; - prepareStatusIntent(); - - agentConnect(); - - Log.i(TAG, "Connected!"); - } - } catch (Exception e) { - Log.i(TAG, "Aborting", e); - updateState(DetailedState.FAILED, e.getMessage()); - exitVpnRunner(); - } - } - - /** - * Check all daemons every two seconds. Return when one of them is stopped. - * The caller will move to the disconnected state when this function returns, - * which can happen if a daemon failed or if the VPN was torn down. - */ - private void waitForDaemonsToStop() throws InterruptedException { - if (!mNetworkInfo.isConnected()) { - return; - } - while (true) { - Thread.sleep(2000); - for (int i = 0; i < mDaemons.length; i++) { - if (mArguments[i] != null && mDeps.isServiceStopped(mDaemons[i])) { - return; - } - } - } - } - } - private void verifyCallingUidAndPackage(String packageName) { mDeps.verifyCallingUidAndPackage(mContext, packageName, mUserId); } @@ -4839,11 +4348,9 @@ public class Vpn { // Build intent first because the sessionKey will be reset after performing // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in // VpnRunner.exit() to prevent design being changed in the future. - // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from - // ConnectivityServiceTest. final int ownerUid = mOwnerUID; Intent intent = null; - if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) { + if (isVpnApp(mPackage)) { intent = buildVpnManagerEventIntent( VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER, -1 /* errorClass */, -1 /* errorCode*/, mPackage, @@ -4884,12 +4391,8 @@ public class Vpn { // The underlying network, NetworkCapabilities and LinkProperties are not // necessary to send to VPN app since the purpose of this event is to notify // VPN app that VPN is deactivated by the user. - // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from - // ConnectivityServiceTest. - if (SdkLevel.isAtLeastT()) { - mEventChanges.log("[VMEvent] " + packageName + " stopped"); - sendEventToVpnManagerApp(intent, packageName); - } + mEventChanges.log("[VMEvent] " + packageName + " stopped"); + sendEventToVpnManagerApp(intent, packageName); } private boolean storeAppExclusionList(@NonNull String packageName, diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index d023913c9694..fb6c9e3cab9d 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -724,8 +724,9 @@ public class DisplayModeDirector { || mode.getPhysicalHeight() > maxAllowedHeight || mode.getPhysicalWidth() < outSummary.minWidth || mode.getPhysicalHeight() < outSummary.minHeight - || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate - || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) { + || mode.getRefreshRate() < (outSummary.minPhysicalRefreshRate - FLOAT_TOLERANCE) + || mode.getRefreshRate() > (outSummary.maxPhysicalRefreshRate + FLOAT_TOLERANCE) + ) { continue; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index d3eedd7e089a..168715713f8d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -191,9 +191,16 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { launchDeviceDiscovery(); startQueuedActions(); if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { - mService.sendCecCommand( - HdmiCecMessageBuilder.buildRequestActiveSource( - getDeviceInfo().getLogicalAddress())); + addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + if (result != HdmiControlManager.RESULT_SUCCESS) { + mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource( + getDeviceInfo().getLogicalAddress(), + getDeviceInfo().getPhysicalAddress())); + } + } + })); } } @@ -1325,6 +1332,8 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { removeAction(TimerRecordingAction.class); removeAction(NewDeviceAction.class); removeAction(AbsoluteVolumeAudioStatusAction.class); + // Remove pending actions. + removeAction(RequestActiveSourceAction.class); // Keep SAM enabled if eARC is enabled, unless we're going to Standby. if (initiatedByCec || !mService.isEarcEnabled()){ diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java new file mode 100644 index 000000000000..017c86d4b363 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java @@ -0,0 +1,66 @@ +/* + * 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.hdmi; + +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; + +/** + * Feature action that sends <Request Active Source> message and waits for <Active Source>. + */ +public class RequestActiveSourceAction extends HdmiCecFeatureAction { + private static final String TAG = "RequestActiveSourceAction"; + + // State to wait for the <Active Source> message. + private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 1; + + RequestActiveSourceAction(HdmiCecLocalDevice source, IHdmiControlCallback callback) { + super(source, callback); + } + + @Override + boolean start() { + Slog.v(TAG, "RequestActiveSourceAction started."); + + sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress())); + + mState = STATE_WAIT_FOR_ACTIVE_SOURCE; + addTimer(mState, HdmiConfig.TIMEOUT_MS); + return true; + } + + @Override + boolean processCommand(HdmiCecMessage cmd) { + // The action finishes successfully if the <Active Source> message is received. + // {@link HdmiCecLocalDevice#onMessage} handles this message, so false is returned. + if (cmd.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) { + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + } + return false; + } + + @Override + void handleTimerEvent(int state) { + if (mState != state) { + return; + } + if (mState == STATE_WAIT_FOR_ACTIVE_SOURCE) { + finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); + } + } +} diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java index 2d6b013d11c1..035a7485fe86 100644 --- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java +++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java @@ -39,7 +39,6 @@ final class AutofillSuggestionsController { private static final String TAG = AutofillSuggestionsController.class.getSimpleName(); @NonNull private final InputMethodManagerService mService; - @NonNull private final InputMethodUtils.InputMethodSettings mSettings; private static final class CreateInlineSuggestionsRequest { @NonNull final InlineSuggestionsRequestInfo mRequestInfo; @@ -76,7 +75,6 @@ final class AutofillSuggestionsController { AutofillSuggestionsController(@NonNull InputMethodManagerService service) { mService = service; - mSettings = mService.mSettings; } @GuardedBy("ImfLock.class") @@ -88,7 +86,7 @@ final class AutofillSuggestionsController { final InputMethodInfo imi = mService.queryInputMethodForCurrentUserLocked( mService.getSelectedMethodIdLocked()); try { - if (userId == mSettings.getCurrentUserId() + if (userId == mService.getCurrentImeUserIdLocked() && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) { mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest( requestInfo, callback, imi.getPackageName()); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index e76aa1aad0cc..c8c0482f5a9d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -63,7 +63,6 @@ final class InputMethodBindingController { @NonNull private final InputMethodManagerService mService; @NonNull private final Context mContext; - @NonNull private final InputMethodUtils.InputMethodSettings mSettings; @NonNull private final PackageManagerInternal mPackageManagerInternal; @NonNull private final WindowManagerInternal mWindowManagerInternal; @@ -113,7 +112,6 @@ final class InputMethodBindingController { int imeConnectionBindFlags, CountDownLatch latchForTesting) { mService = service; mContext = mService.mContext; - mSettings = mService.mSettings; mPackageManagerInternal = mService.mPackageManagerInternal; mWindowManagerInternal = mService.mWindowManagerInternal; mImeConnectionBindFlags = imeConnectionBindFlags; @@ -322,7 +320,7 @@ final class InputMethodBindingController { private void updateCurrentMethodUid() { final String curMethodPackage = mCurIntent.getComponent().getPackageName(); final int curMethodUid = mPackageManagerInternal.getPackageUid( - curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId()); + curMethodPackage, 0 /* flags */, mService.getCurrentImeUserIdLocked()); if (curMethodUid < 0) { Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage); mCurMethodUid = Process.INVALID_UID; @@ -478,7 +476,7 @@ final class InputMethodBindingController { return false; } return mContext.bindServiceAsUser(mCurIntent, conn, flags, - new UserHandle(mSettings.getCurrentUserId())); + new UserHandle(mService.getCurrentImeUserIdLocked())); } @GuardedBy("ImfLock.class") diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8171e71245a7..d656f204ebaa 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1728,6 +1728,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub registerDeviceListenerAndCheckStylusSupport(); } + @GuardedBy("ImfLock.class") + @UserIdInt + int getCurrentImeUserIdLocked() { + return mSettings.getCurrentUserId(); + } + private final class InkWindowInitializer implements Runnable { public void run() { synchronized (ImfLock.class) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 3b5ef4cadfdf..efa1e0d66f35 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -79,7 +79,8 @@ final class InputMethodMenuController { synchronized (ImfLock.class) { final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked() - && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId()); + && mWindowManagerInternal.isKeyguardSecure( + mService.getCurrentImeUserIdLocked()); final String lastInputMethodId = mSettings.getSelectedInputMethod(); int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1bdd402cf0b5..8c753671d77d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -179,6 +179,8 @@ import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; +import android.companion.AssociationInfo; +import android.companion.AssociationRequest; import android.companion.ICompanionDeviceManager; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; @@ -358,6 +360,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -546,6 +549,15 @@ public class NotificationManagerService extends SystemService { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION = 264179692L; + /** + * App calls to {@link android.app.NotificationManager#setInterruptionFilter} and + * {@link android.app.NotificationManager#setNotificationPolicy} manage DND through the + * creation and activation of an implicit {@link android.app.AutomaticZenRule}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + static final long MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES = 308670109L; + private static final Duration POST_WAKE_LOCK_TIMEOUT = Duration.ofSeconds(30); private IActivityManager mAm; @@ -5234,13 +5246,23 @@ public class NotificationManagerService extends SystemService { } } + // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined. @Override public List<ZenModeConfig.ZenRule> getZenRules() throws RemoteException { - enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules"); + enforcePolicyAccess(Binder.getCallingUid(), "getZenRules"); return mZenModeHelper.getZenRules(); } @Override + public Map<String, AutomaticZenRule> getAutomaticZenRules() { + if (!android.app.Flags.modesApi()) { + throw new IllegalStateException("getAutomaticZenRules called with flag off!"); + } + enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules"); + return mZenModeHelper.getAutomaticZenRules(); + } + + @Override public AutomaticZenRule getAutomaticZenRule(String id) throws RemoteException { Objects.requireNonNull(id, "Id is null"); enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRule"); @@ -5343,6 +5365,12 @@ public class NotificationManagerService extends SystemService { if (zen == -1) throw new IllegalArgumentException("Invalid filter: " + filter); final int callingUid = Binder.getCallingUid(); final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi(); + + if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) { + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, callingUid, zen); + return; + } + final long identity = Binder.clearCallingIdentity(); try { mZenModeHelper.setManualZenMode(zen, null, pkg, "setInterruptionFilter", @@ -5426,6 +5454,16 @@ public class NotificationManagerService extends SystemService { } } + private boolean canManageGlobalZenPolicy(String callingPkg, int callingUid) { + boolean isCompatChangeEnabled = Binder.withCleanCallingIdentity( + () -> CompatChanges.isChangeEnabled(MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES, + callingUid)); + return !isCompatChangeEnabled + || isCallerIsSystemOrSystemUi() + || hasCompanionDevice(callingPkg, UserHandle.getUserId(callingUid), + AssociationRequest.DEVICE_PROFILE_WATCH); + } + private void enforcePolicyAccess(String pkg, String method) { if (PackageManager.PERMISSION_GRANTED == getContext().checkCallingPermission( android.Manifest.permission.MANAGE_NOTIFICATIONS)) { @@ -5619,6 +5657,10 @@ public class NotificationManagerService extends SystemService { @Override public Policy getNotificationPolicy(String pkg) { + final int callingUid = Binder.getCallingUid(); + if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) { + return mZenModeHelper.getNotificationPolicyFromImplicitZenRule(pkg); + } final long identity = Binder.clearCallingIdentity(); try { return mZenModeHelper.getNotificationPolicy(); @@ -5649,6 +5691,10 @@ public class NotificationManagerService extends SystemService { enforcePolicyAccess(pkg, "setNotificationPolicy"); int callingUid = Binder.getCallingUid(); boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi(); + + boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi() + && !canManageGlobalZenPolicy(pkg, callingUid); + final long identity = Binder.clearCallingIdentity(); try { final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkg, @@ -5687,16 +5733,21 @@ public class NotificationManagerService extends SystemService { policy = new Policy(policy.priorityCategories, policy.priorityCallSenders, policy.priorityMessageSenders, newVisualEffects, policy.priorityConversationSenders); - ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion, policy); - mZenModeHelper.setNotificationPolicy(policy, callingUid, isSystemOrSystemUi); + + if (shouldApplyAsImplicitRule) { + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy); + } else { + ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion, + policy); + mZenModeHelper.setNotificationPolicy(policy, callingUid, isSystemOrSystemUi); + } } catch (RemoteException e) { + Slog.e(TAG, "Failed to set notification policy", e); } finally { Binder.restoreCallingIdentity(identity); } } - - @Override public List<String> getEnabledNotificationListenerPackages() { checkCallerIsSystem(); @@ -10556,6 +10607,12 @@ public class NotificationManagerService extends SystemService { } boolean hasCompanionDevice(ManagedServiceInfo info) { + return hasCompanionDevice(info.component.getPackageName(), + info.userid, /* withDeviceProfile= */ null); + } + + private boolean hasCompanionDevice(String pkg, @UserIdInt int userId, + @Nullable @AssociationRequest.DeviceProfile String withDeviceProfile) { if (mCompanionManager == null) { mCompanionManager = getCompanionManager(); } @@ -10565,17 +10622,19 @@ public class NotificationManagerService extends SystemService { } final long identity = Binder.clearCallingIdentity(); try { - List<?> associations = mCompanionManager.getAssociations( - info.component.getPackageName(), info.userid); - if (!ArrayUtils.isEmpty(associations)) { - return true; + List<AssociationInfo> associations = mCompanionManager.getAssociations(pkg, userId); + for (AssociationInfo association : associations) { + if (withDeviceProfile == null || withDeviceProfile.equals( + association.getDeviceProfile())) { + return true; + } } } catch (SecurityException se) { // Not a privileged listener } catch (RemoteException re) { Slog.e(TAG, "Cannot reach companion device service", re); } catch (Exception e) { - Slog.e(TAG, "Cannot verify listener " + info, e); + Slog.e(TAG, "Cannot verify caller pkg=" + pkg + ", userId=" + userId, e); } finally { Binder.restoreCallingIdentity(identity); } @@ -11401,17 +11460,16 @@ public class NotificationManagerService extends SystemService { } } } - } - // clean up anything in the disallowed pkgs list - for (int i = 0; i < pkgList.length; i++) { - String pkg = pkgList[i]; - for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) { - NotificationListenerFilter nlf = - mRequestedNotificationListeners.valueAt(j); - - VersionedPackage ai = new VersionedPackage(pkg, uidList[i]); - nlf.removePackage(ai); + // Clean up removed package from the disallowed packages list + for (int i = 0; i < pkgList.length; i++) { + String pkg = pkgList[i]; + for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) { + NotificationListenerFilter nlf = + mRequestedNotificationListeners.valueAt(j); + VersionedPackage ai = new VersionedPackage(pkg, uidList[i]); + nlf.removePackage(ai); + } } } } diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java index 8f5676b31594..9106c33d26db 100644 --- a/services/core/java/com/android/server/notification/SnoozeHelper.java +++ b/services/core/java/com/android/server/notification/SnoozeHelper.java @@ -118,7 +118,10 @@ public final class SnoozeHelper { protected boolean canSnooze(int numberToSnooze) { synchronized (mLock) { - if ((mSnoozedNotifications.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT) { + if ((mSnoozedNotifications.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT + || (mPersistedSnoozedNotifications.size() + + mPersistedSnoozedNotificationsWithContext.size() + numberToSnooze) + > CONCURRENT_SNOOZE_LIMIT) { return false; } } @@ -357,6 +360,9 @@ public final class SnoozeHelper { if (groupSummaryKey != null) { NotificationRecord record = mSnoozedNotifications.remove(groupSummaryKey); + String trimmedKey = getTrimmedString(groupSummaryKey); + mPersistedSnoozedNotificationsWithContext.remove(trimmedKey); + mPersistedSnoozedNotifications.remove(trimmedKey); if (record != null && !record.isCanceled) { Runnable runnable = () -> { diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/services/core/java/com/android/server/notification/ZenAdapters.java new file mode 100644 index 000000000000..2a65aff7f28d --- /dev/null +++ b/services/core/java/com/android/server/notification/ZenAdapters.java @@ -0,0 +1,78 @@ +/* + * 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.notification; + +import android.app.NotificationManager.Policy; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenPolicy; + +/** + * Converters between different Zen representations. + */ +class ZenAdapters { + + static ZenPolicy notificationPolicyToZenPolicy(Policy policy) { + ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder() + .allowAlarms(policy.allowAlarms()) + .allowCalls( + policy.allowCalls() + ? ZenModeConfig.getZenPolicySenders(policy.allowCallsFrom()) + : ZenPolicy.PEOPLE_TYPE_NONE) + .allowConversations( + policy.allowConversations() + ? notificationPolicyConversationSendersToZenPolicy( + policy.allowConversationsFrom()) + : ZenPolicy.CONVERSATION_SENDERS_NONE) + .allowEvents(policy.allowEvents()) + .allowMedia(policy.allowMedia()) + .allowMessages( + policy.allowMessages() + ? ZenModeConfig.getZenPolicySenders(policy.allowMessagesFrom()) + : ZenPolicy.PEOPLE_TYPE_NONE) + .allowReminders(policy.allowReminders()) + .allowRepeatCallers(policy.allowRepeatCallers()) + .allowSystem(policy.allowSystem()); + + if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) { + zenPolicyBuilder.showBadges(policy.showBadges()) + .showFullScreenIntent(policy.showFullScreenIntents()) + .showInAmbientDisplay(policy.showAmbient()) + .showInNotificationList(policy.showInNotificationList()) + .showLights(policy.showLights()) + .showPeeking(policy.showPeeking()) + .showStatusBarIcons(policy.showStatusBarIcons()); + } + + return zenPolicyBuilder.build(); + } + + @ZenPolicy.ConversationSenders + private static int notificationPolicyConversationSendersToZenPolicy( + int npPriorityConversationSenders) { + switch (npPriorityConversationSenders) { + case Policy.CONVERSATION_SENDERS_ANYONE: + return ZenPolicy.CONVERSATION_SENDERS_ANYONE; + case Policy.CONVERSATION_SENDERS_IMPORTANT: + return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; + case Policy.CONVERSATION_SENDERS_NONE: + return ZenPolicy.CONVERSATION_SENDERS_NONE; + case Policy.CONVERSATION_SENDERS_UNSET: + default: + return ZenPolicy.CONVERSATION_SENDERS_UNSET; + } + } +} diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 762c1a162302..a1704c6619ad 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -27,6 +27,7 @@ import static android.service.notification.NotificationServiceProto.ROOT_CONFIG; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.AppOpsManager; @@ -44,6 +45,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -104,7 +106,9 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -332,6 +336,7 @@ public class ZenModeHelper { return mZenMode; } + // TODO: b/310620812 - Make private (or inline) when MODES_API is inlined. public List<ZenRule> getZenRules() { List<ZenRule> rules = new ArrayList<>(); synchronized (mConfigLock) { @@ -345,15 +350,29 @@ public class ZenModeHelper { return rules; } + /** + * Get the list of {@link AutomaticZenRule} instances that the calling package can manage + * (which means the owned rules for a regular app, and every rule for system callers) together + * with their ids. + */ + Map<String, AutomaticZenRule> getAutomaticZenRules() { + List<ZenRule> ruleList = getZenRules(); + HashMap<String, AutomaticZenRule> rules = new HashMap<>(ruleList.size()); + for (ZenRule rule : ruleList) { + rules.put(rule.id, zenRuleToAutomaticZenRule(rule)); + } + return rules; + } + public AutomaticZenRule getAutomaticZenRule(String id) { ZenRule rule; synchronized (mConfigLock) { if (mConfig == null) return null; - rule = mConfig.automaticRules.get(id); + rule = mConfig.automaticRules.get(id); } if (rule == null) return null; if (canManageAutomaticZenRule(rule)) { - return createAutomaticZenRule(rule); + return zenRuleToAutomaticZenRule(rule); } return null; } @@ -439,6 +458,167 @@ public class ZenModeHelper { } } + /** + * Create (or activate, or deactivate) an "implicit" {@link ZenRule} when an app that has + * Notification Policy Access but is not allowed to manage the global zen state + * calls {@link NotificationManager#setInterruptionFilter}. + * + * <p>When the {@code zenMode} is {@link Global#ZEN_MODE_OFF}, an existing implicit rule will be + * deactivated (if there is no implicit rule, the call will be ignored). For other modes, the + * rule's interruption filter will match the supplied {@code zenMode}. The policy of the last + * call to {@link NotificationManager#setNotificationPolicy} will be used (or, if never called, + * the global policy). + * + * <p>The created rule is owned by the calling package, but it has neither a + * {@link ConditionProviderService} nor an associated + * {@link AutomaticZenRule#configurationActivity}. + * + * @param zenMode one of the {@code Global#ZEN_MODE_x} values + */ + void applyGlobalZenModeAsImplicitZenRule(String callingPkg, int callingUid, int zenMode) { + if (!android.app.Flags.modesApi()) { + Log.wtf(TAG, "applyGlobalZenModeAsImplicitZenRule called with flag off!"); + return; + } + synchronized (mConfigLock) { + if (mConfig == null) { + return; + } + if (zenMode == Global.ZEN_MODE_OFF) { + // Deactivate implicit rule if it exists and is active; otherwise ignore. + ZenRule rule = mConfig.automaticRules.get(implicitRuleId(callingPkg)); + if (rule != null) { + Condition deactivated = new Condition(rule.conditionId, + mContext.getString(R.string.zen_mode_implicit_deactivated), + Condition.STATE_FALSE); + setAutomaticZenRuleState(rule.id, deactivated, + callingUid, /* fromSystemOrSystemUi= */ false); + } + } else { + // Either create a new rule with a default ZenPolicy, or update an existing rule's + // filter value. In both cases, also activate (and unsnooze) it. + ZenModeConfig newConfig = mConfig.copy(); + ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg)); + if (rule == null) { + rule = newImplicitZenRule(callingPkg); + newConfig.automaticRules.put(rule.id, rule); + } + rule.zenMode = zenMode; + rule.snoozing = false; + rule.condition = new Condition(rule.conditionId, + mContext.getString(R.string.zen_mode_implicit_activated), + Condition.STATE_TRUE); + setConfigLocked(newConfig, /* triggeringComponent= */ null, + "applyGlobalZenModeAsImplicitZenRule", + callingUid, /* fromSystemOrSystemUi= */ false); + } + } + } + + /** + * Create (or update) an "implicit" {@link ZenRule} when an app that has Notification Policy + * Access but is not allowed to manage the global zen state calls + * {@link NotificationManager#setNotificationPolicy}. + * + * <p>The created rule is owned by the calling package and has the {@link ZenPolicy} + * corresponding to the supplied {@code policy}, but it has neither a + * {@link ConditionProviderService} nor an associated + * {@link AutomaticZenRule#configurationActivity}. Its zen mode will be set to + * {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}. + */ + void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid, + NotificationManager.Policy policy) { + if (!android.app.Flags.modesApi()) { + Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!"); + return; + } + synchronized (mConfigLock) { + if (mConfig == null) { + return; + } + ZenModeConfig newConfig = mConfig.copy(); + ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg)); + if (rule == null) { + rule = newImplicitZenRule(callingPkg); + 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, + "applyGlobalPolicyAsImplicitZenRule", + callingUid, /* fromSystemOrSystemUi= */ false); + } + } + + /** + * Returns the {@link Policy} associated to the "implicit" {@link ZenRule} of a package that has + * Notification Policy Access but is not allowed to manage the global zen state. + * + * <p>If the implicit rule doesn't exist, or it doesn't specify a {@link ZenPolicy} (because the + * app never called {@link NotificationManager#setNotificationPolicy}) then the default policy + * is returned (i.e. same as {@link #getNotificationPolicy}. + * + * <p>Any unset values in the {@link ZenPolicy} will be mapped to their current defaults. + */ + @Nullable + Policy getNotificationPolicyFromImplicitZenRule(String callingPkg) { + if (!android.app.Flags.modesApi()) { + Log.wtf(TAG, "getNotificationPolicyFromImplicitZenRule called with flag off!"); + return getNotificationPolicy(); + } + synchronized (mConfigLock) { + if (mConfig == null) { + return null; + } + ZenRule implicitRule = mConfig.automaticRules.get(implicitRuleId(callingPkg)); + if (implicitRule != null && implicitRule.zenPolicy != null) { + return mConfig.toNotificationPolicy(implicitRule.zenPolicy); + } else { + return getNotificationPolicy(); + } + } + } + + /** + * Creates an empty {@link ZenRule} to be used as the implicit rule for {@code pkg}. + * Both {@link ZenRule#zenMode} and {@link ZenRule#zenPolicy} are unset. + */ + private ZenRule newImplicitZenRule(String pkg) { + ZenRule rule = new ZenRule(); + rule.id = implicitRuleId(pkg); + rule.pkg = pkg; + rule.creationTime = System.currentTimeMillis(); + + Binder.withCleanCallingIdentity(() -> { + try { + ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkg, 0); + rule.name = applicationInfo.loadLabel(mPm).toString(); + } catch (PackageManager.NameNotFoundException e) { + // Should not happen, since it's the app calling us (?) + Log.w(TAG, "Package not found for creating implicit zen rule"); + rule.name = "Unknown"; + } + }); + + rule.condition = null; + rule.conditionId = new Uri.Builder() + .scheme(Condition.SCHEME) + .authority("android") + .appendPath("implicit") + .appendPath(pkg) + .build(); + rule.enabled = true; + rule.modified = false; + rule.component = null; + rule.configurationActivity = null; + return rule; + } + + private static String implicitRuleId(String forPackage) { + return "implicit_" + forPackage; + } + public boolean removeAutomaticZenRule(String id, String reason, int callingUid, boolean fromSystemOrSystemUi) { ZenModeConfig newConfig; @@ -626,7 +806,7 @@ public class ZenModeHelper { } // update default rule (if locale changed, name of rule will change) currRule.name = defaultRule.name; - updateAutomaticZenRule(defaultRule.id, createAutomaticZenRule(currRule), + updateAutomaticZenRule(defaultRule.id, zenRuleToAutomaticZenRule(currRule), "locale changed", callingUid, fromSystemOrSystemUi); } } @@ -669,7 +849,7 @@ public class ZenModeHelper { return null; } - private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, + private static void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, boolean isNew) { if (rule.enabled != automaticZenRule.isEnabled()) { rule.snoozing = false; @@ -699,7 +879,7 @@ public class ZenModeHelper { } } - protected AutomaticZenRule createAutomaticZenRule(ZenRule rule) { + private static AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) { AutomaticZenRule azr; if (Flags.modesApi()) { azr = new AutomaticZenRule.Builder(rule.name, rule.conditionId) diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 25b7ca146ff1..dcac8c98d19f 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -7,8 +7,6 @@ flag { bug: "290381858" } - - flag { name: "polite_notifications" namespace: "systemui" diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 21284a05ddad..659c36c56047 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -45,11 +45,11 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.modules.utils.build.UnboundedSdkLevel; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedApexSystemService; import com.android.server.utils.TimingsTraceAndSlog; import com.google.android.collect.Lists; diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index 21610c9510ac..bdcec3a33221 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -58,6 +58,9 @@ import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.FgThread; @@ -68,9 +71,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java index d38b83fa6758..f3f64c5010ee 100644 --- a/services/core/java/com/android/server/pm/AppsFilterUtils.java +++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java @@ -27,15 +27,15 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.utils.WatchedArraySet; import com.android.server.utils.WatchedSparseSetArray; diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 26177037e1b4..5e76ae5cc2c3 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -123,6 +123,12 @@ import android.util.Xml; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.IndentingPrintWriter; @@ -141,12 +147,6 @@ import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.resolution.ComponentResolverApi; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index f8d27f1bad1c..d46d55977d2f 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -154,6 +154,11 @@ import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.F2fsUtils; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.security.VerityUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -177,11 +182,6 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedLibraryWrapper; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.rollback.RollbackManagerInternal; import com.android.server.security.FileIntegrityService; diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index e7499680b9a2..ea783b88cf64 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -47,13 +47,13 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.dex.DexManager; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import java.io.IOException; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 434c00afa8b2..7d3d85d33dcb 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -181,6 +181,8 @@ import com.android.internal.app.ResolverActivity; import com.android.internal.content.F2fsUtils; import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.om.OverlayConfig; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.telephony.CarrierAppUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -233,8 +235,6 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import com.android.server.pm.pkg.mutate.PackageStateWrite; import com.android.server.pm.pkg.mutate.PackageUserStateWrite; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index bcb7bdebf9bf..cd3416348153 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -92,6 +92,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.HexDump; @@ -104,7 +105,6 @@ import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.PackageDexUsage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.resolution.ComponentResolverApi; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; diff --git a/services/core/java/com/android/server/pm/PackageProperty.java b/services/core/java/com/android/server/pm/PackageProperty.java index 241f14390d5e..651bc5fac06a 100644 --- a/services/core/java/com/android/server/pm/PackageProperty.java +++ b/services/core/java/com/android/server/pm/PackageProperty.java @@ -31,8 +31,8 @@ import android.os.Binder; import android.os.UserHandle; import android.util.ArrayMap; +import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedComponent; import java.util.ArrayList; import java.util.Iterator; diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index b055a3ffd688..a8196f3f4985 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -44,6 +44,7 @@ import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.util.ArrayUtils; import com.android.server.pm.Installer.LegacyDexoptDisabledException; import com.android.server.pm.parsing.PackageCacher; @@ -52,7 +53,6 @@ import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import java.io.File; import java.util.Collections; diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 7ea9e3f529d0..22ee963cf9a2 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -73,6 +73,11 @@ import android.util.jar.StrictJarFile; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.parsing.PackageInfoUtils; @@ -82,11 +87,6 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedArraySet; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 6338965e4a80..7c969ef11666 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -89,6 +89,10 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.IndentingPrintWriter; @@ -113,10 +117,6 @@ import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.SuspendParams; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.resolution.ComponentResolver; import com.android.server.pm.verify.domain.DomainVerificationLegacySettings; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index 9376259c08b1..dddc6b0fbb7a 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -25,13 +25,13 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.proto.ProtoOutputStream; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.ArrayUtils; import com.android.server.pm.permission.LegacyPermissionState; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.pkg.component.ParsedProcessImpl; import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; diff --git a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java index adac68b25749..215646778778 100644 --- a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java +++ b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java @@ -29,9 +29,9 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import org.xmlpull.v1.XmlPullParser; diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 38cde3e3f7d7..91a70a6b4bdf 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -52,6 +52,17 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.PackageArchiver; @@ -65,17 +76,6 @@ import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.SELinuxUtil; import com.android.server.pm.pkg.component.ComponentParseUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedAttribution; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java index 97d526d1c44e..8916efd7aa5a 100644 --- a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java +++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java @@ -20,8 +20,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Pair; +import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedComponent; /** * For exposing internal fields to the rest of the server, enforcing that any overridden state from diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java index e2acc17e94c4..0eb2bbde5886 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java @@ -29,16 +29,16 @@ import android.content.pm.parsing.result.ParseTypeImpl; import android.os.incremental.IncrementalManager; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.PackageManagerException; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.parsing.ParsingPackageHidden; import java.io.IOException; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java index 75dd67d97620..370d239ad329 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java @@ -50,6 +50,19 @@ import android.util.SparseIntArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; @@ -61,27 +74,15 @@ import com.android.server.pm.pkg.AndroidPackageSplit; import com.android.server.pm.pkg.AndroidPackageSplitImpl; import com.android.server.pm.pkg.SELinuxUtil; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedActivityImpl; -import com.android.server.pm.pkg.component.ParsedApexSystemService; import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl; -import com.android.server.pm.pkg.component.ParsedAttribution; import com.android.server.pm.pkg.component.ParsedAttributionImpl; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; import com.android.server.pm.pkg.component.ParsedPermissionImpl; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedProcessImpl; import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.component.ParsedServiceImpl; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageHidden; @@ -3305,7 +3306,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, this.instrumentations = ParsingUtils.createTypedInterfaceList(in, ParsedInstrumentationImpl.CREATOR); this.preferredActivityFilters = sForIntentInfoPairs.unparcel(in); - this.processes = in.readHashMap(ParsedProcess.class.getClassLoader()); + this.processes = in.readHashMap(ParsedProcessImpl.class.getClassLoader()); this.metaData = in.readBundle(boot); this.volumeUuid = sForInternedString.unparcel(in); this.signingDetails = in.readParcelable(boot, android.content.pm.SigningDetails.class); diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java index c81d6d7d0918..07ff0ee049e1 100644 --- a/services/core/java/com/android/server/pm/permission/Permission.java +++ b/services/core/java/com/android/server/pm/permission/Permission.java @@ -28,9 +28,9 @@ import android.os.UserHandle; import android.util.Log; import android.util.Slog; +import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.server.pm.PackageManagerService; import com.android.server.pm.pkg.PackageState; -import com.android.server.pm.pkg.component.ParsedPermission; import libcore.util.EmptyArray; diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 6764e087ff04..883b0666f979 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -119,6 +119,8 @@ import com.android.internal.compat.IPlatformCompat; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.RoSystemProperties; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.IntPair; @@ -142,8 +144,6 @@ import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionUtils; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.SoftRestrictedPermissionPolicy; diff --git a/services/core/java/com/android/server/pm/permission/PermissionRegistry.java b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java index 3a617041d55e..61677eb06fe9 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionRegistry.java +++ b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java @@ -18,10 +18,11 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import android.util.ArrayMap; import android.util.ArraySet; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; + import java.util.Collection; /** diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java index 91854fda09d3..4d4efac4cbe8 100644 --- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -47,17 +47,17 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.R; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedApexSystemService; -import com.android.server.pm.pkg.component.ParsedAttribution; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.component.ParsedUsesPermission; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.security.PublicKey; diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java index 1d2c5ec2d081..20fcdb564b96 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java @@ -22,7 +22,7 @@ import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.util.SparseArray; -import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedMainComponent; /** @hide */ public class PackageStateUtils { diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java index cd3583b814a4..fe80f743ffc3 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java @@ -27,7 +27,7 @@ import android.os.Debug; import android.util.DebugUtils; import android.util.Slog; -import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedMainComponent; /** @hide */ public class PackageUserStateUtils { diff --git a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java index 063f577bce5f..411bdede315f 100644 --- a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java +++ b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java @@ -22,8 +22,8 @@ import android.content.pm.SigningDetails; import android.util.ArrayMap; import android.util.ArraySet; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.server.pm.permission.LegacyPermissionState; -import com.android.server.pm.pkg.component.ParsedProcess; import java.util.List; diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java index 1deb8d055e20..1964df0853fd 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java @@ -19,6 +19,14 @@ package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; + /** * Contains mutation methods so that code doesn't have to cast to the Impl. Meant to eventually * be removed once all post-parsing mutation is moved to parsing. diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java index a8fb79a52837..041edaa98e63 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java @@ -29,6 +29,9 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.TextUtils; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.parsing.ParsingPackage; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java index 68d5428d6604..f02790189cc0 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java @@ -36,7 +36,7 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.ArraySet; -import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; import com.android.server.pm.pkg.parsing.ParsingUtils; @@ -49,7 +49,6 @@ import java.util.Set; * @hide **/ @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false) -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public class ParsedActivityImpl extends ParsedMainComponentImpl implements ParsedActivity, Parcelable { @@ -133,7 +132,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse * should be invisible to user and user should not know or see it. */ @NonNull - static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName, + public static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName, int uiOptions, String taskAffinity, boolean hardwareAccelerated) { ParsedActivityImpl activity = new ParsedActivityImpl(); activity.setPackageName(packageName); @@ -700,7 +699,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse time = 1669437519576L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java", - inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.server.pm.pkg.component.ParsedActivity)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") + inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java index ee793c8b2f87..5709cbb09f93 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java @@ -48,6 +48,7 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.util.ArrayUtils; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java index 167aba301f35..cfed19aa0934 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -250,7 +251,7 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par time = 1643723578605L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java", - inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.server.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)") + inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java index ed9aa2e6860a..d3fb29b8aa66 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java @@ -25,6 +25,8 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.TextUtils; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; + import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java index b59f511afa57..62b994724346 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java @@ -22,6 +22,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedAttribution; import com.android.internal.util.DataClass; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java index 98e94c5214f0..411220ae42e8 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java @@ -26,6 +26,7 @@ import android.content.res.XmlResourceParser; import android.util.ArraySet; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedAttribution; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java index f8d678ee39ac..512e5c7023c7 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java @@ -32,6 +32,8 @@ import android.text.TextUtils; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java index 8a0d356925d4..7bfad14d669a 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java @@ -26,6 +26,7 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java index c63a68975588..9792a91fb699 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java @@ -26,6 +26,7 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.parsing.ParsingPackage; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java index 5b6375d6b365..ab9404310078 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java @@ -23,6 +23,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.util.DataClass; /** diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java index 4f0a504b659f..5e67bbf4ab0b 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java @@ -31,6 +31,7 @@ import android.util.Slog; import android.util.TypedValue; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java index c670e7c1b4f7..f322eef8c3a3 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java @@ -25,6 +25,7 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java index f52ad1393878..6c22f825bab9 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java @@ -31,6 +31,8 @@ import android.os.Build; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java index 59075deb258c..afe37bc3274c 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.util.DataClass; /** @@ -174,7 +175,7 @@ public class ParsedPermissionGroupImpl extends ParsedComponentImpl implements time = 1642132854167L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java", - inputSignatures = "private int requestDetailRes\nprivate int backgroundRequestRes\nprivate int backgroundRequestDetailRes\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.server.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)") + inputSignatures = "private int requestDetailRes\nprivate int backgroundRequestRes\nprivate int backgroundRequestDetailRes\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java index 4c831d36c9e3..69e33c8f281e 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java @@ -24,6 +24,8 @@ import android.text.TextUtils; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; @@ -119,8 +121,8 @@ public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedP this.requestRes = in.readInt(); this.protectionLevel = in.readInt(); this.tree = in.readBoolean(); - this.parsedPermissionGroup = in.readParcelable(ParsedPermissionGroup.class.getClassLoader(), - ParsedPermissionGroupImpl.class); + this.parsedPermissionGroup = in.readParcelable( + ParsedPermissionGroupImpl.class.getClassLoader(), ParsedPermissionGroupImpl.class); this.knownCerts = sForStringSet.unparcel(in); } diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java index c6d1775307f8..0f2b49b8541c 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java @@ -31,6 +31,8 @@ import android.util.EventLog; import android.util.Slog; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java index 6d52f656b2e4..40e3670b9261 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java @@ -25,7 +25,7 @@ import android.os.Parcelable; import android.util.ArrayMap; import android.util.ArraySet; -import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -35,7 +35,6 @@ import java.util.Set; /** @hide */ @DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false, genBuilder = false) -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public class ParsedProcessImpl implements ParsedProcess, Parcelable { @NonNull diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java index 4f4c2d5019f3..766fb90cbfa0 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java @@ -27,6 +27,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.CollectionUtils; import com.android.internal.util.XmlUtils; import com.android.server.pm.pkg.parsing.ParsingPackage; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java index 6f4b4c84a07e..81a3c17e2bb4 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java @@ -28,6 +28,7 @@ import android.os.PatternMatcher; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; @@ -301,7 +302,7 @@ public class ParsedProviderImpl extends ParsedMainComponentImpl implements Parse time = 1642560323360L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java", - inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)") + inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java index 37bed15ba1d7..b66db4f9ced4 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java @@ -34,6 +34,7 @@ import android.os.PatternMatcher; import android.util.Slog; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java index 47e993cb02be..ca8c45d1383c 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java @@ -26,6 +26,8 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java index c15266fc4cbc..1b421841f166 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java @@ -32,6 +32,7 @@ import android.content.res.XmlResourceParser; import android.os.Build; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java index 9b89373bbb90..78377a836651 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java index 699ccbdc5a83..408a531eaf1d 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java @@ -32,18 +32,18 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedApexSystemService; -import com.android.server.pm.pkg.component.ParsedAttribution; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import java.security.PublicKey; import java.util.List; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 061698a929d6..417e3ae6e7df 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -89,6 +89,19 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.R; import com.android.internal.os.ClassLoaderFactory; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import com.android.server.pm.SharedUidMigration; @@ -98,29 +111,17 @@ import com.android.server.pm.permission.CompatibilityPermissionInfo; import com.android.server.pm.pkg.component.ComponentMutateUtils; import com.android.server.pm.pkg.component.ComponentParseUtils; import com.android.server.pm.pkg.component.InstallConstraintsTagParser; -import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityImpl; import com.android.server.pm.pkg.component.ParsedActivityUtils; -import com.android.server.pm.pkg.component.ParsedApexSystemService; import com.android.server.pm.pkg.component.ParsedApexSystemServiceUtils; -import com.android.server.pm.pkg.component.ParsedAttribution; import com.android.server.pm.pkg.component.ParsedAttributionUtils; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.component.ParsedInstrumentationUtils; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; import com.android.server.pm.pkg.component.ParsedIntentInfoUtils; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionUtils; -import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.pkg.component.ParsedProcessUtils; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.component.ParsedProviderUtils; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.component.ParsedServiceUtils; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.server.pm.split.DefaultSplitAssetLoader; import com.android.server.pm.split.SplitAssetDependencyLoader; @@ -2842,7 +2843,7 @@ public class ParsingPackageUtils { String taskAffinity = result.getResult(); // Build custom App Details activity info instead of parsing it from xml - return input.success(ParsedActivity.makeAppDetailsActivity(packageName, + return input.success(ParsedActivityImpl.makeAppDetailsActivity(packageName, pkg.getProcessName(), pkg.getUiOptions(), taskAffinity, pkg.isHardwareAccelerated())); } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java index 07512855d276..2cfffb3b185d 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java @@ -30,9 +30,9 @@ import android.os.Parcelable; import android.util.Pair; import android.util.Slog; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.util.Parcelling; import com.android.internal.util.XmlUtils; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java index 0ceda421913d..ed6d3b94758b 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java @@ -45,6 +45,12 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.server.IntentResolver; import com.android.server.pm.Computer; @@ -57,13 +63,7 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.snapshot.PackageDataSnapshot; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java index b8e4c8d2a51f..0f12ee1b52fb 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java @@ -25,11 +25,11 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.Computer; import com.android.server.pm.DumpState; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import java.io.PrintWriter; import java.util.List; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java index 80cde73ecf1f..2bc926c6d840 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java @@ -28,6 +28,11 @@ import android.os.UserHandle; import android.util.ArrayMap; import android.util.Pair; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.Computer; import com.android.server.pm.DumpState; import com.android.server.pm.UserManagerService; @@ -36,11 +41,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserStateInternal; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.utils.WatchableImpl; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java index 0c84f4c53dfe..add33b25923e 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java @@ -24,13 +24,13 @@ import android.content.Intent; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.Computer; import com.android.server.pm.DumpState; import com.android.server.pm.PackageManagerTracedLock; import com.android.server.pm.UserManagerService; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import java.io.PrintWriter; import java.util.List; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index adef808bd712..735f90faee5b 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -27,11 +27,11 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Patterns; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.server.SystemConfig; import com.android.server.compat.PlatformCompat; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import java.util.List; import java.util.Objects; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 3d4d4eca7f48..6150099b2945 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -48,6 +48,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.util.CollectionUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -60,7 +61,6 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.PackageUserStateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index 77290fd944eb..9f0a97523af8 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -603,40 +603,51 @@ public class PowerStatsService extends SystemService { @NonNull private String getEnergyConsumerName(EnergyConsumer consumer, EnergyConsumer[] energyConsumers) { - if (consumer.type != EnergyConsumerType.OTHER) { - StringBuilder sb = new StringBuilder(); - sb.append(energyConsumerTypeToString(consumer.type)); - boolean hasOrdinal = consumer.ordinal != 0; - if (!hasOrdinal) { - // See if any other EnergyConsumer of the same type has an ordinal - for (EnergyConsumer aConsumer : energyConsumers) { - if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) { - hasOrdinal = true; - break; - } + StringBuilder sb = new StringBuilder(); + switch (consumer.type) { + case EnergyConsumerType.BLUETOOTH: + sb.append("BLUETOOTH"); + break; + case EnergyConsumerType.CPU_CLUSTER: + sb.append("CPU"); + break; + case EnergyConsumerType.DISPLAY: + sb.append("DISPLAY"); + break; + case EnergyConsumerType.GNSS: + sb.append("GNSS"); + break; + case EnergyConsumerType.MOBILE_RADIO: + sb.append("MOBILE_RADIO"); + break; + case EnergyConsumerType.WIFI: + sb.append("WIFI"); + break; + case EnergyConsumerType.CAMERA: + sb.append("CAMERA"); + break; + default: + if (consumer.name != null && !consumer.name.isBlank()) { + sb.append(consumer.name.toUpperCase(Locale.ENGLISH)); + } else { + sb.append("CONSUMER_").append(consumer.type); + } + break; + } + boolean hasOrdinal = consumer.ordinal != 0; + if (!hasOrdinal) { + // See if any other EnergyConsumer of the same type has an ordinal + for (EnergyConsumer aConsumer : energyConsumers) { + if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) { + hasOrdinal = true; + break; } } - if (hasOrdinal) { - sb.append('/').append(consumer.ordinal); - } - return sb.toString(); - } else { - return consumer.name; } - } - - private static String energyConsumerTypeToString(int type) { - switch(type) { - case EnergyConsumerType.BLUETOOTH: return "BLUETOOTH"; - case EnergyConsumerType.CPU_CLUSTER: return "CPU"; - case EnergyConsumerType.DISPLAY: return "DISPLAY"; - case EnergyConsumerType.GNSS: return "GNSS"; - case EnergyConsumerType.MOBILE_RADIO: return "MOBILE_RADIO"; - case EnergyConsumerType.WIFI: return "WIFI"; - case EnergyConsumerType.OTHER: return ""; - default: - throw new IllegalStateException("Unrecognized EnergyConsumerType: " + type); + if (hasOrdinal) { + sb.append('/').append(consumer.ordinal); } + return sb.toString(); } /** diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index 6d580e97d578..8b57f87f5df3 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -280,12 +280,12 @@ final class SpeechRecognitionManagerServiceImpl extends return null; } - if (getSessionCountByUidLocked(callingUid) >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { + if (getSessionCountByUidLocked(callingUid) == MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { Slog.w(TAG, "Number of sessions exceeded for uid: " + callingUid); Counter.logIncrementWithUid( "speech_recognition.value_exceed_session_count", callingUid); - return null; + // TODO(b/297249772): return null early to refuse the new connection } if (servicesForClient != null) { diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index fed6e7ee4686..b2e808ac8e95 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -117,16 +117,16 @@ abstract class Vibration { static final class CallerInfo { public final VibrationAttributes attrs; public final int uid; - public final int displayId; + public final int deviceId; public final String opPkg; public final String reason; - CallerInfo(@NonNull VibrationAttributes attrs, int uid, int displayId, - String opPkg, String reason) { + CallerInfo(@NonNull VibrationAttributes attrs, int uid, int deviceId, String opPkg, + String reason) { Objects.requireNonNull(attrs); this.attrs = attrs; this.uid = uid; - this.displayId = displayId; + this.deviceId = deviceId; this.opPkg = opPkg; this.reason = reason; } @@ -138,14 +138,14 @@ abstract class Vibration { CallerInfo that = (CallerInfo) o; return Objects.equals(attrs, that.attrs) && uid == that.uid - && displayId == that.displayId + && deviceId == that.deviceId && Objects.equals(opPkg, that.opPkg) && Objects.equals(reason, that.reason); } @Override public int hashCode() { - return Objects.hash(attrs, uid, displayId, opPkg, reason); + return Objects.hash(attrs, uid, deviceId, opPkg, reason); } @Override @@ -153,7 +153,7 @@ abstract class Vibration { return "CallerInfo{" + " uid=" + uid + ", opPkg=" + opPkg - + ", displayId=" + displayId + + ", deviceId=" + deviceId + ", attrs=" + attrs + ", reason=" + reason + '}'; @@ -267,8 +267,8 @@ abstract class Vibration { mStartTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mStartTime)), mEndTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mEndTime))); String callerInfoStr = String.format(Locale.ROOT, - " | %s (uid=%d, displayId=%d) | usage: %s (audio=%s) | flags: %s | reason: %s", - mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.displayId, + " | %s (uid=%d, deviceId=%d) | usage: %s (audio=%s) | flags: %s | reason: %s", + mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.attrs.usageToString(), AudioAttributes.usageToString(mCallerInfo.attrs.getAudioUsage()), Long.toBinaryString(mCallerInfo.attrs.getFlags()), diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index 7f55836598b5..839c2075fa8c 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -61,7 +61,6 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; -import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -166,7 +165,6 @@ final class VibrationSettings { final MyUidObserver mUidObserver; @VisibleForTesting final SettingsBroadcastReceiver mSettingChangeReceiver; - final VirtualDeviceListener mVirtualDeviceListener; @GuardedBy("mLock") private final List<OnVibratorSettingsChanged> mListeners = new ArrayList<>(); @@ -180,6 +178,8 @@ final class VibrationSettings { @GuardedBy("mLock") @Nullable private PowerManagerInternal mPowerManagerInternal; + @Nullable + private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal; @GuardedBy("mLock") private boolean mVibrateInputDevices; @@ -207,8 +207,6 @@ final class VibrationSettings { mSettingObserver = new SettingsContentObserver(handler); mUidObserver = new MyUidObserver(); mSettingChangeReceiver = new SettingsBroadcastReceiver(); - mVirtualDeviceListener = new VirtualDeviceListener(); - mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class) .getSystemUiServiceComponent().getPackageName(); @@ -272,13 +270,6 @@ final class VibrationSettings { } }); - VirtualDeviceManagerInternal vdm = LocalServices.getService( - VirtualDeviceManagerInternal.class); - if (vdm != null) { - vdm.registerVirtualDisplayListener(mVirtualDeviceListener); - vdm.registerAppsOnVirtualDeviceListener(mVirtualDeviceListener); - } - registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER); registerSettingsChangeReceiver(INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER); @@ -414,8 +405,14 @@ final class VibrationSettings { && !BACKGROUND_PROCESS_USAGE_ALLOWLIST.contains(usage)) { return Vibration.Status.IGNORED_BACKGROUND; } - if (mVirtualDeviceListener.isAppOrDisplayOnAnyVirtualDevice(callerInfo.uid, - callerInfo.displayId)) { + + if (callerInfo.deviceId != Context.DEVICE_ID_DEFAULT + && callerInfo.deviceId != Context.DEVICE_ID_INVALID) { + return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE; + } + + if (callerInfo.deviceId == Context.DEVICE_ID_INVALID + && isAppRunningOnAnyVirtualDevice(callerInfo.uid)) { return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE; } @@ -794,6 +791,15 @@ final class VibrationSettings { return out; } + private boolean isAppRunningOnAnyVirtualDevice(int uid) { + if (mVirtualDeviceManagerInternal == null) { + mVirtualDeviceManagerInternal = + LocalServices.getService(VirtualDeviceManagerInternal.class); + } + return mVirtualDeviceManagerInternal != null + && mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(uid); + } + /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */ @VisibleForTesting final class SettingsContentObserver extends ContentObserver { @@ -853,73 +859,4 @@ final class VibrationSettings { } } } - - /** - * Implementation of Virtual Device listeners for the changes of virtual displays and of apps - * running on any virtual device. - */ - final class VirtualDeviceListener implements - VirtualDeviceManagerInternal.VirtualDisplayListener, - VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener { - @GuardedBy("mLock") - private final Set<Integer> mVirtualDisplays = new HashSet<>(); - @GuardedBy("mLock") - private final Set<Integer> mAppsOnVirtualDevice = new HashSet<>(); - - - @Override - public void onVirtualDisplayCreated(int displayId) { - synchronized (mLock) { - mVirtualDisplays.add(displayId); - } - } - - @Override - public void onVirtualDisplayRemoved(int displayId) { - synchronized (mLock) { - mVirtualDisplays.remove(displayId); - } - } - - - @Override - public void onAppsOnAnyVirtualDeviceChanged(Set<Integer> allRunningUids) { - synchronized (mLock) { - mAppsOnVirtualDevice.clear(); - mAppsOnVirtualDevice.addAll(allRunningUids); - } - } - - /** - * @param uid: uid of the calling app. - * @param displayId: the id of a Display. - * @return Returns true if: - * <ul> - * <li> the displayId is valid, and it's owned by a virtual device.</li> - * <li> the displayId is invalid, and the calling app (uid) is running on a virtual - * device.</li> - * </ul> - */ - public boolean isAppOrDisplayOnAnyVirtualDevice(int uid, int displayId) { - if (displayId == Display.DEFAULT_DISPLAY) { - // The default display is the primary physical display on the phone. - return false; - } - - synchronized (mLock) { - if (displayId == Display.INVALID_DISPLAY) { - // There is no Display object associated with the Context of calling - // {@link SystemVibratorManager}, checking the calling UID instead. - return mAppsOnVirtualDevice.contains(uid); - } else { - // Other valid display IDs representing valid logical displays will be - // checked - // against the active virtual displays set built with the registered - // {@link VirtualDisplayListener}. - return mVirtualDisplays.contains(displayId); - } - } - } - - } } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index ace7777c9b58..cf33cc5f43bd 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -63,7 +63,6 @@ import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; -import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -385,7 +384,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return false; } AlwaysOnVibration alwaysOnVibration = new AlwaysOnVibration(alwaysOnId, - new Vibration.CallerInfo(attrs, uid, Display.DEFAULT_DISPLAY, opPkg, + new Vibration.CallerInfo(attrs, uid, Context.DEVICE_ID_DEFAULT, opPkg, null), effects); mAlwaysOnEffects.put(alwaysOnId, alwaysOnVibration); updateAlwaysOnLocked(alwaysOnVibration); @@ -397,16 +396,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override // Binder call - public void vibrate(int uid, int displayId, String opPkg, @NonNull CombinedVibration effect, + public void vibrate(int uid, int deviceId, String opPkg, @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs, String reason, IBinder token) { - vibrateWithPermissionCheck(uid, displayId, opPkg, effect, attrs, reason, token); + vibrateWithPermissionCheck(uid, deviceId, opPkg, effect, attrs, reason, token); } @Override // Binder call public void performHapticFeedback( - int uid, int displayId, String opPkg, int constant, boolean always, String reason, + int uid, int deviceId, String opPkg, int constant, boolean always, String reason, IBinder token) { - performHapticFeedbackInternal(uid, displayId, opPkg, constant, always, reason, token); + performHapticFeedbackInternal(uid, deviceId, opPkg, constant, always, reason, token); } /** @@ -417,7 +416,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @VisibleForTesting @Nullable HalVibration performHapticFeedbackInternal( - int uid, int displayId, String opPkg, int constant, boolean always, String reason, + int uid, int deviceId, String opPkg, int constant, boolean always, String reason, IBinder token) { HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider(); if (hapticVibrationProvider == null) { @@ -433,7 +432,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { VibrationAttributes attrs = hapticVibrationProvider.getVibrationAttributesForHapticFeedback( constant, /* bypassVibrationIntensitySetting= */ always); - return vibrateWithoutPermissionCheck(uid, displayId, opPkg, combinedVibration, attrs, + return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, combinedVibration, attrs, "performHapticFeedback: " + reason, token); } @@ -444,7 +443,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ @VisibleForTesting @Nullable - HalVibration vibrateWithPermissionCheck(int uid, int displayId, String opPkg, + HalVibration vibrateWithPermissionCheck(int uid, int deviceId, String opPkg, @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs, String reason, IBinder token) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason); @@ -452,24 +451,24 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { attrs = fixupVibrationAttributes(attrs, effect); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.VIBRATE, "vibrate"); - return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token); + return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } } - HalVibration vibrateWithoutPermissionCheck(int uid, int displayId, String opPkg, + HalVibration vibrateWithoutPermissionCheck(int uid, int deviceId, String opPkg, @NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs, String reason, IBinder token) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate no perm check, reason = " + reason); try { - return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token); + return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } } - private HalVibration vibrateInternal(int uid, int displayId, String opPkg, + private HalVibration vibrateInternal(int uid, int deviceId, String opPkg, @NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs, String reason, IBinder token) { if (token == null) { @@ -482,7 +481,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } // Create Vibration.Stats as close to the received request as possible, for tracking. HalVibration vib = new HalVibration(token, effect, - new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason)); + new Vibration.CallerInfo(attrs, uid, deviceId, opPkg, reason)); fillVibrationFallbacks(vib, effect); if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { @@ -1558,10 +1557,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private ExternalVibrationHolder(ExternalVibration externalVibration) { super(externalVibration.getToken(), new Vibration.CallerInfo( externalVibration.getVibrationAttributes(), externalVibration.getUid(), - // TODO(b/243604888): propagating displayID from IExternalVibration instead of - // using INVALID_DISPLAY for all external vibrations. - Display.INVALID_DISPLAY, - externalVibration.getPackage(), null)); + // TODO(b/249785241): Find a way to link ExternalVibration to a VirtualDevice + // instead of using DEVICE_ID_INVALID here and relying on the UID checks. + Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null)); this.externalVibration = externalVibration; this.scale = IExternalVibratorService.SCALE_NONE; mStatus = Vibration.Status.RUNNING; @@ -1974,8 +1972,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { boolean alreadyUnderExternalControl = false; boolean waitForCompletion = false; synchronized (mLock) { - // TODO(b/243604888): propagating displayID from IExternalVibration instead of - // using INVALID_DISPLAY for all external vibrations. Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked( vibHolder.callerInfo); @@ -2184,7 +2180,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { IBinder deathBinder = commonOptions.background ? VibratorManagerService.this : mShellCallbacksToken; HalVibration vib = vibrateWithPermissionCheck(Binder.getCallingUid(), - Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, combined, attrs, + Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, combined, attrs, commonOptions.description, deathBinder); maybeWaitOnVibration(vib, commonOptions); } @@ -2241,7 +2237,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { IBinder deathBinder = commonOptions.background ? VibratorManagerService.this : mShellCallbacksToken; HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(), - Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, constant, + Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, constant, /* always= */ commonOptions.force, /* reason= */ commonOptions.description, deathBinder); maybeWaitOnVibration(vib, commonOptions); diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 26f0d34a6261..7b399c837d32 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1018,9 +1018,8 @@ class ActivityClientController extends IActivityClientController.Stub { } try { - final ClientTransaction transaction = ClientTransaction.obtain(r.app.getThread()); - transaction.addCallback(EnterPipRequestedItem.obtain(r.token)); - mService.getLifecycleManager().scheduleTransaction(transaction); + mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), + EnterPipRequestedItem.obtain(r.token)); return true; } catch (Exception e) { Slog.w(TAG, "Failed to send enter pip requested item: " diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index eeeca1018b0a..24d99387d63c 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8185,6 +8185,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * aspect ratio. */ boolean shouldCreateCompatDisplayInsets() { + if (mLetterboxUiController.shouldApplyUserFullscreenOverride()) { + // If the user has forced the applications aspect ratio to be fullscreen, don't use size + // compatibility mode in any situation. The user has been warned and therefore accepts + // the risk of the application misbehaving. + return false; + } switch (supportsSizeChanges()) { case SIZE_CHANGES_SUPPORTED_METADATA: case SIZE_CHANGES_SUPPORTED_OVERRIDE: diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 777b5cd4337b..e196d463db79 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -936,7 +936,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { final int deviceId = getDeviceIdForDisplayId(r.getDisplayId()); clientTransaction.addCallback(LaunchActivityItem.obtain(r.token, - new Intent(r.intent), System.identityHashCode(r), r.info, + r.intent, System.identityHashCode(r), r.info, // TODO: Have this take the merged configuration instead of separate global // and override configs. mergedConfiguration.getGlobalConfiguration(), diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index a2f5a383caad..c2b5f88d0b4f 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -583,7 +583,7 @@ public class BackgroundActivityStartController { + " if the PI sender upgrades target_sdk to 34+! " + " (missing opt in by PI sender)! " + state.dump(resultForCaller, resultForRealCaller)); - showBalBlockedToast("BAL would be blocked", state); + showBalRiskToast("BAL would be blocked", state); return statsLog(resultForRealCaller, state); } Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed" diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 735cbc4e4287..5518de7b64fd 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -254,7 +254,9 @@ final class LetterboxUiController { // Counter for ActivityRecord#setRequestedOrientation private int mSetOrientationRequestCounter = 0; - // The min aspect ratio override set by user + // The min aspect ratio override set by user. Stores the last selected aspect ratio after + // {@link #shouldApplyUserFullscreenOverride} or {@link #shouldApplyUserMinAspectRatioOverride} + // have been invoked. @PackageManager.UserMinAspectRatio private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET; @@ -661,7 +663,9 @@ final class LetterboxUiController { @ScreenOrientation int overrideOrientationIfNeeded(@ScreenOrientation int candidate) { - if (shouldApplyUserFullscreenOverride()) { + if (shouldApplyUserFullscreenOverride() + && mActivityRecord.mDisplayContent != null + && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) { Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + mActivityRecord + " is overridden to " + screenOrientationToString(SCREEN_ORIENTATION_USER) @@ -1171,9 +1175,7 @@ final class LetterboxUiController { boolean shouldApplyUserFullscreenOverride() { if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride) || FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride) - || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled() - || mActivityRecord.mDisplayContent == null - || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) { + || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) { return false; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a7784152a883..c9703dbe6311 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4648,7 +4648,7 @@ class Task extends TaskFragment { // Expanding pip into new rotation, so create a rotation leash // until the display is rotated. topActivity.getOrCreateFixedRotationLeash( - topActivity.getSyncTransaction()); + topActivity.getPendingTransaction()); } lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask"); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index c0bf2ce41435..3e23fab86223 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1052,12 +1052,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { * @return true if we are *guaranteed* to enter-pip. This means we return false if there's * a chance we won't thus legacy-entry (via pause+userLeaving) will return false. */ - private boolean checkEnterPipOnFinish(@NonNull ActivityRecord ar, - @Nullable ActivityRecord resuming) { + private boolean checkEnterPipOnFinish(@NonNull ActivityRecord ar) { if (!mCanPipOnFinish || !ar.isVisible() || ar.getTask() == null || !ar.isState(RESUMED)) { return false; } + final ActivityRecord resuming = getVisibleTransientLaunch(ar.getTaskDisplayArea()); if (ar.pictureInPictureArgs != null && ar.pictureInPictureArgs.isAutoEnterEnabled()) { if (!ar.getTask().isVisibleRequested() || didCommitTransientLaunch()) { // force enable pip-on-task-switch now that we've committed to actually launching @@ -1196,9 +1196,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final boolean isScreenOff = ar.mDisplayContent == null || ar.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF; if ((!visibleAtTransitionEnd || isScreenOff) && !ar.isVisibleRequested()) { - final ActivityRecord resuming = getVisibleTransientLaunch( - ar.getTaskDisplayArea()); - final boolean commitVisibility = !checkEnterPipOnFinish(ar, resuming); + final boolean commitVisibility = !checkEnterPipOnFinish(ar); // Avoid commit visibility if entering pip or else we will get a sudden // "flash" / surface going invisible for a split second. if (commitVisibility) { @@ -1431,7 +1429,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (candidateActivity.getTaskDisplayArea() != taskDisplayArea) { continue; } - if (!candidateActivity.isVisible()) { + if (!candidateActivity.isVisibleRequested()) { continue; } return candidateActivity; diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java index 46677107c670..b3a36501b7cf 100644 --- a/services/core/java/com/android/server/wm/WindowManagerFlags.java +++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java @@ -49,5 +49,8 @@ class WindowManagerFlags { final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync(); + final boolean mAllowsScreenSizeDecoupledFromStatusBarAndCutout = + Flags.allowsScreenSizeDecoupledFromStatusBarAndCutout(); + /* End Available Flags */ } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 55678c525c4b..0a986c8a6f2f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -336,7 +336,6 @@ import com.android.server.policy.WindowManagerPolicy; import com.android.server.policy.WindowManagerPolicy.ScreenOffListener; import com.android.server.power.ShutdownThread; import com.android.server.utils.PriorityDump; -import com.android.window.flags.Flags; import dalvik.annotation.optimization.NeverCompile; @@ -1194,7 +1193,7 @@ public class WindowManagerService extends IWindowManager.Stub .getBoolean(R.bool.config_skipActivityRelaunchWhenDocking); final boolean isScreenSizeDecoupledFromStatusBarAndCutout = context.getResources() .getBoolean(R.bool.config_decoupleStatusBarAndDisplayCutoutFromScreenSize) - && Flags.closeToSquareConfigIncludesStatusBar(); + && mFlags.mAllowsScreenSizeDecoupledFromStatusBarAndCutout; if (!isScreenSizeDecoupledFromStatusBarAndCutout) { mDecorTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars(); mConfigTypes = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars(); diff --git a/services/proguard.flags b/services/proguard.flags index 261bb7cacdc4..407505d6eda0 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -14,13 +14,20 @@ } # APIs referenced by dependent JAR files and modules --keep @interface android.annotation.SystemApi +# TODO(b/300514883): Pull @SystemApi keep rules from system-api.pro. +-keep interface android.annotation.SystemApi -keep @android.annotation.SystemApi class * { public protected *; } -keepclasseswithmembers class * { @android.annotation.SystemApi *; } +# Also ensure nested classes are kept. This is overly conservative, but handles +# cases where such classes aren't explicitly marked @SystemApi. +-if @android.annotation.SystemApi class * +-keep public class <1>$** { + public protected *; +} # Derivatives of SystemService and other services created via reflection -keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService { diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index 12cd0f6e1a7c..8d76fddcc793 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -24,6 +24,7 @@ import android.content.pm.PackageManager import android.os.Binder import android.os.UserHandle import android.util.ArrayMap +import com.android.internal.pm.pkg.component.ParsedActivity import com.android.server.pm.AppsFilterImpl import com.android.server.pm.PackageManagerService import com.android.server.pm.PackageManagerServiceInjector @@ -39,7 +40,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackageInternal import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.parsing.pkg.ParsedPackage import com.android.server.pm.pkg.AndroidPackage -import com.android.server.pm.pkg.component.ParsedActivity import com.android.server.pm.resolution.ComponentResolver import com.android.server.pm.snapshot.PackageDataSnapshot import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java index d5cd6ef9eb69..25146a87970f 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java @@ -49,16 +49,16 @@ import android.util.SparseArray; import androidx.annotation.NonNull; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.server.om.OverlayReferenceMapper; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedActivityImpl; import com.android.server.pm.pkg.component.ParsedComponentImpl; import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; -import com.android.server.pm.pkg.component.ParsedPermission; import com.android.server.pm.pkg.component.ParsedPermissionImpl; import com.android.server.pm.pkg.component.ParsedProviderImpl; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java index 0eac4e6a25ac..7c28e13f0eee 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java @@ -58,6 +58,16 @@ import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageInfoUtils; @@ -69,24 +79,14 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.permission.CompatibilityPermissionInfo; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageUserStateInternal; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedActivityImpl; -import com.android.server.pm.pkg.component.ParsedApexSystemService; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; import com.android.server.pm.pkg.component.ParsedPermissionImpl; import com.android.server.pm.pkg.component.ParsedPermissionUtils; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.component.ParsedServiceImpl; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.server.pm.pkg.parsing.ParsingPackage; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java index 9e371649214c..7123c2076640 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java @@ -38,16 +38,16 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.server.pm.test.service.server.R; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.internal.util.ArrayUtils; import com.android.server.pm.PackageManagerException; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.component.ParsedActivityUtils; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; import com.android.server.pm.pkg.component.ParsedPermissionUtils; +import com.android.server.pm.test.service.server.R; import com.google.common.truth.Expect; diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt index 0e2e35f25b15..26468544e8d6 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.ActivityInfo -import com.android.server.pm.pkg.component.ParsedActivity +import com.android.internal.pm.pkg.component.ParsedActivity import com.android.server.pm.pkg.component.ParsedActivityImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt index 4e44e96aa710..52d5b3bccb72 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedAttribution +import com.android.internal.pm.pkg.component.ParsedAttribution import com.android.server.pm.pkg.component.ParsedAttributionImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt index 058f6d69f3e7..af0c0de2db15 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PackageManager -import com.android.server.pm.pkg.component.ParsedComponent +import com.android.internal.pm.pkg.component.ParsedComponent import com.android.server.pm.pkg.component.ParsedComponentImpl import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Bundle diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt index eeb30b70c143..dc0f194f10cc 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedInstrumentation +import com.android.internal.pm.pkg.component.ParsedInstrumentation import com.android.server.pm.pkg.component.ParsedInstrumentationImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt index f27a51f63049..5224f23d38d1 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedIntentInfo +import com.android.internal.pm.pkg.component.ParsedIntentInfo import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Parcelable import android.os.PatternMatcher diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt index a0d8c44899d8..dfff6025e2eb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedMainComponent +import com.android.internal.pm.pkg.component.ParsedMainComponent import com.android.server.pm.pkg.component.ParsedMainComponentImpl import android.os.Parcelable import java.util.Arrays diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt index f266e7616ff3..ccbf558734d3 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.internal.pm.pkg.component.ParsedPermissionGroup import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt index c72a44e4c4e0..2814783b6849 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedPermission -import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.internal.pm.pkg.component.ParsedPermission +import com.android.internal.pm.pkg.component.ParsedPermissionGroup import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl import com.android.server.pm.pkg.component.ParsedPermissionImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt index 8b9361a31d0a..2e9604696acb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedProcess +import com.android.internal.pm.pkg.component.ParsedProcess import com.android.server.pm.pkg.component.ParsedProcessImpl import android.util.ArrayMap import kotlin.contracts.ExperimentalContracts @@ -45,7 +45,7 @@ class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class, ParsedPr override fun extraParams() = listOf( getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission")), getter(ParsedProcess::getAppClassNamesByPackage, ArrayMap<String, String>().apply { - put("package1", "classname1"); + put("package1", "classname1") }), ) } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt index 0302d5785d8f..290dbd6277b6 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt @@ -17,14 +17,14 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PathPermission -import com.android.server.pm.pkg.component.ParsedProvider +import com.android.internal.pm.pkg.component.ParsedProvider import com.android.server.pm.pkg.component.ParsedProviderImpl import android.os.PatternMatcher import kotlin.contracts.ExperimentalContracts @ExperimentalContracts -class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, ParsedProviderImpl::class) { - +class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, ParsedProviderImpl::class) +{ override val defaultImpl = ParsedProviderImpl() override val creator = ParsedProviderImpl.CREATOR diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt index e2c9439df9cf..3ae7e9220cc6 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedService +import com.android.internal.pm.pkg.component.ParsedService import com.android.server.pm.pkg.component.ParsedServiceImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt index ad607366967f..67dfc6d4f7ef 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedUsesPermission +import com.android.internal.pm.pkg.component.ParsedUsesPermission import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt index d217d63c5b44..1da3a2234ffc 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt @@ -24,26 +24,25 @@ import android.content.pm.SharedLibraryInfo import android.content.pm.VersionedPackage import android.os.PatternMatcher import android.util.ArraySet +import com.android.internal.pm.pkg.component.ParsedActivity +import com.android.internal.pm.pkg.component.ParsedInstrumentation +import com.android.internal.pm.pkg.component.ParsedPermission +import com.android.internal.pm.pkg.component.ParsedPermissionGroup +import com.android.internal.pm.pkg.component.ParsedProcess +import com.android.internal.pm.pkg.component.ParsedProvider +import com.android.internal.pm.pkg.component.ParsedService import com.android.server.pm.PackageSetting import com.android.server.pm.PackageSettingBuilder import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.pm.pkg.PackageUserState -import com.android.server.pm.pkg.PackageUserStateImpl -import com.android.server.pm.pkg.component.ParsedActivity import com.android.server.pm.pkg.component.ParsedActivityImpl import com.android.server.pm.pkg.component.ParsedComponentImpl -import com.android.server.pm.pkg.component.ParsedInstrumentation import com.android.server.pm.pkg.component.ParsedIntentInfoImpl -import com.android.server.pm.pkg.component.ParsedPermission -import com.android.server.pm.pkg.component.ParsedPermissionGroup import com.android.server.pm.pkg.component.ParsedPermissionImpl -import com.android.server.pm.pkg.component.ParsedProcess import com.android.server.pm.pkg.component.ParsedProcessImpl -import com.android.server.pm.pkg.component.ParsedProvider import com.android.server.pm.pkg.component.ParsedProviderImpl -import com.android.server.pm.pkg.component.ParsedService import com.android.server.pm.test.parsing.parcelling.AndroidPackageTest import com.google.common.truth.Expect import org.junit.Rule diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt index ec84bc329674..316f3387c539 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt @@ -26,6 +26,8 @@ import android.os.Bundle import android.util.ArrayMap import android.util.SparseArray import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.internal.pm.pkg.component.ParsedPermission +import com.android.internal.pm.pkg.component.ParsedPermissionGroup import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.server.extendedtestutils.wheneverStatic import com.android.server.permission.access.MutableAccessState @@ -39,8 +41,6 @@ import com.android.server.pm.parsing.PackageInfoUtils import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.pm.pkg.PackageUserState -import com.android.server.pm.pkg.component.ParsedPermission -import com.android.server.pm.pkg.component.ParsedPermissionGroup import com.android.server.testutils.any import com.android.server.testutils.mock import com.android.server.testutils.whenever diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 6a95d5c57024..499e7008febd 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -229,7 +229,19 @@ public class DisplayModeDirectorTest { LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight(), 0, APP_MODE_65.getRefreshRate())), - /*displayResolutionRangeVotingEnabled*/ true}}); + /*displayResolutionRangeVotingEnabled*/ true}, + {/*expectedBaseModeId*/ APP_MODE_65.getModeId(), + /*expectedPhysicalRefreshRate*/ 64.99f, + /*expectedAppRequestedRefreshRate*/ 64.99f, + /*votesWithPriorities*/ Map.of( + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()), + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_65.getPhysicalWidth(), + APP_MODE_65.getPhysicalHeight()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forPhysicalRefreshRates( + 0, 64.99f))}}); final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java new file mode 100644 index 000000000000..72dc7259dc1f --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java @@ -0,0 +1,259 @@ +/* + * 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.am; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.annotation.NonNull; +import android.app.usage.UsageStatsManagerInternal; +import android.content.ComponentName; +import android.content.Context; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; +import android.content.pm.ResolveInfo; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.TestLooperManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.SparseArray; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.AlarmManagerInternal; +import com.android.server.DropBoxManagerInternal; +import com.android.server.LocalServices; +import com.android.server.appop.AppOpsService; +import com.android.server.wm.ActivityTaskManagerService; + +import org.junit.Rule; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class BaseBroadcastQueueTest { + + static final int USER_GUEST = 11; + + static final String PACKAGE_ANDROID = "android"; + static final String PACKAGE_PHONE = "com.android.phone"; + static final String PACKAGE_RED = "com.example.red"; + static final String PACKAGE_GREEN = "com.example.green"; + static final String PACKAGE_BLUE = "com.example.blue"; + static final String PACKAGE_YELLOW = "com.example.yellow"; + static final String PACKAGE_ORANGE = "com.example.orange"; + + static final String PROCESS_SYSTEM = "system"; + + static final String CLASS_RED = "com.example.red.Red"; + static final String CLASS_GREEN = "com.example.green.Green"; + static final String CLASS_BLUE = "com.example.blue.Blue"; + static final String CLASS_YELLOW = "com.example.yellow.Yellow"; + static final String CLASS_ORANGE = "com.example.orange.Orange"; + + static final BroadcastProcessQueue.BroadcastPredicate BROADCAST_PREDICATE_ANY = + (r, i) -> true; + + @Rule + public final ApplicationExitInfoTest.ServiceThreadRule + mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); + + @Mock + AppOpsService mAppOpsService; + @Mock + PackageManagerInternal mPackageManagerInt; + @Mock + UsageStatsManagerInternal mUsageStatsManagerInt; + @Mock + DropBoxManagerInternal mDropBoxManagerInt; + @Mock + AlarmManagerInternal mAlarmManagerInt; + @Mock + ProcessList mProcessList; + + Context mContext; + ActivityManagerService mAms; + BroadcastConstants mConstants; + BroadcastSkipPolicy mSkipPolicy; + HandlerThread mHandlerThread; + TestLooperManager mLooper; + AtomicInteger mNextPid; + + /** + * Map from PID to registered registered runtime receivers. + */ + SparseArray<ReceiverList> mRegisteredReceivers = new SparseArray<>(); + + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mHandlerThread = new HandlerThread(getTag()); + mHandlerThread.start(); + // Pause all event processing until a test chooses to resume + mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation() + .acquireLooperManager(mHandlerThread.getLooper())); + mNextPid = new AtomicInteger(100); + + LocalServices.removeServiceForTest(DropBoxManagerInternal.class); + LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); + LocalServices.removeServiceForTest(AlarmManagerInternal.class); + LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt); + doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); + doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any()); + doAnswer((invocation) -> { + return getUidForPackage(invocation.getArgument(0)); + }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM)); + + final ActivityManagerService realAms = new ActivityManagerService( + new TestInjector(mContext), mServiceThreadRule.getThread()); + realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); + realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); + realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal()); + realAms.mOomAdjuster = spy(realAms.mOomAdjuster); + realAms.mPackageManagerInt = mPackageManagerInt; + realAms.mUsageStatsService = mUsageStatsManagerInt; + realAms.mProcessesReady = true; + mAms = spy(realAms); + + mSkipPolicy = spy(new BroadcastSkipPolicy(mAms)); + doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any()); + doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any()); + + mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); + } + + public void tearDown() throws Exception { + mHandlerThread.quit(); + } + + static int getUidForPackage(@NonNull String packageName) { + switch (packageName) { + case PACKAGE_ANDROID: return android.os.Process.SYSTEM_UID; + case PACKAGE_PHONE: return android.os.Process.PHONE_UID; + case PACKAGE_RED: return android.os.Process.FIRST_APPLICATION_UID + 1; + case PACKAGE_GREEN: return android.os.Process.FIRST_APPLICATION_UID + 2; + case PACKAGE_BLUE: return android.os.Process.FIRST_APPLICATION_UID + 3; + case PACKAGE_YELLOW: return android.os.Process.FIRST_APPLICATION_UID + 4; + case PACKAGE_ORANGE: return android.os.Process.FIRST_APPLICATION_UID + 5; + default: throw new IllegalArgumentException(); + } + } + + static int getUidForPackage(@NonNull String packageName, int userId) { + return UserHandle.getUid(userId, getUidForPackage(packageName)); + } + + private class TestInjector extends ActivityManagerService.Injector { + TestInjector(Context context) { + super(context); + } + + @Override + public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile, + Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandlerThread.getThreadHandler(); + } + + @Override + public ProcessList getProcessList(ActivityManagerService service) { + return mProcessList; + } + } + + abstract String getTag(); + + static ApplicationInfo makeApplicationInfo(String packageName) { + return makeApplicationInfo(packageName, packageName, UserHandle.USER_SYSTEM); + } + + static ApplicationInfo makeApplicationInfo(String packageName, String processName, int userId) { + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = packageName; + ai.processName = processName; + ai.uid = getUidForPackage(packageName, userId); + return ai; + } + + static ResolveInfo withPriority(ResolveInfo info, int priority) { + info.priority = priority; + return info; + } + + static BroadcastFilter withPriority(BroadcastFilter filter, int priority) { + filter.setPriority(priority); + return filter; + } + + static ResolveInfo makeManifestReceiver(String packageName, String name) { + return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM); + } + + static ResolveInfo makeManifestReceiver(String packageName, String name, int userId) { + return makeManifestReceiver(packageName, packageName, name, userId); + } + + static ResolveInfo makeManifestReceiver(String packageName, String processName, + String name, int userId) { + final ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = new ActivityInfo(); + ri.activityInfo.packageName = packageName; + ri.activityInfo.processName = processName; + ri.activityInfo.name = name; + ri.activityInfo.applicationInfo = makeApplicationInfo(packageName, processName, userId); + return ri; + } + + BroadcastFilter makeRegisteredReceiver(ProcessRecord app) { + return makeRegisteredReceiver(app, 0); + } + + BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) { + final ReceiverList receiverList = mRegisteredReceivers.get(app.getPid()); + return makeRegisteredReceiver(receiverList, priority); + } + + static BroadcastFilter makeRegisteredReceiver(ReceiverList receiverList, int priority) { + final IntentFilter filter = new IntentFilter(); + filter.setPriority(priority); + final BroadcastFilter res = new BroadcastFilter(filter, receiverList, + receiverList.app.info.packageName, null, null, null, receiverList.uid, + receiverList.userId, false, false, true); + receiverList.add(res); + return res; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index 08f5d03e0148..2378416f8bd0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -31,17 +31,6 @@ import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_ORDERE import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_PRIORITIZED; import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList; import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList; -import static com.android.server.am.BroadcastQueueTest.CLASS_BLUE; -import static com.android.server.am.BroadcastQueueTest.CLASS_GREEN; -import static com.android.server.am.BroadcastQueueTest.CLASS_RED; -import static com.android.server.am.BroadcastQueueTest.CLASS_YELLOW; -import static com.android.server.am.BroadcastQueueTest.PACKAGE_BLUE; -import static com.android.server.am.BroadcastQueueTest.PACKAGE_GREEN; -import static com.android.server.am.BroadcastQueueTest.PACKAGE_RED; -import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW; -import static com.android.server.am.BroadcastQueueTest.getUidForPackage; -import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver; -import static com.android.server.am.BroadcastQueueTest.withPriority; import static com.android.server.am.BroadcastRecord.isReceiverEquals; import static com.google.common.truth.Truth.assertThat; @@ -74,17 +63,15 @@ import android.appwidget.AppWidgetManager; import android.content.IIntentReceiver; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.media.AudioManager; import android.os.Bundle; import android.os.BundleMerger; import android.os.DropBoxManager; -import android.os.HandlerThread; import android.os.Process; import android.os.SystemClock; -import android.os.TestLooperManager; import android.os.UserHandle; -import android.provider.Settings; import android.util.IndentingPrintWriter; import android.util.Pair; @@ -108,11 +95,12 @@ import java.util.List; import java.util.Objects; @SmallTest -public final class BroadcastQueueModernImplTest { +public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest { + private static final String TAG = "BroadcastQueueModernImplTest"; + private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID; private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1; - @Mock ActivityManagerService mAms; @Mock ProcessRecord mProcess; @Mock BroadcastProcessQueue mQueue1; @@ -120,11 +108,6 @@ public final class BroadcastQueueModernImplTest { @Mock BroadcastProcessQueue mQueue3; @Mock BroadcastProcessQueue mQueue4; - HandlerThread mHandlerThread; - TestLooperManager mLooper; - - BroadcastConstants mConstants; - private BroadcastSkipPolicy mSkipPolicy; BroadcastQueueModernImpl mImpl; BroadcastProcessQueue mHead; @@ -136,22 +119,12 @@ public final class BroadcastQueueModernImplTest { @Before public void setUp() throws Exception { - mHandlerThread = new HandlerThread(getClass().getSimpleName()); - mHandlerThread.start(); - - // Pause all event processing until a test chooses to resume - mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation() - .acquireLooperManager(mHandlerThread.getLooper())); + super.setUp(); - mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); mConstants.DELAY_URGENT_MILLIS = -120_000; mConstants.DELAY_NORMAL_MILLIS = 10_000; mConstants.DELAY_CACHED_MILLIS = 120_000; - mSkipPolicy = spy(new BroadcastSkipPolicy(mAms)); - doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any()); - doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any()); - final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) { public void addBroadcastToHistoryLocked(BroadcastRecord original) { // Ignored @@ -169,7 +142,12 @@ public final class BroadcastQueueModernImplTest { @After public void tearDown() throws Exception { - mHandlerThread.quit(); + super.tearDown(); + } + + @Override + public String getTag() { + return TAG; } /** @@ -225,11 +203,6 @@ public final class BroadcastQueueModernImplTest { List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false); } - private BroadcastRecord makeOrderedBroadcastRecord(Intent intent) { - return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(), - List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true); - } - private BroadcastRecord makeBroadcastRecord(Intent intent, List receivers) { return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(), receivers, false); } @@ -246,8 +219,8 @@ public final class BroadcastQueueModernImplTest { private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options, List receivers, IIntentReceiver resultTo, boolean ordered) { - return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, 42, false, null, - null, null, null, AppOpsManager.OP_NONE, options, receivers, null, resultTo, + return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, TEST_UID, false, + null, null, null, null, AppOpsManager.OP_NONE, options, receivers, null, resultTo, Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM, BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN); } @@ -259,12 +232,12 @@ public final class BroadcastQueueModernImplTest { private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue, BroadcastRecord record, int recordIndex, long enqueueTime) { - queue.enqueueOrReplaceBroadcast(record, recordIndex, (r, i) -> { - throw new UnsupportedOperationException(); - }); record.enqueueTime = enqueueTime; record.enqueueRealTime = enqueueTime; record.enqueueClockTime = enqueueTime; + queue.enqueueOrReplaceBroadcast(record, recordIndex, (r, i) -> { + throw new UnsupportedOperationException(); + }); } @Test @@ -419,6 +392,7 @@ public final class BroadcastQueueModernImplTest { assertFalse(queue.isRunnable()); assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER, queue.getRunnableAtReason()); + assertTrue(queue.shouldBeDeferred()); assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); } @@ -445,6 +419,7 @@ public final class BroadcastQueueModernImplTest { assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertTrue(queue.isRunnable()); assertEquals(BroadcastProcessQueue.REASON_CACHED, queue.getRunnableAtReason()); + assertTrue(queue.shouldBeDeferred()); assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); } @@ -526,11 +501,13 @@ public final class BroadcastQueueModernImplTest { queue.invalidateRunnableAt(); assertThat(queue.getRunnableAt()).isGreaterThan(airplaneRecord.enqueueTime); assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason()); + assertFalse(queue.shouldBeDeferred()); mConstants.MAX_PENDING_BROADCASTS = 1; queue.invalidateRunnableAt(); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueTime); assertEquals(BroadcastProcessQueue.REASON_MAX_PENDING, queue.getRunnableAtReason()); + assertFalse(queue.shouldBeDeferred()); } @Test @@ -549,10 +526,12 @@ public final class BroadcastQueueModernImplTest { queue.setProcessAndUidState(mProcess, true, false); assertThat(queue.getRunnableAt()).isLessThan(timeTickRecord.enqueueTime); assertEquals(BroadcastProcessQueue.REASON_FOREGROUND, queue.getRunnableAtReason()); + assertFalse(queue.shouldBeDeferred()); queue.setProcessAndUidState(mProcess, false, false); assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime); assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason()); + assertFalse(queue.shouldBeDeferred()); } @Test @@ -570,6 +549,7 @@ public final class BroadcastQueueModernImplTest { assertThat(queue.getRunnableAt()).isLessThan(timeTickRecord.enqueueTime); assertEquals(BroadcastProcessQueue.REASON_TOP_PROCESS, queue.getRunnableAtReason()); + assertFalse(queue.shouldBeDeferred()); doReturn(ActivityManager.PROCESS_STATE_SERVICE).when(mProcess).getSetProcState(); queue.setProcessAndUidState(mProcess, false, false); @@ -580,6 +560,7 @@ public final class BroadcastQueueModernImplTest { List.of(makeMockRegisteredReceiver())), 0); assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime); assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason()); + assertFalse(queue.shouldBeDeferred()); } @Test @@ -594,16 +575,19 @@ public final class BroadcastQueueModernImplTest { assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime); assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason()); + assertFalse(queue.shouldBeDeferred()); doReturn(true).when(mProcess).isPersistent(); queue.setProcessAndUidState(mProcess, false, false); assertThat(queue.getRunnableAt()).isLessThan(timeTickRecord.enqueueTime); assertEquals(BroadcastProcessQueue.REASON_PERSISTENT, queue.getRunnableAtReason()); + assertFalse(queue.shouldBeDeferred()); doReturn(false).when(mProcess).isPersistent(); queue.setProcessAndUidState(mProcess, false, false); assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime); assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason()); + assertFalse(queue.shouldBeDeferred()); } @Test @@ -618,6 +602,7 @@ public final class BroadcastQueueModernImplTest { assertThat(queue.getRunnableAt()).isEqualTo(timeTickRecord.enqueueTime); assertEquals(BroadcastProcessQueue.REASON_CORE_UID, queue.getRunnableAtReason()); + assertFalse(queue.shouldBeDeferred()); } @Test @@ -636,10 +621,12 @@ public final class BroadcastQueueModernImplTest { assertEquals(Long.MAX_VALUE, queue.getRunnableAt()); assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER, queue.getRunnableAtReason()); + assertTrue(queue.shouldBeDeferred()); queue.setProcessAndUidState(mProcess, false, false); assertThat(queue.getRunnableAt()).isEqualTo(timeTickRecord.enqueueTime); assertEquals(BroadcastProcessQueue.REASON_CORE_UID, queue.getRunnableAtReason()); + assertFalse(queue.shouldBeDeferred()); } /** @@ -1575,6 +1562,216 @@ public final class BroadcastQueueModernImplTest { verifyPendingRecords(redQueue, List.of(userPresent, timeTick)); } + @Test + public void testDeliveryDeferredForCached() throws Exception { + final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN)); + final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED)); + + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); + final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, + List.of(makeRegisteredReceiver(greenProcess, 0))); + + final Intent batteryChanged = new Intent(Intent.ACTION_BATTERY_CHANGED); + final BroadcastOptions optionsBatteryChanged = + BroadcastOptions.makeWithDeferUntilActive(true); + final BroadcastRecord batteryChangedRecord = makeBroadcastRecord(batteryChanged, + optionsBatteryChanged, + List.of(makeRegisteredReceiver(greenProcess, 10), + makeRegisteredReceiver(redProcess, 0)), + false /* ordered */); + + mImpl.enqueueBroadcastLocked(timeTickRecord); + mImpl.enqueueBroadcastLocked(batteryChangedRecord); + + final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN, + getUidForPackage(PACKAGE_GREEN)); + final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED, + getUidForPackage(PACKAGE_RED)); + assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason()); + assertFalse(greenQueue.shouldBeDeferred()); + assertEquals(BroadcastProcessQueue.REASON_BLOCKED, redQueue.getRunnableAtReason()); + assertFalse(redQueue.shouldBeDeferred()); + + // Simulate process state change + greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */, + true /* processFreezable */); + greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply, + mImpl.mBroadcastConsumerDeferClear); + + assertEquals(BroadcastProcessQueue.REASON_CACHED, greenQueue.getRunnableAtReason()); + assertTrue(greenQueue.shouldBeDeferred()); + // Once the broadcasts to green process are deferred, broadcasts to red process + // shouldn't be blocked anymore. + assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason()); + assertFalse(redQueue.shouldBeDeferred()); + + // All broadcasts to green process should be deferred. + greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i)); + }, false /* andRemove */); + redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i)); + }, false /* andRemove */); + + final Intent packageChanged = new Intent(Intent.ACTION_PACKAGE_CHANGED); + final BroadcastRecord packageChangedRecord = makeBroadcastRecord(packageChanged, + List.of(makeRegisteredReceiver(greenProcess, 0))); + mImpl.enqueueBroadcastLocked(packageChangedRecord); + + assertEquals(BroadcastProcessQueue.REASON_CACHED, greenQueue.getRunnableAtReason()); + assertTrue(greenQueue.shouldBeDeferred()); + assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason()); + assertFalse(redQueue.shouldBeDeferred()); + + // All broadcasts to the green process, including the newly enqueued one, should be + // deferred. + greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i)); + }, false /* andRemove */); + redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i)); + }, false /* andRemove */); + + // Simulate process state change + greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */, + false /* processFreezable */); + greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply, + mImpl.mBroadcastConsumerDeferClear); + + assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason()); + assertFalse(greenQueue.shouldBeDeferred()); + assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason()); + assertFalse(redQueue.shouldBeDeferred()); + + greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i)); + }, false /* andRemove */); + redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i)); + }, false /* andRemove */); + } + + @Test + public void testDeliveryDeferredForCached_withInfiniteDeferred() throws Exception { + final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN)); + final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED)); + + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); + final BroadcastOptions optionsTimeTick = BroadcastOptions.makeWithDeferUntilActive(true); + final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, optionsTimeTick, + List.of(makeRegisteredReceiver(greenProcess, 0)), false /* ordered */); + + final Intent batteryChanged = new Intent(Intent.ACTION_BATTERY_CHANGED); + final BroadcastOptions optionsBatteryChanged = + BroadcastOptions.makeWithDeferUntilActive(true); + final BroadcastRecord batteryChangedRecord = makeBroadcastRecord(batteryChanged, + optionsBatteryChanged, + List.of(makeRegisteredReceiver(greenProcess, 10), + makeRegisteredReceiver(redProcess, 0)), + false /* ordered */); + + mImpl.enqueueBroadcastLocked(timeTickRecord); + mImpl.enqueueBroadcastLocked(batteryChangedRecord); + + final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN, + getUidForPackage(PACKAGE_GREEN)); + final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED, + getUidForPackage(PACKAGE_RED)); + assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason()); + assertFalse(greenQueue.shouldBeDeferred()); + assertEquals(BroadcastProcessQueue.REASON_BLOCKED, redQueue.getRunnableAtReason()); + assertFalse(redQueue.shouldBeDeferred()); + + // Simulate process state change + greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */, + true /* processFreezable */); + greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply, + mImpl.mBroadcastConsumerDeferClear); + + assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER, + greenQueue.getRunnableAtReason()); + assertTrue(greenQueue.shouldBeDeferred()); + // Once the broadcasts to green process are deferred, broadcasts to red process + // shouldn't be blocked anymore. + assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason()); + assertFalse(redQueue.shouldBeDeferred()); + + // All broadcasts to green process should be deferred. + greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i)); + }, false /* andRemove */); + redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i)); + }, false /* andRemove */); + + final Intent packageChanged = new Intent(Intent.ACTION_PACKAGE_CHANGED); + final BroadcastOptions optionsPackageChanged = + BroadcastOptions.makeWithDeferUntilActive(true); + final BroadcastRecord packageChangedRecord = makeBroadcastRecord(packageChanged, + optionsPackageChanged, + List.of(makeRegisteredReceiver(greenProcess, 0)), false /* ordered */); + mImpl.enqueueBroadcastLocked(packageChangedRecord); + + assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER, + greenQueue.getRunnableAtReason()); + assertTrue(greenQueue.shouldBeDeferred()); + assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason()); + assertFalse(redQueue.shouldBeDeferred()); + + // All broadcasts to the green process, including the newly enqueued one, should be + // deferred. + greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i)); + }, false /* andRemove */); + redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i)); + }, false /* andRemove */); + + // Simulate process state change + greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */, + false /* processFreezable */); + greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply, + mImpl.mBroadcastConsumerDeferClear); + + assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason()); + assertFalse(greenQueue.shouldBeDeferred()); + assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason()); + assertFalse(redQueue.shouldBeDeferred()); + + greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i)); + }, false /* andRemove */); + redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> { + assertEquals("Unexpected state for " + r, + BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i)); + }, false /* andRemove */); + } + + // TODO: Reuse BroadcastQueueTest.makeActiveProcessRecord() + private ProcessRecord makeProcessRecord(ApplicationInfo info) { + final ProcessRecord r = spy(new ProcessRecord(mAms, info, info.processName, info.uid)); + r.setPid(mNextPid.incrementAndGet()); + return r; + } + + BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) { + final IIntentReceiver receiver = mock(IIntentReceiver.class); + final ReceiverList receiverList = new ReceiverList(mAms, app, app.getPid(), app.info.uid, + UserHandle.getUserId(app.info.uid), receiver); + return makeRegisteredReceiver(receiverList, priority); + } + private Intent createPackageChangedIntent(int uid, List<String> componentNameList) { final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED); packageChangedIntent.putExtra(Intent.EXTRA_UID, uid); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 1c8e9490435d..33645455ca98 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -62,58 +62,36 @@ import android.app.BroadcastOptions; import android.app.IApplicationThread; import android.app.UidObserver; import android.app.usage.UsageEvents.Event; -import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; -import android.content.Context; import android.content.IIntentReceiver; import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; -import android.content.pm.ResolveInfo; import android.os.Binder; import android.os.Bundle; import android.os.DeadObjectException; -import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.PowerExemptionManager; import android.os.SystemClock; -import android.os.TestLooperManager; import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; import android.util.Pair; -import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import androidx.test.filters.MediumTest; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.server.AlarmManagerInternal; -import com.android.server.DropBoxManagerInternal; -import com.android.server.LocalServices; -import com.android.server.am.ActivityManagerService.Injector; -import com.android.server.appop.AppOpsService; -import com.android.server.wm.ActivityTaskManagerService; - import org.junit.After; import org.junit.Assume; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.mockito.ArgumentMatcher; import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.mockito.verification.VerificationMode; -import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; import java.io.Writer; @@ -126,7 +104,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; @@ -136,13 +113,9 @@ import java.util.function.UnaryOperator; @MediumTest @RunWith(Parameterized.class) @SuppressWarnings("GuardedBy") -public class BroadcastQueueTest { +public class BroadcastQueueTest extends BaseBroadcastQueueTest { private static final String TAG = "BroadcastQueueTest"; - @Rule - public final ApplicationExitInfoTest.ServiceThreadRule - mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); - private final Impl mImpl; private enum Impl { @@ -150,30 +123,8 @@ public class BroadcastQueueTest { MODERN, } - private Context mContext; - private HandlerThread mHandlerThread; - private TestLooperManager mLooper; - private AtomicInteger mNextPid; - - @Mock - private AppOpsService mAppOpsService; - @Mock - private ProcessList mProcessList; - @Mock - private DropBoxManagerInternal mDropBoxManagerInt; - @Mock - private PackageManagerInternal mPackageManagerInt; - @Mock - private UsageStatsManagerInternal mUsageStatsManagerInt; - @Mock - private AlarmManagerInternal mAlarmManagerInt; - - private ActivityManagerService mAms; private BroadcastQueue mQueue; - BroadcastConstants mConstants; - private BroadcastSkipPolicy mSkipPolicy; private UidObserver mUidObserver; - private UidObserver mUidCachedStateObserver; /** * Desired behavior of the next @@ -183,11 +134,6 @@ public class BroadcastQueueTest { ProcessStartBehavior.SUCCESS); /** - * Map from PID to registered registered runtime receivers. - */ - private SparseArray<ReceiverList> mRegisteredReceivers = new SparseArray<>(); - - /** * Collection of all active processes during current test run. */ private List<ProcessRecord> mActiveProcesses = new ArrayList<>(); @@ -208,41 +154,8 @@ public class BroadcastQueueTest { @Before public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + super.setUp(); - mHandlerThread = new HandlerThread(TAG); - mHandlerThread.start(); - - // Pause all event processing until a test chooses to resume - mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation() - .acquireLooperManager(mHandlerThread.getLooper())); - - mNextPid = new AtomicInteger(100); - - LocalServices.removeServiceForTest(DropBoxManagerInternal.class); - LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt); - LocalServices.removeServiceForTest(PackageManagerInternal.class); - LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); - LocalServices.removeServiceForTest(AlarmManagerInternal.class); - LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt); - doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); - doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any()); - doAnswer((invocation) -> { - return getUidForPackage(invocation.getArgument(0)); - }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM)); - - final ActivityManagerService realAms = new ActivityManagerService( - new TestInjector(mContext), mServiceThreadRule.getThread()); - realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); - realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); - realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal()); - realAms.mOomAdjuster = spy(realAms.mOomAdjuster); - realAms.mPackageManagerInt = mPackageManagerInt; - realAms.mUsageStatsService = mUsageStatsManagerInt; - realAms.mProcessesReady = true; - mAms = spy(realAms); doAnswer((invocation) -> { Log.v(TAG, "Intercepting startProcessLocked() for " + Arrays.toString(invocation.getArguments())); @@ -321,21 +234,11 @@ public class BroadcastQueueTest { return null; }).when(mAms).registerUidObserver(any(), anyInt(), eq(ActivityManager.PROCESS_STATE_TOP), any()); - doAnswer((invocation) -> { - mUidCachedStateObserver = invocation.getArgument(0); - return null; - }).when(mAms).registerUidObserver(any(), anyInt(), - eq(ActivityManager.PROCESS_STATE_LAST_ACTIVITY), any()); - mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); mConstants.TIMEOUT = 200; mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0; mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500; - mSkipPolicy = spy(new BroadcastSkipPolicy(mAms)); - doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any()); - doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any()); - final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) { public void addBroadcastToHistoryLocked(BroadcastRecord original) { // Ignored @@ -358,7 +261,7 @@ public class BroadcastQueueTest { @After public void tearDown() throws Exception { - mHandlerThread.quit(); + super.tearDown(); // Verify that all processes have finished handling broadcasts for (ProcessRecord app : mActiveProcesses) { @@ -369,26 +272,9 @@ public class BroadcastQueueTest { } } - private class TestInjector extends Injector { - TestInjector(Context context) { - super(context); - } - - @Override - public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile, - Handler handler) { - return mAppOpsService; - } - - @Override - public Handler getUiHandler(ActivityManagerService service) { - return mHandlerThread.getThreadHandler(); - } - - @Override - public ProcessList getProcessList(ActivityManagerService service) { - return mProcessList; - } + @Override + public String getTag() { + return TAG; } private enum ProcessStartBehavior { @@ -534,62 +420,6 @@ public class BroadcastQueueTest { return Pair.create(app.getPid(), intent.getAction()); } - static ApplicationInfo makeApplicationInfo(String packageName) { - return makeApplicationInfo(packageName, packageName, UserHandle.USER_SYSTEM); - } - - static ApplicationInfo makeApplicationInfo(String packageName, String processName, int userId) { - final ApplicationInfo ai = new ApplicationInfo(); - ai.packageName = packageName; - ai.processName = processName; - ai.uid = getUidForPackage(packageName, userId); - return ai; - } - - static ResolveInfo withPriority(ResolveInfo info, int priority) { - info.priority = priority; - return info; - } - - static BroadcastFilter withPriority(BroadcastFilter filter, int priority) { - filter.setPriority(priority); - return filter; - } - - static ResolveInfo makeManifestReceiver(String packageName, String name) { - return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM); - } - - static ResolveInfo makeManifestReceiver(String packageName, String name, int userId) { - return makeManifestReceiver(packageName, packageName, name, userId); - } - - static ResolveInfo makeManifestReceiver(String packageName, String processName, String name, - int userId) { - final ResolveInfo ri = new ResolveInfo(); - ri.activityInfo = new ActivityInfo(); - ri.activityInfo.packageName = packageName; - ri.activityInfo.processName = processName; - ri.activityInfo.name = name; - ri.activityInfo.applicationInfo = makeApplicationInfo(packageName, processName, userId); - return ri; - } - - private BroadcastFilter makeRegisteredReceiver(ProcessRecord app) { - return makeRegisteredReceiver(app, 0); - } - - private BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) { - final ReceiverList receiverList = mRegisteredReceivers.get(app.getPid()); - final IntentFilter filter = new IntentFilter(); - filter.setPriority(priority); - final BroadcastFilter res = new BroadcastFilter(filter, receiverList, - receiverList.app.info.packageName, null, null, null, receiverList.uid, - receiverList.userId, false, false, true); - receiverList.add(res); - return res; - } - private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp, List<Object> receivers) { return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(), @@ -776,41 +606,6 @@ public class BroadcastQueueTest { eq(userId), anyInt(), anyInt(), any()); } - static final int USER_GUEST = 11; - - static final String PACKAGE_ANDROID = "android"; - static final String PACKAGE_PHONE = "com.android.phone"; - static final String PACKAGE_RED = "com.example.red"; - static final String PACKAGE_GREEN = "com.example.green"; - static final String PACKAGE_BLUE = "com.example.blue"; - static final String PACKAGE_YELLOW = "com.example.yellow"; - static final String PACKAGE_ORANGE = "com.example.orange"; - - static final String PROCESS_SYSTEM = "system"; - - static final String CLASS_RED = "com.example.red.Red"; - static final String CLASS_GREEN = "com.example.green.Green"; - static final String CLASS_BLUE = "com.example.blue.Blue"; - static final String CLASS_YELLOW = "com.example.yellow.Yellow"; - static final String CLASS_ORANGE = "com.example.orange.Orange"; - - static int getUidForPackage(@NonNull String packageName) { - switch (packageName) { - case PACKAGE_ANDROID: return android.os.Process.SYSTEM_UID; - case PACKAGE_PHONE: return android.os.Process.PHONE_UID; - case PACKAGE_RED: return android.os.Process.FIRST_APPLICATION_UID + 1; - case PACKAGE_GREEN: return android.os.Process.FIRST_APPLICATION_UID + 2; - case PACKAGE_BLUE: return android.os.Process.FIRST_APPLICATION_UID + 3; - case PACKAGE_YELLOW: return android.os.Process.FIRST_APPLICATION_UID + 4; - case PACKAGE_ORANGE: return android.os.Process.FIRST_APPLICATION_UID + 5; - default: throw new IllegalArgumentException(); - } - } - - static int getUidForPackage(@NonNull String packageName, int userId) { - return UserHandle.getUid(userId, getUidForPackage(packageName)); - } - /** * Baseline verification of common debugging infrastructure, mostly to make * sure it doesn't crash. diff --git a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java index df46054f0f6f..1838fe884561 100644 --- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -1081,7 +1081,7 @@ public class PowerStatsServiceTest { assertThat(result.powerMonitors).isNotNull(); assertThat(Arrays.stream(result.powerMonitors).map(PowerMonitor::getName).toList()) .containsAtLeast( - "energyconsumer0", + "ENERGYCONSUMER0", "BLUETOOTH/1", "[channelname0]:channelsubsystem0", "[channelname1]:channelsubsystem1"); @@ -1131,7 +1131,7 @@ public class PowerStatsServiceTest { Map<String, PowerMonitor> map = Arrays.stream(supportedPowerMonitorsResult.powerMonitors) .collect(Collectors.toMap(PowerMonitor::getName, pm -> pm)); - PowerMonitor consumer1 = map.get("energyconsumer0"); + PowerMonitor consumer1 = map.get("ENERGYCONSUMER0"); PowerMonitor consumer2 = map.get("BLUETOOTH/1"); PowerMonitor measurement1 = map.get("[channelname0]:channelsubsystem0"); PowerMonitor measurement2 = map.get("[channelname1]:channelsubsystem1"); @@ -1196,6 +1196,6 @@ public class PowerStatsServiceTest { supportedPowerMonitorsResult = new GetSupportedPowerMonitorsResult(); mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult); assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors) - .map(PowerMonitor::getName).toList()).contains("energyconsumer0"); + .map(PowerMonitor::getName).toList()).contains("ENERGYCONSUMER0"); } } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index ad35360bb0bc..30300ec3ad2e 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -52,9 +52,11 @@ import android.Manifest; import android.app.WindowConfiguration; import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; +import android.companion.AssociationRequest; import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.IVirtualDeviceIntentInterceptor; import android.companion.virtual.IVirtualDeviceSoundEffectListener; +import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; @@ -134,6 +136,8 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -259,10 +263,10 @@ public class VirtualDeviceManagerServiceTest { @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; @Mock - private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener; - @Mock private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener mAppsOnVirtualDeviceListener; @Mock + private Consumer<String> mPersistentDeviceIdRemovedListener; + @Mock IPowerManager mIPowerManagerMock; @Mock IThermalService mIThermalServiceMock; @@ -374,9 +378,8 @@ public class VirtualDeviceManagerServiceTest { mCameraAccessController = new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback); - mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null, - null, MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, - 0, 0, -1); + mAssociationInfo = createAssociationInfo( + /* associationId= */ 1, AssociationRequest.DEVICE_PROFILE_APP_STREAMING); mVdms = new VirtualDeviceManagerService(mContext); mLocalService = mVdms.getLocalServiceInstance(); @@ -724,25 +727,36 @@ public class VirtualDeviceManagerServiceTest { } @Test - public void onVirtualDisplayCreatedLocked_listenersNotified() { - mLocalService.registerVirtualDisplayListener(mDisplayListener); - - mLocalService.onVirtualDisplayCreated(DISPLAY_ID_1); + public void onPersistentDeviceIdsRemoved_listenersNotified() { + mLocalService.registerPersistentDeviceIdRemovedListener(mPersistentDeviceIdRemovedListener); + mLocalService.onPersistentDeviceIdsRemoved(Set.of(mDeviceImpl.getPersistentDeviceId())); TestableLooper.get(this).processAllMessages(); - verify(mDisplayListener).onVirtualDisplayCreated(DISPLAY_ID_1); + verify(mPersistentDeviceIdRemovedListener).accept(mDeviceImpl.getPersistentDeviceId()); } @Test - public void onVirtualDisplayRemovedLocked_listenersNotified() { - mLocalService.registerVirtualDisplayListener(mDisplayListener); + public void onCdmAssociationsChanged_persistentDeviceIdRemovedListenersNotified() { + mLocalService.registerPersistentDeviceIdRemovedListener(mPersistentDeviceIdRemovedListener); + mVdms.onCdmAssociationsChanged(List.of(mAssociationInfo)); + TestableLooper.get(this).processAllMessages(); - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + mVdms.onCdmAssociationsChanged(List.of( + createAssociationInfo(2, AssociationRequest.DEVICE_PROFILE_APP_STREAMING), + createAssociationInfo(3, AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION), + createAssociationInfo(4, AssociationRequest.DEVICE_PROFILE_WATCH))); + TestableLooper.get(this).processAllMessages(); - mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID_1); + verify(mPersistentDeviceIdRemovedListener).accept(mDeviceImpl.getPersistentDeviceId()); + + mVdms.onCdmAssociationsChanged(Collections.emptyList()); TestableLooper.get(this).processAllMessages(); - verify(mDisplayListener).onVirtualDisplayRemoved(DISPLAY_ID_1); + verify(mPersistentDeviceIdRemovedListener) + .accept(VirtualDeviceImpl.createPersistentDeviceId(2)); + verify(mPersistentDeviceIdRemovedListener) + .accept(VirtualDeviceImpl.createPersistentDeviceId(3)); + verifyNoMoreInteractions(mPersistentDeviceIdRemovedListener); } @Test @@ -1884,11 +1898,16 @@ public class VirtualDeviceManagerServiceTest { @Test public void getPersistentIdForDevice_invalidDeviceId_returnsNull() { assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_INVALID)).isNull(); - assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_DEFAULT)).isNull(); assertThat(mLocalService.getPersistentIdForDevice(VIRTUAL_DEVICE_ID_2)).isNull(); } @Test + public void getPersistentIdForDevice_defaultDeviceId() { + assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_DEFAULT)).isEqualTo( + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + } + + @Test public void getPersistentIdForDevice_returnsCorrectId() { assertThat(mLocalService.getPersistentIdForDevice(VIRTUAL_DEVICE_ID_1)) .isEqualTo(mDeviceImpl.getPersistentDeviceId()); @@ -1943,6 +1962,14 @@ public class VirtualDeviceManagerServiceTest { return intent.resolveActivity(packageManager); } + private AssociationInfo createAssociationInfo(int associationId, String deviceProfile) { + return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null, + /* tag= */ null, MacAddress.BROADCAST_ADDRESS, /* displayName= */ "", deviceProfile, + /* associatedDevice= */ null, /* selfManaged= */ true, + /* notifyOnDeviceNearby= */ false, /* revoked= */false, /* timeApprovedMs= */0, + /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1); + } + /** Helper class to drop permissions temporarily and restore them at the end of a test. */ static final class DropShellPermissionsTemporarily implements AutoCloseable { DropShellPermissionsTemporarily() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index c632727fd420..9e5bea7d135b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -1680,4 +1680,47 @@ public class HdmiCecLocalDeviceTvTest { assertThat(mHdmiControlService.isSystemAudioActivated()).isTrue(); } + + @Test + public void onAddressAllocated_startRequestActiveSourceAction_playbackActiveSource() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + HdmiCecMessage activeSourceFromPlayback = + HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x1000); + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + + mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); + mNativeWrapper.clearResultMessages(); + mNativeWrapper.onCecMessage(activeSourceFromPlayback); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); + } + + @Test + public void onAddressAllocated_startRequestActiveSourceAction_noActiveSource() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + + mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(activeSourceFromTv); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java index 4406d831dd93..ea113957dddd 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -426,7 +426,7 @@ public class NotificationListenersTest extends UiServiceTestCase { } @Test - public void testOnPackageChanged_removingDisallowedPackage() { + public void testOnPackageChanged_removingPackage_removeFromDisallowed() { NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); VersionedPackage a1 = new VersionedPackage("pkg1", 243); NotificationListenerFilter nlf2 = @@ -440,6 +440,25 @@ public class NotificationListenersTest extends UiServiceTestCase { assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0)) .getDisallowedPackages()).isEmpty(); + assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)) + .getDisallowedPackages()).isEmpty(); + } + + @Test + public void testOnPackageChanged_notRemovingPackage_staysInDisallowed() { + NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); + VersionedPackage a1 = new VersionedPackage("pkg1", 243); + NotificationListenerFilter nlf2 = + new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); + mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); + mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2); + + String[] pkgs = new String[] {"pkg1"}; + int[] uids = new int[] {243}; + mListeners.onPackagesChanged(false, pkgs, uids); + + assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)) + .getDisallowedPackages()).contains(a1); } @Test 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 6792cfe6e788..3803244c7012 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -43,6 +43,7 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; +import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; @@ -72,6 +73,7 @@ import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; +import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; @@ -163,6 +165,7 @@ import android.app.StatsManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.usage.UsageStatsManagerInternal; import android.companion.AssociationInfo; +import android.companion.AssociationRequest; import android.companion.ICompanionDeviceManager; import android.compat.testing.PlatformCompatChangeRule; import android.content.BroadcastReceiver; @@ -3820,6 +3823,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mCompanionMgr.getAssociations(PKG, mUserId)) .thenReturn(singletonList(mock(AssociationInfo.class))); mListener = mock(ManagedServices.ManagedServiceInfo.class); + mListener.component = new ComponentName(PKG, PKG); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); @@ -3870,6 +3874,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mCompanionMgr.getAssociations(PKG, mUserId)) .thenReturn(emptyList()); mListener = mock(ManagedServices.ManagedServiceInfo.class); + mListener.component = new ComponentName(PKG, PKG); when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false); when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); try { @@ -12777,6 +12782,145 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mSnoozeHelper).clearData(anyInt()); } + @Test + @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setNotificationPolicy_mappedToImplicitRule() throws RemoteException { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mService.setCallerIsNormalPackage(); + ZenModeHelper zenHelper = mock(ZenModeHelper.class); + mService.mZenModeHelper = zenHelper; + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + + NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); + mBinderService.setNotificationPolicy("package", policy); + + verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy)); + } + + @Test + @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setNotificationPolicy_systemCaller_setsGlobalPolicy() throws RemoteException { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); + mService.mZenModeHelper = zenModeHelper; + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + mService.isSystemUid = true; + + NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); + mBinderService.setNotificationPolicy("package", policy); + + verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean()); + } + + @Test + @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy() throws RemoteException { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mService.setCallerIsNormalPackage(); + ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); + mService.mZenModeHelper = zenModeHelper; + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + when(mCompanionMgr.getAssociations(anyString(), anyInt())) + .thenReturn(ImmutableList.of( + new AssociationInfo.Builder(1, mUserId, "package") + .setDisplayName("My watch") + .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH) + .build())); + + NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); + mBinderService.setNotificationPolicy("package", policy); + + verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean()); + } + + @Test + @DisableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setNotificationPolicy_withoutCompat_setsGlobalPolicy() throws RemoteException { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mService.setCallerIsNormalPackage(); + ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); + mService.mZenModeHelper = zenModeHelper; + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + + NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); + mBinderService.setNotificationPolicy("package", policy); + + verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean()); + } + + @Test + @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void getNotificationPolicy_mappedFromImplicitRule() throws RemoteException { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mService.setCallerIsNormalPackage(); + ZenModeHelper zenHelper = mock(ZenModeHelper.class); + mService.mZenModeHelper = zenHelper; + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + + mBinderService.getNotificationPolicy("package"); + + verify(zenHelper).getNotificationPolicyFromImplicitZenRule(eq("package")); + } + + @Test + @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setInterruptionFilter_mappedToImplicitRule() throws RemoteException { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mService.setCallerIsNormalPackage(); + ZenModeHelper zenHelper = mock(ZenModeHelper.class); + mService.mZenModeHelper = zenHelper; + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + + mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY); + + verify(zenHelper).applyGlobalZenModeAsImplicitZenRule(eq("package"), anyInt(), + eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS)); + } + + @Test + @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setInterruptionFilter_systemCaller_setsGlobalPolicy() throws RemoteException { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mService.setCallerIsNormalPackage(); + ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); + mService.mZenModeHelper = zenModeHelper; + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + mService.isSystemUid = true; + + mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY); + + verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null), + eq("package"), anyString(), anyInt(), anyBoolean()); + } + + @Test + @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setInterruptionFilter_watchCompanionApp_setsGlobalPolicy() throws RemoteException { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); + mService.mZenModeHelper = zenModeHelper; + when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) + .thenReturn(true); + when(mCompanionMgr.getAssociations(anyString(), anyInt())) + .thenReturn(ImmutableList.of( + new AssociationInfo.Builder(1, mUserId, "package") + .setDisplayName("My watch") + .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH) + .build())); + + mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY); + + verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null), + eq("package"), anyString(), anyInt(), anyBoolean()); + } + private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) throws RemoteException { StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java index 47f15b8df076..1e3b7282e2f7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java @@ -19,6 +19,7 @@ import static com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIM import static com.android.server.notification.SnoozeHelper.EXTRA_KEY; import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -73,6 +74,14 @@ import java.io.IOException; public class SnoozeHelperTest extends UiServiceTestCase { private static final String TEST_CHANNEL_ID = "test_channel_id"; + private static final String XML_TAG_NAME = "snoozed-notifications"; + private static final String XML_SNOOZED_NOTIFICATION = "notification"; + private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context"; + private static final String XML_SNOOZED_NOTIFICATION_KEY = "key"; + private static final String XML_SNOOZED_NOTIFICATION_TIME = "time"; + private static final String XML_SNOOZED_NOTIFICATION_CONTEXT_ID = "id"; + private static final String XML_SNOOZED_NOTIFICATION_VERSION_LABEL = "version"; + @Mock SnoozeHelper.Callback mCallback; @Mock AlarmManager mAm; @Mock ManagedServices.UserProfiles mUserProfiles; @@ -316,6 +325,53 @@ public class SnoozeHelperTest extends UiServiceTestCase { } @Test + public void testSnoozeLimit_maximumPersisted() throws XmlPullParserException, IOException { + final long snoozeTimeout = 1234; + final String snoozeContext = "ctx"; + // Serialize & deserialize notifications so that only persisted lists are used + TypedXmlSerializer serializer = Xml.newFastSerializer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); + serializer.startDocument(null, true); + serializer.startTag(null, XML_TAG_NAME); + // Serialize maximum number of timed + context snoozed notifications, half of each + for (int i = 0; i < CONCURRENT_SNOOZE_LIMIT; i++) { + final boolean timedNotification = i % 2 == 0; + if (timedNotification) { + serializer.startTag(null, XML_SNOOZED_NOTIFICATION); + } else { + serializer.startTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT); + } + serializer.attributeInt(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, 1); + serializer.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, "key" + i); + if (timedNotification) { + serializer.attributeLong(null, XML_SNOOZED_NOTIFICATION_TIME, snoozeTimeout); + serializer.endTag(null, XML_SNOOZED_NOTIFICATION); + } else { + serializer.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID, snoozeContext); + serializer.endTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT); + } + } + serializer.endTag(null, XML_TAG_NAME); + serializer.endDocument(); + serializer.flush(); + + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), "utf-8"); + mSnoozeHelper.readXml(parser, 1); + // Verify that we can't snooze any more notifications + // and that the limit is caused by persisted notifications + assertThat(mSnoozeHelper.canSnooze(1)).isFalse(); + assertThat(mSnoozeHelper.isSnoozed(UserHandle.USER_SYSTEM, "pkg", "key0")).isFalse(); + assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, + "pkg", "key0")).isEqualTo(snoozeTimeout); + assertThat( + mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg", + "key1")).isEqualTo(snoozeContext); + } + + @Test public void testCancelByApp() throws Exception { NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM); NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM); @@ -611,6 +667,7 @@ public class SnoozeHelperTest extends UiServiceTestCase { @Test public void repostGroupSummary_repostsSummary() throws Exception { + final int snoozeDuration = 1000; IntArray profileIds = new IntArray(); profileIds.add(UserHandle.USER_SYSTEM); when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); @@ -618,10 +675,44 @@ public class SnoozeHelperTest extends UiServiceTestCase { "pkg", 1, "one", UserHandle.SYSTEM, "group1", true); NotificationRecord r2 = getNotificationRecord( "pkg", 2, "two", UserHandle.SYSTEM, "group1", false); - mSnoozeHelper.snooze(r, 1000); - mSnoozeHelper.snooze(r2, 1000); + final long snoozeTime = System.currentTimeMillis() + snoozeDuration; + mSnoozeHelper.snooze(r, snoozeDuration); + mSnoozeHelper.snooze(r2, snoozeDuration); + assertEquals(2, mSnoozeHelper.getSnoozed().size()); + assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size()); + // Verify that summary notification was added to the persisted list + assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg", + r.getKey())).isAtLeast(snoozeTime); + + mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey()); + + verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, false); + verify(mCallback, never()).repost(UserHandle.USER_SYSTEM, r2, false); + + assertEquals(1, mSnoozeHelper.getSnoozed().size()); + assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size()); + // Verify that summary notification was removed from the persisted list + assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg", + r.getKey())).isEqualTo(0); + } + + @Test + public void snoozeWithContext_repostGroupSummary_removesPersisted() throws Exception { + final String snoozeContext = "zzzzz"; + IntArray profileIds = new IntArray(); + profileIds.add(UserHandle.USER_SYSTEM); + when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); + NotificationRecord r = getNotificationRecord( + "pkg", 1, "one", UserHandle.SYSTEM, "group1", true); + NotificationRecord r2 = getNotificationRecord( + "pkg", 2, "two", UserHandle.SYSTEM, "group1", false); + mSnoozeHelper.snooze(r, snoozeContext); + mSnoozeHelper.snooze(r2, snoozeContext); assertEquals(2, mSnoozeHelper.getSnoozed().size()); assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size()); + // Verify that summary notification was added to the persisted list + assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM, + "pkg", r.getKey())).isEqualTo(snoozeContext); mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey()); @@ -630,6 +721,9 @@ public class SnoozeHelperTest extends UiServiceTestCase { assertEquals(1, mSnoozeHelper.getSnoozed().size()); assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size()); + // Verify that summary notification was removed from the persisted list + assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM, + "pkg", r.getKey())).isNull(); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java index 27e8f3664a65..8f30f413d4d0 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java @@ -54,6 +54,15 @@ public class TestableNotificationManagerService extends NotificationManagerServi return mRankingHelper; } + /** + * Sets {@link #isSystemUid} and {@link #isSystemAppId} to {@code false}, so that calls to NMS + * methods don't succeed {@link #isCallingUidSystem()} and similar checks. + */ + void setCallerIsNormalPackage() { + isSystemUid = false; + isSystemAppId = false; + } + @Override protected boolean isCallingUidSystem() { countSystemChecks++; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java new file mode 100644 index 000000000000..6cc1c4365fca --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java @@ -0,0 +1,130 @@ +/* + * 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.notification; + +import static com.android.server.notification.ZenAdapters.notificationPolicyToZenPolicy; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.NotificationManager.Policy; +import android.service.notification.ZenPolicy; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.UiServiceTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ZenAdaptersTest extends UiServiceTestCase { + + @Test + public void notificationPolicyToZenPolicy_allCallers() { + Policy policy = new Policy(Policy.PRIORITY_CATEGORY_CALLS, Policy.PRIORITY_SENDERS_ANY, 0); + + ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy); + + assertThat(zenPolicy.getPriorityCategoryCalls()).isEqualTo(ZenPolicy.STATE_ALLOW); + assertThat(zenPolicy.getPriorityCallSenders()).isEqualTo(ZenPolicy.PEOPLE_TYPE_ANYONE); + } + + @Test + public void notificationPolicyToZenPolicy_starredCallers() { + Policy policy = new Policy(Policy.PRIORITY_CATEGORY_CALLS, Policy.PRIORITY_SENDERS_STARRED, + 0); + + ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy); + + assertThat(zenPolicy.getPriorityCategoryCalls()).isEqualTo(ZenPolicy.STATE_ALLOW); + assertThat(zenPolicy.getPriorityCallSenders()).isEqualTo(ZenPolicy.PEOPLE_TYPE_STARRED); + } + + @Test + public void notificationPolicyToZenPolicy_repeatCallers() { + Policy policy = new Policy(Policy.PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0); + + ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy); + + assertThat(zenPolicy.getPriorityCategoryCalls()).isEqualTo(ZenPolicy.STATE_DISALLOW); + assertThat(zenPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(ZenPolicy.STATE_ALLOW); + assertThat(zenPolicy.getPriorityCallSenders()).isEqualTo(ZenPolicy.PEOPLE_TYPE_NONE); + } + + @Test + public void notificationPolicyToZenPolicy_noCallers() { + Policy policy = new Policy(0, 0, 0); + + ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy); + + assertThat(zenPolicy.getPriorityCategoryCalls()).isEqualTo(ZenPolicy.STATE_DISALLOW); + assertThat(zenPolicy.getPriorityCallSenders()).isEqualTo(ZenPolicy.PEOPLE_TYPE_NONE); + } + + @Test + public void notificationPolicyToZenPolicy_conversationsAllowedSendersUnset() { + Policy policy = new Policy(Policy.PRIORITY_CATEGORY_CONVERSATIONS, 0, 0); + + ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy); + + assertThat(zenPolicy.getPriorityCategoryConversations()).isEqualTo(ZenPolicy.STATE_UNSET); + } + + @Test + public void notificationPolicyToZenPolicy_conversationsNotAllowedSendersUnset() { + Policy policy = new Policy(0, 0, 0); + + ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy); + + assertThat(zenPolicy.getPriorityCategoryConversations()).isEqualTo( + ZenPolicy.STATE_DISALLOW); + } + + @Test + public void notificationPolicyToZenPolicy_setEffects() { + Policy policy = new Policy(0, 0, 0, + Policy.SUPPRESSED_EFFECT_BADGE | Policy.SUPPRESSED_EFFECT_LIGHTS); + + ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy); + + assertThat(zenPolicy.getVisualEffectBadge()).isEqualTo(ZenPolicy.STATE_DISALLOW); + assertThat(zenPolicy.getVisualEffectLights()).isEqualTo(ZenPolicy.STATE_DISALLOW); + + assertThat(zenPolicy.getVisualEffectAmbient()).isEqualTo(ZenPolicy.STATE_ALLOW); + assertThat(zenPolicy.getVisualEffectFullScreenIntent()).isEqualTo(ZenPolicy.STATE_ALLOW); + assertThat(zenPolicy.getVisualEffectNotificationList()).isEqualTo(ZenPolicy.STATE_ALLOW); + assertThat(zenPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_ALLOW); + assertThat(zenPolicy.getVisualEffectStatusBar()).isEqualTo(ZenPolicy.STATE_ALLOW); + } + + @Test + public void notificationPolicyToZenPolicy_unsetEffects() { + Policy policy = new Policy(0, 0, 0); + + ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy); + + assertThat(zenPolicy.getVisualEffectAmbient()).isEqualTo(ZenPolicy.STATE_UNSET); + assertThat(zenPolicy.getVisualEffectBadge()).isEqualTo(ZenPolicy.STATE_UNSET); + assertThat(zenPolicy.getVisualEffectFullScreenIntent()).isEqualTo(ZenPolicy.STATE_UNSET); + assertThat(zenPolicy.getVisualEffectLights()).isEqualTo(ZenPolicy.STATE_UNSET); + assertThat(zenPolicy.getVisualEffectNotificationList()).isEqualTo(ZenPolicy.STATE_UNSET); + assertThat(zenPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET); + assertThat(zenPolicy.getVisualEffectStatusBar()).isEqualTo(ZenPolicy.STATE_UNSET); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java new file mode 100644 index 000000000000..8dcf89bb8438 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java @@ -0,0 +1,105 @@ +/* + * 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.notification; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.service.notification.ZenDeviceEffects; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.UiServiceTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class ZenDeviceEffectsTest extends UiServiceTestCase { + + @Test + public void builder() { + ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldDisableTapToWake(true).setShouldDisableTapToWake(false) + .setShouldDisableTiltToWake(true) + .setShouldMaximizeDoze(true) + .setShouldUseNightMode(false) + .setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true) + .build(); + + assertThat(deviceEffects.shouldDimWallpaper()).isTrue(); + assertThat(deviceEffects.shouldDisableAutoBrightness()).isFalse(); + assertThat(deviceEffects.shouldDisableTapToWake()).isFalse(); + assertThat(deviceEffects.shouldDisableTiltToWake()).isTrue(); + assertThat(deviceEffects.shouldDisableTouch()).isFalse(); + assertThat(deviceEffects.shouldDisplayGrayscale()).isFalse(); + assertThat(deviceEffects.shouldMaximizeDoze()).isTrue(); + assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse(); + assertThat(deviceEffects.shouldUseNightMode()).isFalse(); + assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue(); + } + + @Test + public void builder_fromInstance() { + ZenDeviceEffects original = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldDisableTiltToWake(true) + .setShouldUseNightMode(true) + .setShouldSuppressAmbientDisplay(true) + .build(); + + ZenDeviceEffects modified = new ZenDeviceEffects.Builder(original) + .setShouldDisplayGrayscale(true) + .setShouldUseNightMode(false) + .build(); + + assertThat(modified.shouldDimWallpaper()).isTrue(); // from original + assertThat(modified.shouldDisableTiltToWake()).isTrue(); // from original + assertThat(modified.shouldDisplayGrayscale()).isTrue(); // updated + assertThat(modified.shouldUseNightMode()).isFalse(); // updated + assertThat(modified.shouldSuppressAmbientDisplay()).isTrue(); // from original + } + + @Test + public void writeToParcel_parcelsAndUnparcels() { + ZenDeviceEffects source = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldDisableTouch(true) + .setShouldMinimizeRadioUsage(true) + .setShouldUseNightMode(true) + .setShouldSuppressAmbientDisplay(true) + .build(); + + Parcel parcel = Parcel.obtain(); + ZenDeviceEffects copy; + try { + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + copy = ZenDeviceEffects.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + + assertThat(copy.shouldDimWallpaper()).isTrue(); + assertThat(copy.shouldDisableTouch()).isTrue(); + assertThat(copy.shouldMinimizeRadioUsage()).isTrue(); + assertThat(copy.shouldUseNightMode()).isTrue(); + assertThat(copy.shouldSuppressAmbientDisplay()).isTrue(); + assertThat(copy.shouldDisplayGrayscale()).isFalse(); + } +} 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 e8201fdef8de..37aeb57f728e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -22,6 +22,7 @@ import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED; import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE; +import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS; @@ -32,6 +33,7 @@ import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; +import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; @@ -40,8 +42,11 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; 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_OFF; import static android.service.notification.Condition.STATE_FALSE; import static android.service.notification.Condition.STATE_TRUE; +import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS; +import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS; import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED; @@ -50,6 +55,8 @@ 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.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -72,6 +79,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -82,6 +90,7 @@ import android.app.NotificationManager.Policy; import android.content.ComponentName; import android.content.ContentResolver; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; @@ -92,6 +101,7 @@ import android.media.AudioManagerInternal; import android.media.AudioSystem; import android.media.VolumePolicy; import android.net.Uri; +import android.os.Parcel; import android.os.Process; import android.os.UserHandle; import android.platform.test.flag.junit.SetFlagsRule; @@ -100,6 +110,7 @@ import android.provider.Settings.Global; import android.service.notification.Condition; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ScheduleInfo; +import android.service.notification.ZenModeConfig.ZenRule; import android.service.notification.ZenModeDiff; import android.service.notification.ZenPolicy; import android.test.suitebuilder.annotation.SmallTest; @@ -124,6 +135,7 @@ import com.android.server.UiServiceTestCase; import com.android.server.notification.ManagedServices.UserProfiles; import com.google.common.collect.ImmutableList; +import com.google.common.truth.Correspondence; import com.google.protobuf.InvalidProtocolBufferException; import org.junit.Before; @@ -157,27 +169,29 @@ public class ZenModeHelperTest extends UiServiceTestCase { private static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE"; private static final String SCHEDULE_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE"; private static final String CUSTOM_PKG_NAME = "not.android"; + private static final String CUSTOM_APP_LABEL = "This is not Android"; private static final int CUSTOM_PKG_UID = 1; private static final String CUSTOM_RULE_ID = "custom_rule"; - private final String NAME = "name"; - private final ComponentName OWNER = new ComponentName("pkg", "cls"); - private final ComponentName CONFIG_ACTIVITY = new ComponentName("pkg", "act"); - private final ZenPolicy POLICY = new ZenPolicy.Builder().allowAlarms(true).build(); - private final Uri CONDITION_ID = new Uri.Builder().scheme("scheme") + private static final String NAME = "name"; + private static final ComponentName OWNER = new ComponentName("pkg", "cls"); + private static final ComponentName CONFIG_ACTIVITY = new ComponentName("pkg", "act"); + private static final ZenPolicy POLICY = new ZenPolicy.Builder().allowAlarms(true).build(); + private static final Uri CONDITION_ID = new Uri.Builder().scheme("scheme") .authority("authority") .appendPath("path") .appendPath("test") .build(); - private final Condition CONDITION = new Condition(CONDITION_ID, "", Condition.STATE_TRUE); - private final String TRIGGER_DESC = "Every Night, 10pm to 6am"; - private final int TYPE = TYPE_BEDTIME; - private final boolean ALLOW_MANUAL = true; - private final int ICON_RES_ID = 1234; - private final int INTERRUPTION_FILTER = Settings.Global.ZEN_MODE_ALARMS; - private final boolean ENABLED = true; - private final int CREATION_TIME = 123; + private static final Condition CONDITION = new Condition(CONDITION_ID, "", + Condition.STATE_TRUE); + private static final String TRIGGER_DESC = "Every Night, 10pm to 6am"; + private static final int TYPE = TYPE_BEDTIME; + private static final boolean ALLOW_MANUAL = true; + private static final int ICON_RES_ID = 1234; + private static final int INTERRUPTION_FILTER = Settings.Global.ZEN_MODE_ALARMS; + private static final boolean ENABLED = true; + private static final int CREATION_TIME = 123; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -227,6 +241,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { .thenReturn(CUSTOM_PKG_UID); when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( new String[] {pkg}); + ApplicationInfo mockAppInfo = mock(ApplicationInfo.class); + when(mockAppInfo.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL); + when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt())) + .thenReturn(mockAppInfo); mZenModeHelper.mPm = mPackageManager; mZenModeEventLogger.reset(); @@ -334,7 +352,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOff_NoMuteApplied() { - mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_OFF; + mZenModeHelper.mZenMode = ZEN_MODE_OFF; mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0); @@ -635,7 +653,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // 3. apply zen off - verify zen is set to previous ringer (normal) when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); - mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF; + mZenModeHelper.mZenMode = ZEN_MODE_OFF; mZenModeHelper.applyZenToRingerMode(); verify(mAudioManager, atLeastOnce()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -721,7 +739,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // 3. apply zen off - verify ringer remains normal when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); - mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF; + mZenModeHelper.mZenMode = ZEN_MODE_OFF; mZenModeHelper.applyZenToRingerMode(); verify(mAudioManager, atLeastOnce()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -746,7 +764,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // 3. apply zen-off - verify ringer is still silent when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); - mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF; + mZenModeHelper.mZenMode = ZEN_MODE_OFF; mZenModeHelper.applyZenToRingerMode(); verify(mAudioManager, atLeastOnce()).setRingerModeInternal(AudioManager.RINGER_MODE_SILENT, mZenModeHelper.TAG); @@ -781,7 +799,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // 4. apply zen off - verify ringer still silenced when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); - mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF; + mZenModeHelper.mZenMode = ZEN_MODE_OFF; mZenModeHelper.applyZenToRingerMode(); verify(mAudioManager, atLeastOnce()).setRingerModeInternal(AudioManager.RINGER_MODE_SILENT, mZenModeHelper.TAG); @@ -795,7 +813,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // apply zen off multiple times - verify ringer is not set to normal when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); - mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF; + mZenModeHelper.mZenMode = ZEN_MODE_OFF; mZenModeHelper.mConfig = null; // will evaluate config to zen mode off for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer @@ -809,7 +827,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testSilentRingerSavedOnZenOff_startsZenOn() { AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class); mZenModeHelper.mAudioManager = mAudioManager; - mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF; + mZenModeHelper.mZenMode = ZEN_MODE_OFF; mZenModeHelper.mConfig = new ZenModeConfig(); // previously set silent ringer @@ -836,7 +854,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testVibrateRingerSavedOnZenOff_startsZenOn() { AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class); mZenModeHelper.mAudioManager = mAudioManager; - mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF; + mZenModeHelper.mZenMode = ZEN_MODE_OFF; mZenModeHelper.mConfig = new ZenModeConfig(); // previously set silent ringer @@ -1209,7 +1227,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .allowMedia(false) .allowRepeatCallers(false) .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) - .allowMessages(ZenPolicy.PEOPLE_TYPE_CONTACTS) + .allowMessages(PEOPLE_TYPE_CONTACTS) .allowEvents(true) .allowReminders(false) .build(); @@ -2023,10 +2041,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode); // and also that it works to turn it back off again - mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "", + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", Process.SYSTEM_UID, true); - assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode); + assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode); } @Test @@ -2041,7 +2059,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Now turn zen mode off, but via a different package UID -- this should get registered as // "not an action by the user" because some other app is changing zen mode - mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "", CUSTOM_PKG_UID, + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", CUSTOM_PKG_UID, false); // In total, this should be 2 loggable changes @@ -2060,7 +2078,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // - resulting DNDPolicyProto the same as the values in setupZenConfig() assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(), mZenModeEventLogger.getEventId(0)); - assertEquals(Global.ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0)); + assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0)); assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0)); assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(0)); assertEquals(1, mZenModeEventLogger.getNumRulesActive(0)); @@ -2080,7 +2098,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(), mZenModeEventLogger.getEventId(1)); assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getPrevZenMode(1)); - assertEquals(Global.ZEN_MODE_OFF, mZenModeEventLogger.getNewZenMode(1)); + assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getNewZenMode(1)); assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(1)); assertEquals(0, mZenModeEventLogger.getNumRulesActive(1)); assertFalse(mZenModeEventLogger.getIsUserAction(1)); @@ -2144,7 +2162,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // - zen policy is the same as the set-up zen config assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(), mZenModeEventLogger.getEventId(0)); - assertEquals(Global.ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0)); + assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0)); assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0)); assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(0)); assertEquals(1, mZenModeEventLogger.getNumRulesActive(0)); @@ -2157,7 +2175,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(), mZenModeEventLogger.getEventId(1)); assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getPrevZenMode(1)); - assertEquals(Global.ZEN_MODE_OFF, mZenModeEventLogger.getNewZenMode(1)); + assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getNewZenMode(1)); assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(1)); assertEquals(0, mZenModeEventLogger.getNumRulesActive(1)); assertTrue(mZenModeEventLogger.getIsUserAction(1)); @@ -2201,7 +2219,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Turn zen mode off; we want to make sure policy changes do not get logged when zen mode // is off. - mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "", + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", Process.SYSTEM_UID, true); // Change the policy again @@ -2305,7 +2323,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // what the event should reflect. At this time, the policy is the same as initial setup. assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(), mZenModeEventLogger.getEventId(0)); - assertEquals(Global.ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0)); + assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0)); assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0)); assertEquals(1, mZenModeEventLogger.getNumRulesActive(0)); assertFalse(mZenModeEventLogger.getIsUserAction(0)); @@ -2355,7 +2373,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.evaluateZenModeLocked("test", true); // Check that the change actually took: zen mode should be off now - assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode); + assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode); // but still, nothing should've been logged assertEquals(0, mZenModeEventLogger.numLoggedChanges()); @@ -2483,7 +2501,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { true); // Turn off manual mode, call from a package: don't reset UID even though enabler is set - mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, CUSTOM_PKG_NAME, "", 12345, false); // And likewise when turning it back on again @@ -2660,8 +2678,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testCreateAutomaticZenRule_allFields() { + public void zenRuleToAutomaticZenRule_allFields() { mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( + new String[] {OWNER.getPackageName()}); + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.configurationActivity = CONFIG_ACTIVITY; rule.component = OWNER; @@ -2682,7 +2703,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { rule.iconResId = ICON_RES_ID; rule.triggerDescription = TRIGGER_DESC; - AutomaticZenRule actual = mZenModeHelper.createAutomaticZenRule(rule); + mZenModeHelper.mConfig.automaticRules.put(rule.id, rule); + AutomaticZenRule actual = mZenModeHelper.getAutomaticZenRule(rule.id); assertEquals(NAME, actual.getName()); assertEquals(OWNER, actual.getOwner()); @@ -2915,8 +2937,253 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing); } + @Test + public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.mConfig.automaticRules.clear(); + + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, + ZEN_MODE_IMPORTANT_INTERRUPTIONS); + + assertThat(mZenModeHelper.mConfig.automaticRules.values()) + .comparingElementsUsing(IGNORE_TIMESTAMPS) + .containsExactly( + expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + null, true)); + } + + @Test + public void applyGlobalZenModeAsImplicitZenRule_updatesImplicitRuleAndActivatesIt() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.mConfig.automaticRules.clear(); + + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, + ZEN_MODE_IMPORTANT_INTERRUPTIONS); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true); + assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1); + + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, + ZEN_MODE_ALARMS); + + assertThat(mZenModeHelper.mConfig.automaticRules.values()) + .comparingElementsUsing(IGNORE_TIMESTAMPS) + .containsExactly( + expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, null, true)); + } + + @Test + public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.mConfig.automaticRules.clear(); + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, + ZEN_MODE_IMPORTANT_INTERRUPTIONS); + assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1); + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state) + .isEqualTo(STATE_TRUE); + + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, + ZEN_MODE_OFF); + + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state) + .isEqualTo(STATE_FALSE); + } + + @Test + public void applyGlobalZenModeAsImplicitZenRule_modeOffButNoPreviousRule_ignored() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.mConfig.automaticRules.clear(); + + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, + ZEN_MODE_OFF); + + assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty(); + } + + @Test + public void applyGlobalZenModeAsImplicitZenRule_update_unsnoozesRule() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.mConfig.automaticRules.clear(); + + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, + ZEN_MODE_IMPORTANT_INTERRUPTIONS); + assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1); + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse(); + + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true); + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue(); + + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, + ZEN_MODE_ALARMS); + + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse(); + assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state) + .isEqualTo(STATE_TRUE); + } + + @Test + public void applyGlobalZenModeAsImplicitZenRule_flagOff_ignored() { + mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.mConfig.automaticRules.clear(); + + withoutWtfCrash( + () -> mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, + CUSTOM_PKG_UID, + ZEN_MODE_IMPORTANT_INTERRUPTIONS)); + + assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty(); + } + + @Test + public void applyGlobalPolicyAsImplicitZenRule_createsImplicitRule() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.mConfig.automaticRules.clear(); + + 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); + + ZenPolicy expectedZenPolicy = new ZenPolicy.Builder() + .disallowAllSounds() + .allowCalls(PEOPLE_TYPE_CONTACTS) + .allowConversations(CONVERSATION_SENDERS_IMPORTANT) + .hideAllVisualEffects() + .build(); + assertThat(mZenModeHelper.mConfig.automaticRules.values()) + .comparingElementsUsing(IGNORE_TIMESTAMPS) + .containsExactly( + expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + expectedZenPolicy, /* conditionActive= */ null)); + } + + @Test + public void applyGlobalPolicyAsImplicitZenRule_updatesImplicitRule() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.mConfig.automaticRules.clear(); + + Policy original = 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, + 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); + + ZenPolicy expectedZenPolicy = new ZenPolicy.Builder() + .disallowAllSounds() + .allowCalls(PEOPLE_TYPE_STARRED) + .allowConversations(CONVERSATION_SENDERS_IMPORTANT) + .hideAllVisualEffects() + .build(); + assertThat(mZenModeHelper.mConfig.automaticRules.values()) + .comparingElementsUsing(IGNORE_TIMESTAMPS) + .containsExactly( + expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + expectedZenPolicy, /* conditionActive= */ null)); + } + + @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))); + + assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty(); + } + + @Test + public void getNotificationPolicyFromImplicitZenRule_returnsSetPolicy() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + Policy writtenPolicy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS, + PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED, + Policy.getAllSuppressedVisualEffects(), STATE_FALSE, + CONVERSATION_SENDERS_IMPORTANT); + mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, + writtenPolicy); + + Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule( + CUSTOM_PKG_NAME); + + assertThat(readPolicy).isEqualTo(writtenPolicy); + } + + @Test + public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_returnsGlobalPolicy() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, + ZEN_MODE_ALARMS); + mZenModeHelper.mConfig.allowCalls = true; + mZenModeHelper.mConfig.allowConversations = false; + + Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule( + CUSTOM_PKG_NAME); + + assertThat(readPolicy).isNotNull(); + assertThat(readPolicy.allowCalls()).isTrue(); + assertThat(readPolicy.allowConversations()).isFalse(); + } + + @Test + public void getNotificationPolicyFromImplicitZenRule_noImplicitRule_returnsGlobalPolicy() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + mZenModeHelper.mConfig.allowCalls = true; + mZenModeHelper.mConfig.allowConversations = false; + + Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule( + CUSTOM_PKG_NAME); + + assertThat(readPolicy).isNotNull(); + assertThat(readPolicy.allowCalls()).isTrue(); + assertThat(readPolicy.allowConversations()).isFalse(); + } + + private static final Correspondence<ZenRule, ZenRule> IGNORE_TIMESTAMPS = + Correspondence.transforming(zr -> { + Parcel p = Parcel.obtain(); + try { + zr.writeToParcel(p, 0); + p.setDataPosition(0); + ZenRule copy = new ZenRule(p); + copy.creationTime = 0; + return copy; + } finally { + p.recycle(); + } + }, + "Ignoring timestamps"); + + private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy, + @Nullable Boolean conditionActive) { + ZenRule rule = new ZenModeConfig.ZenRule(); + rule.id = "implicit_" + ownerPkg; + rule.conditionId = Uri.parse("condition://android/implicit/" + ownerPkg); + if (conditionActive != null) { + rule.condition = conditionActive + ? new Condition(rule.conditionId, + mContext.getString(R.string.zen_mode_implicit_activated), STATE_TRUE) + : new Condition(rule.conditionId, + mContext.getString(R.string.zen_mode_implicit_deactivated), + STATE_FALSE); + } + rule.zenMode = zenMode; + rule.zenPolicy = policy; + rule.pkg = ownerPkg; + rule.name = CUSTOM_APP_LABEL; + rule.enabled = true; + return rule; + } + private void setupZenConfig() { - mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF; + mZenModeHelper.mZenMode = ZEN_MODE_OFF; mZenModeHelper.mConfig.allowAlarms = false; mZenModeHelper.mConfig.allowMedia = false; mZenModeHelper.mConfig.allowSystem = false; @@ -2965,6 +3232,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(STATE_ALLOW, dndProto.getNotificationList().getNumber()); } + private static void withoutWtfCrash(Runnable test) { + Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> {}); + try { + test.run(); + } finally { + Log.setWtfHandler(oldHandler); + } + } + /** * Wrapper to use TypedXmlPullParser as XmlResourceParser for Resources.getXml() */ diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java index 7a2bb5a90846..f0803418376f 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -75,8 +75,6 @@ import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; -import android.util.ArraySet; -import android.view.Display; import androidx.test.InstrumentationRegistry; @@ -103,7 +101,7 @@ public class VibrationSettingsTest { public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final int UID = 1; - private static final int VIRTUAL_DISPLAY_ID = 1; + private static final int VIRTUAL_DEVICE_ID = 1; private static final String SYSUI_PACKAGE_NAME = "sysui"; private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build(); private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder() @@ -137,9 +135,6 @@ public class VibrationSettingsTest { private VibrationSettings mVibrationSettings; private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener; private BroadcastReceiver mRegisteredBatteryBroadcastReceiver; - private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener; - private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener - mRegisteredAppsOnVirtualDeviceListener; @Before public void setUp() throws Exception { @@ -155,14 +150,6 @@ public class VibrationSettingsTest { }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any()); when(mPackageManagerInternalMock.getSystemUiServiceComponent()) .thenReturn(new ComponentName(SYSUI_PACKAGE_NAME, "")); - doAnswer(invocation -> { - mRegisteredVirtualDisplayListener = invocation.getArgument(0); - return null; - }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any()); - doAnswer(invocation -> { - mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0); - return null; - }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any()); removeServicesForTest(); addServicesForTest(); @@ -654,62 +641,20 @@ public class VibrationSettingsTest { } @Test - public void shouldIgnoreVibrationFromVirtualDisplays_displayNonVirtual_neverIgnored() { - // Vibrations from the primary display is never ignored regardless of the creation and - // removal of virtual displays and of the changes of apps running on virtual displays. - mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID); - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged( - new ArraySet<>(Arrays.asList(UID))); - for (int usage : ALL_USAGES) { - assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY); - } - - mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID); + public void shouldIgnoreVibrationFromVirtualDevices_defaultDevice_neverIgnored() { + // Vibrations from the primary device is never ignored. for (int usage : ALL_USAGES) { - assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY); - } - - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>()); - for (int usage : ALL_USAGES) { - assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY); + assertVibrationNotIgnoredForUsageAndDevice(usage, Context.DEVICE_ID_DEFAULT); } } @Test - public void shouldIgnoreVibrationFromVirtualDisplays_displayVirtual() { - // Ignore the vibration when the coming display id represents a virtual display. - mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID); - + public void shouldIgnoreVibrationFromVirtualDevices_virtualDevice_alwaysIgnored() { + // Ignore the vibration when the coming device id represents a virtual device. for (int usage : ALL_USAGES) { - assertVibrationIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID, + assertVibrationIgnoredForUsageAndDevice(usage, VIRTUAL_DEVICE_ID, Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE); } - - // Stop ignoring when the virtual display is removed. - mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID); - for (int usage : ALL_USAGES) { - assertVibrationNotIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID); - } - } - - - @Test - public void shouldIgnoreVibrationFromVirtualDisplays_appsOnVirtualDisplay() { - // Ignore when the passed-in display id is invalid and the calling uid is on a virtual - // display. - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged( - new ArraySet<>(Arrays.asList(UID))); - for (int usage : ALL_USAGES) { - assertVibrationIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY, - Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE); - } - - // Stop ignoring when the app is no longer on virtual display. - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>()); - for (int usage : ALL_USAGES) { - assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY); - } - } @Test @@ -932,13 +877,13 @@ public class VibrationSettingsTest { private void assertVibrationIgnoredForUsage(@VibrationAttributes.Usage int usage, Vibration.Status expectedStatus) { - assertVibrationIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY, expectedStatus); + assertVibrationIgnoredForUsageAndDevice(usage, Context.DEVICE_ID_DEFAULT, expectedStatus); } - private void assertVibrationIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage, - int displayId, Vibration.Status expectedStatus) { + private void assertVibrationIgnoredForUsageAndDevice(@VibrationAttributes.Usage int usage, + int deviceId, Vibration.Status expectedStatus) { Vibration.CallerInfo callerInfo = new Vibration.CallerInfo( - VibrationAttributes.createForUsage(usage), UID, displayId, null, null); + VibrationAttributes.createForUsage(usage), UID, deviceId, null, null); assertEquals(errorMessageForUsage(usage), expectedStatus, mVibrationSettings.shouldIgnoreVibration(callerInfo)); } @@ -946,7 +891,7 @@ public class VibrationSettingsTest { private void assertVibrationIgnoredForAttributes(VibrationAttributes attrs, Vibration.Status expectedStatus) { Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID, - Display.DEFAULT_DISPLAY, null, null); + Context.DEVICE_ID_DEFAULT, null, null); assertEquals(errorMessageForAttributes(attrs), expectedStatus, mVibrationSettings.shouldIgnoreVibration(callerInfo)); } @@ -957,27 +902,27 @@ public class VibrationSettingsTest { private void assertVibrationNotIgnoredForUsageAndFlags(@VibrationAttributes.Usage int usage, @VibrationAttributes.Flag int flags) { - assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, Display.DEFAULT_DISPLAY, flags); + assertVibrationNotIgnoredForUsageAndFlagsAndDevice(usage, Context.DEVICE_ID_DEFAULT, flags); } - private void assertVibrationNotIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage, - int displayId) { - assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, displayId, /* flags= */ 0); + private void assertVibrationNotIgnoredForUsageAndDevice(@VibrationAttributes.Usage int usage, + int deviceId) { + assertVibrationNotIgnoredForUsageAndFlagsAndDevice(usage, deviceId, /* flags= */ 0); } - private void assertVibrationNotIgnoredForUsageAndFlagsAndDisplay( - @VibrationAttributes.Usage int usage, int displayId, + private void assertVibrationNotIgnoredForUsageAndFlagsAndDevice( + @VibrationAttributes.Usage int usage, int deviceId, @VibrationAttributes.Flag int flags) { Vibration.CallerInfo callerInfo = new Vibration.CallerInfo( new VibrationAttributes.Builder().setUsage(usage).setFlags(flags).build(), UID, - displayId, null, null); + deviceId, null, null); assertNull(errorMessageForUsage(usage), mVibrationSettings.shouldIgnoreVibration(callerInfo)); } private void assertVibrationNotIgnoredForAttributes(VibrationAttributes attrs) { Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID, - Display.DEFAULT_DISPLAY, null, null); + Context.DEVICE_ID_DEFAULT, null, null); assertNull(errorMessageForAttributes(attrs), mVibrationSettings.shouldIgnoreVibration(callerInfo)); } @@ -1032,7 +977,7 @@ public class VibrationSettingsTest { private Vibration.CallerInfo createCallerInfo(int uid, String opPkg, @VibrationAttributes.Usage int usage) { VibrationAttributes attrs = VibrationAttributes.createForUsage(usage); - return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DISPLAY_ID, opPkg, null); + return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DEVICE_ID, opPkg, null); } private void setBatteryReceiverRegistrationResult(Intent result) { diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java index 085241ff971a..b0aef47d0900 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -88,7 +88,7 @@ public class VibrationThreadTest { private static final int TEST_TIMEOUT_MILLIS = 900; private static final int UID = Process.ROOT_UID; - private static final int DISPLAY_ID = 10; + private static final int DEVICE_ID = 10; private static final int VIBRATOR_ID = 1; private static final String PACKAGE_NAME = "package"; private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build(); @@ -250,7 +250,7 @@ public class VibrationThreadTest { Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo( Vibration.Status.CANCELLED_SUPERSEDED, new Vibration.CallerInfo( VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM), /* uid= */ - 1, /* displayId= */ -1, /* opPkg= */ null, /* reason= */ null)); + 1, /* deviceId= */ -1, /* opPkg= */ null, /* reason= */ null)); mVibrationConductor.notifyCancelled( cancelVibrationInfo, /* immediate= */ false); @@ -1641,7 +1641,7 @@ public class VibrationThreadTest { private HalVibration createVibration(CombinedVibration effect) { return new HalVibration(mVibrationToken, effect, - new Vibration.CallerInfo(ATTRS, UID, DISPLAY_ID, PACKAGE_NAME, "reason")); + new Vibration.CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason")); } private SparseArray<VibratorController> createVibratorControllers() { diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 3dfaed69dea6..3fce9e7a83ef 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -84,10 +84,8 @@ import android.os.vibrator.VibrationConfig; import android.os.vibrator.VibrationEffectSegment; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; -import android.util.ArraySet; import android.util.SparseArray; import android.util.SparseBooleanArray; -import android.view.Display; import android.view.HapticFeedbackConstants; import android.view.InputDevice; import android.view.flags.Flags; @@ -129,7 +127,7 @@ public class VibratorManagerServiceTest { // be cancelled in the body of the individual test. private static final int CLEANUP_TIMEOUT_MILLIS = 100; private static final int UID = Process.ROOT_UID; - private static final int VIRTUAL_DISPLAY_ID = 1; + private static final int VIRTUAL_DEVICE_ID = 1; private static final String PACKAGE_NAME = "package"; private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build(); private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder() @@ -190,9 +188,6 @@ public class VibratorManagerServiceTest { private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener; private VibratorManagerService.ExternalVibratorService mExternalVibratorService; private VibrationConfig mVibrationConfig; - private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener; - private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener - mRegisteredAppsOnVirtualDeviceListener; private InputManagerGlobal.TestSession mInputManagerGlobalSession; private InputManager mInputManager; @@ -223,14 +218,6 @@ public class VibratorManagerServiceTest { mRegisteredPowerModeListener = invocation.getArgument(0); return null; }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any()); - doAnswer(invocation -> { - mRegisteredVirtualDisplayListener = invocation.getArgument(0); - return null; - }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any()); - doAnswer(invocation -> { - mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0); - return null; - }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any()); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1); @@ -273,6 +260,7 @@ public class VibratorManagerServiceTest { LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.removeServiceForTest(PowerManagerInternal.class); + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); // Ignore potential exceptions about the looper having never dispatched any messages. mTestLooper.stopAutoDispatchAndIgnoreExceptions(); if (mInputManagerGlobalSession != null) { @@ -1510,65 +1498,33 @@ public class VibratorManagerServiceTest { } @Test - public void vibrate_withVirtualDisplayChange_ignoreVibrationFromVirtualDisplay() - throws Exception { + public void vibrate_ignoreVibrationFromVirtualDevice() throws Exception { mockVibrators(1); VibratorManagerService service = createSystemReadyService(); - mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID); - vibrateWithDisplay(service, - VIRTUAL_DISPLAY_ID, + vibrateWithDevice(service, + VIRTUAL_DEVICE_ID, CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.createOneShot(1000, 100)) .combine(), HAPTIC_FEEDBACK_ATTRS); - // Haptic feedback ignored when it's from a virtual display. + // Haptic feedback ignored when it's from a virtual device. assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50)); - mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID); - vibrateWithDisplay(service, - VIRTUAL_DISPLAY_ID, + vibrateWithDevice(service, + Context.DEVICE_ID_DEFAULT, CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.createOneShot(1000, 100)) .combine(), HAPTIC_FEEDBACK_ATTRS); - // Haptic feedback played normally when the virtual display is removed. + // Haptic feedback played normally when it's from the default device. assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); cancelVibrate(service); // Clean up long-ish effect. } @Test - public void vibrate_withAppsOnVirtualDisplayChange_ignoreVibrationFromVirtualDisplay() - throws Exception { - mockVibrators(1); - VibratorManagerService service = createSystemReadyService(); - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged( - new ArraySet<>(Arrays.asList(UID))); - vibrateWithDisplay(service, - Display.INVALID_DISPLAY, - CombinedVibration.startParallel() - .addVibrator(1, VibrationEffect.createOneShot(1000, 100)) - .combine(), - HAPTIC_FEEDBACK_ATTRS); - - // Haptic feedback ignored when it's from an app running virtual display. - assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50)); - - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>()); - vibrateWithDisplay(service, - Display.INVALID_DISPLAY, - CombinedVibration.startParallel() - .addVibrator(1, VibrationEffect.createOneShot(1000, 100)) - .combine(), - HAPTIC_FEEDBACK_ATTRS); - // Haptic feedback played normally when the same app no long runs on a virtual display. - assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); - cancelVibrate(service); // Clean up long-ish effect. - } - - @Test public void vibrate_prebakedAndComposedVibrationsWithFallbacks_playsFallbackOnlyForPredefined() throws Exception { mockVibrators(1); @@ -1685,7 +1641,7 @@ public class VibratorManagerServiceTest { } @Test - public void onExternalVibration_ignoreVibrationFromVirtualDevices() throws Exception { + public void onExternalVibration_ignoreVibrationFromVirtualDevices() { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); createSystemReadyService(); @@ -1697,8 +1653,8 @@ public class VibratorManagerServiceTest { int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale); - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged( - new ArraySet<>(Arrays.asList(UID))); + when(mVirtualDeviceManagerInternalMock.isAppRunningOnAnyVirtualDevice(UID)) + .thenReturn(true); scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); assertEquals(IExternalVibratorService.SCALE_MUTE, scale); } @@ -2396,7 +2352,7 @@ public class VibratorManagerServiceTest { private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service, int constant, boolean always) throws InterruptedException { HalVibration vib = - service.performHapticFeedbackInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, + service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, constant, always, "some reason", service); if (vib != null) { vib.waitForEnd(); @@ -2414,7 +2370,7 @@ public class VibratorManagerServiceTest { private HalVibration vibrateAndWaitUntilFinished(VibratorManagerService service, CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException { HalVibration vib = - service.vibrateWithPermissionCheck(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, + service.vibrateWithPermissionCheck(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, effect, attrs, "some reason", service); if (vib != null) { vib.waitForEnd(); @@ -2430,12 +2386,12 @@ public class VibratorManagerServiceTest { private void vibrate(VibratorManagerService service, CombinedVibration effect, VibrationAttributes attrs) { - vibrateWithDisplay(service, Display.DEFAULT_DISPLAY, effect, attrs); + vibrateWithDevice(service, Context.DEVICE_ID_DEFAULT, effect, attrs); } - private void vibrateWithDisplay(VibratorManagerService service, int displayId, + private void vibrateWithDevice(VibratorManagerService service, int deviceId, CombinedVibration effect, VibrationAttributes attrs) { - service.vibrate(UID, displayId, PACKAGE_NAME, effect, attrs, "some reason", service); + service.vibrate(UID, deviceId, PACKAGE_NAME, effect, attrs, "some reason", service); } private boolean waitUntil(Predicate<VibratorManagerService> predicate, diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 0c996e0155fd..1776ba556b72 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -195,6 +195,8 @@ public class ActivityRecordTests extends WindowTestsBase { setBooted(mAtm); // Because the booted state is set, avoid starting real home if there is no task. doReturn(false).when(mRootWindowContainer).resumeHomeActivity(any(), anyString(), any()); + // Do not execute the transaction, because we can't verify the parameter after it recycles. + doNothing().when(mClientLifecycleManager).scheduleTransaction(any()); } private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() { @@ -262,7 +264,7 @@ public class ActivityRecordTests extends WindowTestsBase { pauseFound.value = true; } return null; - }).when(activity.app.getThread()).scheduleTransaction(any()); + }).when(mClientLifecycleManager).scheduleTransaction(any()); activity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); @@ -477,7 +479,7 @@ public class ActivityRecordTests extends WindowTestsBase { .build(); final Task task = activity.getTask(); activity.setState(DESTROYED, "Testing"); - clearInvocations(mAtm.getLifecycleManager()); + clearInvocations(mClientLifecycleManager); final Configuration newConfig = new Configuration(task.getConfiguration()); newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT @@ -487,7 +489,7 @@ public class ActivityRecordTests extends WindowTestsBase { ensureActivityConfiguration(activity); - verify(mAtm.getLifecycleManager(), never()) + verify(mClientLifecycleManager, never()) .scheduleTransaction(any(), isA(ActivityConfigurationChangeItem.class)); } @@ -500,7 +502,7 @@ public class ActivityRecordTests extends WindowTestsBase { // test properly. activity.finishRelaunching(); // Clear out any calls to scheduleTransaction from launching the activity. - reset(mAtm.getLifecycleManager()); + reset(mClientLifecycleManager); final Task task = activity.getTask(); activity.setState(RESUMED, "Testing"); @@ -517,7 +519,7 @@ public class ActivityRecordTests extends WindowTestsBase { // The configuration change is still sent to the activity, even if it doesn't relaunch. final ActivityConfigurationChangeItem expected = ActivityConfigurationChangeItem.obtain(activity.token, newConfig); - verify(mAtm.getLifecycleManager()).scheduleTransaction( + verify(mClientLifecycleManager).scheduleTransaction( eq(activity.app.getThread()), eq(expected)); } @@ -558,19 +560,7 @@ public class ActivityRecordTests extends WindowTestsBase { activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), activity.getConfiguration())); - clearInvocations(mAtm.getLifecycleManager()); - final Configuration newConfig = new Configuration(activity.getConfiguration()); - final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp); - final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp); - if (newConfig.orientation == ORIENTATION_PORTRAIT) { - newConfig.orientation = ORIENTATION_LANDSCAPE; - newConfig.screenWidthDp = longSide; - newConfig.screenHeightDp = shortSide; - } else { - newConfig.orientation = ORIENTATION_PORTRAIT; - newConfig.screenWidthDp = shortSide; - newConfig.screenHeightDp = longSide; - } + clearInvocations(mClientLifecycleManager); // Mimic the behavior that display doesn't handle app's requested orientation. final DisplayContent dc = activity.getTask().getDisplayContent(); @@ -578,12 +568,15 @@ public class ActivityRecordTests extends WindowTestsBase { doReturn(false).when(dc).handlesOrientationChangeFromDescendant(anyInt()); final int requestedOrientation; - switch (newConfig.orientation) { - case ORIENTATION_LANDSCAPE: + final int expectedOrientation; + switch (activity.getConfiguration().orientation) { + case ORIENTATION_PORTRAIT: requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE; + expectedOrientation = ORIENTATION_LANDSCAPE; break; - case ORIENTATION_PORTRAIT: + case ORIENTATION_LANDSCAPE: requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + expectedOrientation = ORIENTATION_PORTRAIT; break; default: throw new IllegalStateException("Orientation in new config should be either" @@ -595,11 +588,11 @@ public class ActivityRecordTests extends WindowTestsBase { activity.setRequestedOrientation(requestedOrientation); + final Configuration currentConfig = activity.getConfiguration(); + assertEquals(expectedOrientation, currentConfig.orientation); final ActivityConfigurationChangeItem expected = - ActivityConfigurationChangeItem.obtain(activity.token, newConfig); - verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()), - eq(expected)); - + ActivityConfigurationChangeItem.obtain(activity.token, currentConfig); + verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected); verify(displayRotation).onSetRequestedOrientation(); } @@ -788,7 +781,7 @@ public class ActivityRecordTests extends WindowTestsBase { final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); try { - clearInvocations(mAtm.getLifecycleManager()); + clearInvocations(mClientLifecycleManager); doReturn(false).when(stack).isTranslucent(any()); assertTrue(task.shouldBeVisible(null /* starting */)); @@ -796,7 +789,10 @@ public class ActivityRecordTests extends WindowTestsBase { activity.getConfiguration())); final Configuration newConfig = new Configuration(activity.getConfiguration()); - final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp); + final int shortSide = newConfig.screenWidthDp == newConfig.screenHeightDp + // To avoid the case where it is always portrait because of width == height. + ? newConfig.screenWidthDp - 1 + : Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp); final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp); if (newConfig.orientation == ORIENTATION_PORTRAIT) { newConfig.orientation = ORIENTATION_LANDSCAPE; @@ -811,12 +807,12 @@ public class ActivityRecordTests extends WindowTestsBase { task.onConfigurationChanged(newConfig); activity.ensureActivityConfiguration(0 /* globalChanges */, - false /* preserveWindow */, true /* ignoreStopState */); + false /* preserveWindow */, true /* ignoreVisibility */); final ActivityConfigurationChangeItem expected = - ActivityConfigurationChangeItem.obtain(activity.token, newConfig); - verify(mAtm.getLifecycleManager()).scheduleTransaction( - eq(activity.app.getThread()), eq(expected)); + ActivityConfigurationChangeItem.obtain(activity.token, + activity.getConfiguration()); + verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected); } finally { stack.getDisplayArea().removeChild(stack); } @@ -1259,12 +1255,12 @@ public class ActivityRecordTests extends WindowTestsBase { targetActivity.resultTo = sourceActivity; targetActivity.setForceSendResultForMediaProjection(); - clearInvocations(mAtm.getLifecycleManager()); + clearInvocations(mClientLifecycleManager); targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */); try { - verify(mAtm.getLifecycleManager(), atLeastOnce()).scheduleTransaction( + verify(mClientLifecycleManager, atLeastOnce()).scheduleTransaction( any(ClientTransaction.class)); } catch (RemoteException ignored) { } @@ -1283,7 +1279,7 @@ public class ActivityRecordTests extends WindowTestsBase { targetActivity.setState(RESUMED, "test"); targetActivity.resultTo = resultToActivity; - clearInvocations(mAtm.getLifecycleManager()); + clearInvocations(mClientLifecycleManager); targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */); waitUntilHandlersIdle(); @@ -1786,10 +1782,10 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWithTask(); final WindowProcessController wpc = activity.app; setup.accept(activity); - clearInvocations(mAtm.getLifecycleManager()); + clearInvocations(mClientLifecycleManager); activity.getTask().removeImmediately("test"); try { - verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), + verify(mClientLifecycleManager).scheduleTransaction(any(), isA(DestroyActivityItem.class)); } catch (RemoteException ignored) { } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index e7ebd7db1023..3c027ffa5dac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -39,9 +39,10 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.never; @@ -51,7 +52,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.IApplicationThread; import android.app.PictureInPictureParams; -import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.ClientTransactionItem; import android.app.servertransaction.EnterPipRequestedItem; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -90,9 +91,6 @@ import java.util.function.Consumer; @RunWith(WindowTestRunner.class) public class ActivityTaskManagerServiceTests extends WindowTestsBase { - private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor = - ArgumentCaptor.forClass(ClientTransaction.class); - private static final String DEFAULT_PACKAGE_NAME = "my.application.package"; private static final int DEFAULT_USER_ID = 100; @@ -123,53 +121,42 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { final ClientLifecycleManager mockLifecycleManager = mock(ClientLifecycleManager.class); doReturn(mockLifecycleManager).when(mAtm).getLifecycleManager(); doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean()); + clearInvocations(mClientLifecycleManager); mAtm.mActivityClientController.requestPictureInPictureMode(activity); - verify(mockLifecycleManager).scheduleTransaction(mClientTransactionCaptor.capture()); - final ClientTransaction transaction = mClientTransactionCaptor.getValue(); + final ArgumentCaptor<ClientTransactionItem> clientTransactionItemCaptor = + ArgumentCaptor.forClass(ClientTransactionItem.class); + verify(mockLifecycleManager).scheduleTransaction(any(), + clientTransactionItemCaptor.capture()); + final ClientTransactionItem transactionItem = clientTransactionItemCaptor.getValue(); // Check that only an enter pip request item callback was scheduled. - assertEquals(1, transaction.getCallbacks().size()); - assertTrue(transaction.getCallbacks().get(0) instanceof EnterPipRequestedItem); - // Check the activity lifecycle state remains unchanged. - assertNull(transaction.getLifecycleStateRequest()); + assertTrue(transactionItem instanceof EnterPipRequestedItem); } @Test public void testOnPictureInPictureRequested_cannotEnterPip() throws RemoteException { final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity(); - ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager(); doReturn(false).when(activity).inPinnedWindowingMode(); doReturn(false).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean()); + clearInvocations(mClientLifecycleManager); mAtm.mActivityClientController.requestPictureInPictureMode(activity); - verify(lifecycleManager, atLeast(0)) - .scheduleTransaction(mClientTransactionCaptor.capture()); - final ClientTransaction transaction = mClientTransactionCaptor.getValue(); - // Check that none are enter pip request items. - transaction.getCallbacks().forEach(clientTransactionItem -> { - assertFalse(clientTransactionItem instanceof EnterPipRequestedItem); - }); + verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any()); } @Test public void testOnPictureInPictureRequested_alreadyInPIPMode() throws RemoteException { final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity(); - ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager(); doReturn(true).when(activity).inPinnedWindowingMode(); + clearInvocations(mClientLifecycleManager); mAtm.mActivityClientController.requestPictureInPictureMode(activity); - verify(lifecycleManager, atLeast(0)) - .scheduleTransaction(mClientTransactionCaptor.capture()); - final ClientTransaction transaction = mClientTransactionCaptor.getValue(); - // Check that none are enter pip request items. - transaction.getCallbacks().forEach(clientTransactionItem -> { - assertFalse(clientTransactionItem instanceof EnterPipRequestedItem); - }); + verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 0d4c443ce1b0..c6796dc9e90d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -787,22 +787,44 @@ public class LetterboxUiControllerTest extends WindowTestsBase { public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() { spyOn(mController); doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(true); assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); } + @Test + public void testOverrideOrientationIfNeeded_respectOrientationRequestOverUserFullScreen() { + spyOn(mController); + doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(false); + + assertNotEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); + } @Test @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) public void testOverrideOrientationIfNeeded_userFullScreenOverrideOverSystem_returnsUser() { spyOn(mController); doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(true); assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); } @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_respectOrientationReqOverUserFullScreenAndSystem() { + spyOn(mController); + doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(false); + + assertNotEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); + } + + @Test public void testOverrideOrientationIfNeeded_userFullScreenOverrideDisabled_returnsUnchanged() { spyOn(mController); doReturn(false).when(mController).shouldApplyUserFullscreenOverride(); @@ -872,14 +894,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test - public void testShouldApplyUserFullscreenOverride_disabledIgnoreOrientationRequest() { - prepareActivityThatShouldApplyUserFullscreenOverride(); - mDisplayContent.setIgnoreOrientationRequest(false); - - assertFalse(mController.shouldApplyUserFullscreenOverride()); - } - - @Test public void testShouldApplyUserFullscreenOverride_returnsTrue() { prepareActivityThatShouldApplyUserFullscreenOverride(); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 491d5b56c8e2..8de45b039f62 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -202,8 +202,7 @@ public class RecentsAnimationTest extends WindowTestsBase { any() /* starting */, anyInt() /* configChanges */, anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */); doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt()); - ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager(); - doNothing().when(lifecycleManager).scheduleTransaction(any()); + doNothing().when(mClientLifecycleManager).scheduleTransaction(any()); startRecentsActivity(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index a7c14c38b832..c3102e08489d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -31,6 +31,7 @@ import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -1301,6 +1302,27 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testShouldCreateCompatDisplayUserAspectRatioFullscreenOverride() { + setUpDisplaySizeWithApp(1000, 2500); + + // Make the task root resizable. + mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE; + + // Create an activity on the same task. + final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false, + RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + + // Simulate the user selecting the fullscreen user aspect ratio override + spyOn(activity.mWmService.mLetterboxConfiguration); + spyOn(activity.mLetterboxUiController); + doReturn(true).when(activity.mWmService.mLetterboxConfiguration) + .isUserAppAspectRatioFullscreenEnabled(); + doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(activity.mLetterboxUiController) + .getUserMinAspectRatioOverrideCode(); + assertFalse(activity.shouldCreateCompatDisplayInsets()); + } + + @Test @EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS}) public void testNeverSandboxDisplayApis_configEnabled_sandboxingNotApplied() { setUpDisplaySizeWithApp(1000, 1200); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java index 4a335944228a..6655932b060b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -31,7 +31,9 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -143,6 +145,15 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { secureWindow.mAttrs.flags |= FLAG_SECURE; assertEquals(SNAPSHOT_MODE_APP_THEME, mWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask())); + + // Verifies that if the snapshot can be cached, then getSnapshotMode should be respected. + // Otherwise a real snapshot can be taken even if the activity disables recents screenshot. + spyOn(mWm.mTaskSnapshotController); + final int disabledInRecentsTaskId = disabledWindow.getTask().mTaskId; + mAtm.takeTaskSnapshot(disabledInRecentsTaskId, true /* updateCache */); + verify(mWm.mTaskSnapshotController, never()).prepareTaskSnapshot(any(), any()); + mAtm.takeTaskSnapshot(disabledInRecentsTaskId, false /* updateCache */); + verify(mWm.mTaskSnapshotController).prepareTaskSnapshot(any(), any()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index b89182d728ec..46cff8b82c6d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -306,29 +306,28 @@ public class WindowProcessControllerTests extends WindowTestsBase { @Test public void testCachedStateConfigurationChange() throws RemoteException { - final ClientLifecycleManager clientManager = mAtm.getLifecycleManager(); - doNothing().when(clientManager).scheduleTransaction(any(), any()); + doNothing().when(mClientLifecycleManager).scheduleTransaction(any(), any()); final IApplicationThread thread = mWpc.getThread(); final Configuration newConfig = new Configuration(mWpc.getConfiguration()); newConfig.densityDpi += 100; // Non-cached state will send the change directly. mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); - clearInvocations(clientManager); + clearInvocations(mClientLifecycleManager); mWpc.onConfigurationChanged(newConfig); - verify(clientManager).scheduleTransaction(eq(thread), any()); + verify(mClientLifecycleManager).scheduleTransaction(eq(thread), any()); // Cached state won't send the change. - clearInvocations(clientManager); + clearInvocations(mClientLifecycleManager); mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); newConfig.densityDpi += 100; mWpc.onConfigurationChanged(newConfig); - verify(clientManager, never()).scheduleTransaction(eq(thread), any()); + verify(mClientLifecycleManager, never()).scheduleTransaction(eq(thread), any()); // Cached -> non-cached will send the previous deferred config immediately. mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER); final ArgumentCaptor<ConfigurationChangeItem> captor = ArgumentCaptor.forClass(ConfigurationChangeItem.class); - verify(clientManager).scheduleTransaction(eq(thread), captor.capture()); + verify(mClientLifecycleManager).scheduleTransaction(eq(thread), captor.capture()); final ClientTransactionHandler client = mock(ClientTransactionHandler.class); captor.getValue().preExecute(client); final ArgumentCaptor<Configuration> configCaptor = diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index e0ed642d3130..df4af112c087 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -152,6 +152,7 @@ class WindowTestsBase extends SystemServiceTestsBase { ActivityTaskManagerService mAtm; RootWindowContainer mRootWindowContainer; ActivityTaskSupervisor mSupervisor; + ClientLifecycleManager mClientLifecycleManager; WindowManagerService mWm; private final IWindow mIWindow = new TestIWindow(); private Session mTestSession; @@ -215,6 +216,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mAtm = mSystemServicesTestRule.getActivityTaskManagerService(); mSupervisor = mAtm.mTaskSupervisor; mRootWindowContainer = mAtm.mRootWindowContainer; + mClientLifecycleManager = mAtm.getLifecycleManager(); mWm = mSystemServicesTestRule.getWindowManagerService(); mOriginalPerDisplayFocusEnabled = mWm.mPerDisplayFocusEnabled; SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index f64ab22628d9..63de41f80c23 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -206,6 +206,7 @@ public class UsageStatsService extends SystemService implements static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9; static final int MSG_UID_REMOVED = 10; static final int MSG_USER_STARTED = 11; + static final int MSG_NOTIFY_USAGE_EVENT_LISTENER = 12; private final Object mLock = new Object(); private Handler mHandler; @@ -315,6 +316,16 @@ public class UsageStatsService extends SystemService implements Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); return true; } + case MSG_NOTIFY_USAGE_EVENT_LISTENER: { + final int userId = msg.arg1; + final Event event = (Event) msg.obj; + synchronized (mUsageEventListeners) { + final int size = mUsageEventListeners.size(); + for (int i = 0; i < size; ++i) { + mUsageEventListeners.valueAt(i).onUsageEvent(userId, event); + } + } + } } return false; }; @@ -532,9 +543,6 @@ public class UsageStatsService extends SystemService implements } reportEvent(unlockEvent, userId); - mIoHandler.obtainMessage(MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK, - userId, 0).sendToTarget(); - // Remove all the stats stored in system DE. deleteRecursively(new File(Environment.getDataSystemDeDirectory(userId), "usagestats")); @@ -546,6 +554,8 @@ public class UsageStatsService extends SystemService implements userService.persistActiveStats(); } } + + mIoHandler.obtainMessage(MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK, userId, 0).sendToTarget(); } /** @@ -1240,12 +1250,7 @@ public class UsageStatsService extends SystemService implements service.reportEvent(event); } - synchronized (mUsageEventListeners) { - final int size = mUsageEventListeners.size(); - for (int i = 0; i < size; ++i) { - mUsageEventListeners.valueAt(i).onUsageEvent(userId, event); - } - } + mIoHandler.obtainMessage(MSG_NOTIFY_USAGE_EVENT_LISTENER, userId, 0, event).sendToTarget(); } private String getUsageSourcePackage(Event event) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java index bb6cf5246267..fb183754f921 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java @@ -52,9 +52,12 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPH import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA; -import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_SECURITY_EXCEPTION; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_EGRESS_LIMIT_REACHED; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_REMOTE_EXCEPTION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_SECURITY_EXCEPTION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_DETECTION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_REJECTION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_TRAINING_DATA; import static com.android.server.voiceinteraction.HotwordDetectionConnection.ENFORCE_HOTWORD_PHRASE_ID; import android.annotation.NonNull; @@ -73,7 +76,9 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; +import android.os.Parcel; import android.os.ParcelFileDescriptor; +import android.os.Parcelable; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SharedMemory; @@ -94,6 +99,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.infra.AndroidFuture; +import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; import com.android.server.policy.AppOpsPolicy; import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener; @@ -180,6 +186,13 @@ abstract class DetectorSession { private static final int METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION = HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION; + private static final int HOTWORD_EVENT_TYPE_DETECTION = + HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_DETECTION; + private static final int HOTWORD_EVENT_TYPE_REJECTION = + HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_REJECTION; + private static final int HOTWORD_EVENT_TYPE_TRAINING_DATA = + HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_TRAINING_DATA; + private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool(); // TODO: This may need to be a Handler(looper) final ScheduledExecutorService mScheduledExecutorService; @@ -516,6 +529,7 @@ abstract class DetectorSession { if (result != null) { Slog.i(TAG, "Egressed 'hotword rejected result' " + "from hotword trusted process"); + logEgressSizeStats(result); if (mDebugHotwordLogging) { Slog.i(TAG, "Egressed detected result: " + result); } @@ -608,6 +622,7 @@ abstract class DetectorSession { Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult) + " bits from hotword trusted process"); + logEgressSizeStats(newResult); if (mDebugHotwordLogging) { Slog.i(TAG, "Egressed detected result: " + newResult); @@ -624,6 +639,32 @@ abstract class DetectorSession { mVoiceInteractionServiceUid); } + void logEgressSizeStats(HotwordTrainingData data) { + logEgressSizeStats(data, HOTWORD_EVENT_TYPE_TRAINING_DATA); + } + + void logEgressSizeStats(HotwordDetectedResult data) { + logEgressSizeStats(data, HOTWORD_EVENT_TYPE_DETECTION); + + } + + void logEgressSizeStats(HotwordRejectedResult data) { + logEgressSizeStats(data, HOTWORD_EVENT_TYPE_REJECTION); + } + + /** Logs event size stats for events egressed from trusted hotword detection service. */ + private void logEgressSizeStats(Parcelable data, int eventType) { + BackgroundThread.getExecutor().execute(() -> { + Parcel parcel = Parcel.obtain(); + parcel.writeValue(data); + int dataSizeBytes = parcel.dataSize(); + parcel.recycle(); + + HotwordMetricsLogger.writeHotwordDataEgressSize(eventType, dataSizeBytes, + getDetectorType(), mVoiceInteractionServiceUid); + }); + } + /** Used to send training data. * * @hide @@ -723,6 +764,7 @@ abstract class DetectorSession { mVoiceInteractionServiceUid); throw e; } + logEgressSizeStats(data); } void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java index 6418f3e83114..2938a58267d7 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java @@ -186,6 +186,7 @@ final class DspTrustedHotwordDetectorSession extends DetectorSession { if (mDebugHotwordLogging) { Slog.i(TAG, "Egressed detected result: " + newResult); } + logEgressSizeStats(newResult); } } @@ -228,6 +229,7 @@ final class DspTrustedHotwordDetectorSession extends DetectorSession { if (mDebugHotwordLogging && result != null) { Slog.i(TAG, "Egressed rejected result: " + result); } + logEgressSizeStats(result); } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java index f7b66a26ff68..ca72c85af6af 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java @@ -34,6 +34,9 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENT import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__NORMAL_DETECTOR; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; import android.content.Context; @@ -120,6 +123,16 @@ public final class HotwordMetricsLogger { } /** + * Logs hotword event egress size metrics. + */ + public static void writeHotwordDataEgressSize(int eventType, long eventSize, int detectorType, + int uid) { + int metricsDetectorType = getHotwordEventEgressSizeDetectorType(detectorType); + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_EGRESS_SIZE_ATOM_REPORTED, + eventType, eventSize, metricsDetectorType, uid); + } + + /** * Starts a {@link LatencyTracker} log for the time it takes to show the * {@link android.service.voice.VoiceInteractionSession} system UI after a voice trigger. * @@ -224,4 +237,15 @@ public final class HotwordMetricsLogger { return AUDIO_EGRESS_NORMAL_DETECTOR; } } + + private static int getHotwordEventEgressSizeDetectorType(int detectorType) { + switch (detectorType) { + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE: + return HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP: + return HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; + default: + return HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__NORMAL_DETECTOR; + } + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java index 2e23eff7a179..9de7f9ad4922 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java @@ -179,6 +179,7 @@ final class SoftwareTrustedHotwordDetectorSession extends DetectorSession { } Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult) + " bits from hotword trusted process"); + logEgressSizeStats(newResult); if (mDebugHotwordLogging) { Slog.i(TAG, "Egressed detected result: " + newResult); } @@ -194,6 +195,7 @@ final class SoftwareTrustedHotwordDetectorSession extends DetectorSession { HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE, HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED, mVoiceInteractionServiceUid); + logEgressSizeStats(result); // onRejected isn't allowed here, and we are not expecting it. } diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java index b1a7d819cd17..8c6e101f2c03 100644 --- a/telecomm/java/android/telecom/CallAttributes.java +++ b/telecomm/java/android/telecom/CallAttributes.java @@ -24,6 +24,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -83,6 +85,7 @@ public final class CallAttributes implements Parcelable { /** @hide */ @IntDef(value = {DIRECTION_INCOMING, DIRECTION_OUTGOING}) + @Retention(RetentionPolicy.SOURCE) public @interface Direction { } /** @@ -96,6 +99,7 @@ public final class CallAttributes implements Parcelable { /** @hide */ @IntDef(value = {AUDIO_CALL, VIDEO_CALL}) + @Retention(RetentionPolicy.SOURCE) public @interface CallType { } /** @@ -110,6 +114,7 @@ public final class CallAttributes implements Parcelable { /** @hide */ @IntDef(value = {SUPPORTS_SET_INACTIVE, SUPPORTS_STREAM, SUPPORTS_TRANSFER}, flag = true) + @Retention(RetentionPolicy.SOURCE) public @interface CallCapability { } /** diff --git a/telephony/OWNERS b/telephony/OWNERS index 3158ad8fc58e..287aa653ef9a 100644 --- a/telephony/OWNERS +++ b/telephony/OWNERS @@ -7,10 +7,12 @@ rgreenwalt@google.com tgunn@google.com huiwang@google.com jayachandranc@google.com -chinmayd@google.com amruthr@google.com sasindran@google.com # Requiring TL ownership for new carrier config keys. per-file CarrierConfigManager.java=set noparent per-file CarrierConfigManager.java=amruthr@google.com,tgunn@google.com,rgreenwalt@google.com,satk@google.com + +#Domain Selection is jointly owned, add additional owners for domain selection specific files +per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index baeff06a5836..c124079ca2e3 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8875,6 +8875,19 @@ public class CarrierConfigManager { KEY_PREFIX + "child_session_aes_ctr_key_size_int_array"; /** + * List of supported key sizes for AES Galois/Counter Mode (GCM) encryption mode + * of child session. + * Possible values are: + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = + KEY_PREFIX + "child_session_aes_gcm_key_size_int_array"; + + /** * List of supported encryption algorithms for child session. Possible values are * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC}, * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR} @@ -8883,6 +8896,16 @@ public class CarrierConfigManager { KEY_PREFIX + "supported_child_session_encryption_algorithms_int_array"; /** + * List of supported AEAD algorithms for child session. Possible values are + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_8}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_12}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_16} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY = + KEY_PREFIX + "supported_child_session_aead_algorithms_int_array"; + + /** * Time in seconds after which the IKE session is terminated if rekey procedure is not * successful. If not set or set to <= 0, default value is 3600 seconds. */ @@ -8919,6 +8942,19 @@ public class CarrierConfigManager { KEY_PREFIX + "ike_session_encryption_aes_ctr_key_size_int_array"; /** + * List of supported key sizes for AES Galois/Counter Mode (GCM) encryption mode + * of IKE session. + * Possible values - + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = + KEY_PREFIX + "ike_session_encryption_aes_gcm_key_size_int_array"; + + /** * List of supported encryption algorithms for IKE session. Possible values are * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC}, * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR} @@ -8927,6 +8963,16 @@ public class CarrierConfigManager { KEY_PREFIX + "supported_ike_session_encryption_algorithms_int_array"; /** + * List of supported AEAD algorithms for IKE session. Possible values are + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_8}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_12}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_16} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY = + KEY_PREFIX + "supported_ike_session_aead_algorithms_int_array"; + + /** * List of supported integrity algorithms for IKE session. Possible values are * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_NONE}, * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_HMAC_SHA1_96}, @@ -9156,9 +9202,13 @@ public class CarrierConfigManager { KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY, new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC}); defaults.putIntArray( + KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {}); + defaults.putIntArray( KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY, new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC}); defaults.putIntArray( + KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {}); + defaults.putIntArray( KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY, new int[] { SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96, @@ -9207,6 +9257,10 @@ public class CarrierConfigManager { SaProposal.KEY_LEN_AES_192, SaProposal.KEY_LEN_AES_256}); defaults.putIntArray( + KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {}); + defaults.putIntArray( + KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {}); + defaults.putIntArray( KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, new int[] {EPDG_ADDRESS_PLMN, EPDG_ADDRESS_STATIC}); defaults.putIntArray( diff --git a/telephony/java/android/telephony/NumberVerificationCallback.java b/telephony/java/android/telephony/NumberVerificationCallback.java index b00c57351589..71df1f2fa28c 100644 --- a/telephony/java/android/telephony/NumberVerificationCallback.java +++ b/telephony/java/android/telephony/NumberVerificationCallback.java @@ -20,6 +20,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A callback for number verification. After a request for number verification is received, * the system will call {@link #onCallReceived(String)} if a phone call was received from a number @@ -34,6 +37,7 @@ public interface NumberVerificationCallback { REASON_TOO_MANY_CALLS, REASON_CONCURRENT_REQUESTS, REASON_IN_ECBM, REASON_IN_EMERGENCY_CALL}, prefix = {"REASON_"}) + @Retention(RetentionPolicy.SOURCE) @interface NumberVerificationFailureReason {} /** diff --git a/telephony/java/android/telephony/PinResult.java b/telephony/java/android/telephony/PinResult.java index b8c1ffe58371..14713c7d4b12 100644 --- a/telephony/java/android/telephony/PinResult.java +++ b/telephony/java/android/telephony/PinResult.java @@ -25,6 +25,8 @@ import android.os.Parcelable; import com.android.internal.telephony.PhoneConstants; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -46,6 +48,7 @@ public final class PinResult implements Parcelable { PIN_RESULT_TYPE_FAILURE, PIN_RESULT_TYPE_ABORTED, }) + @Retention(RetentionPolicy.SOURCE) public @interface PinResultType {} /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 1c5761dcebe0..b96914e59c0e 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -3400,6 +3400,7 @@ public class TelephonyManager { SIM_STATE_LOADED, SIM_STATE_PRESENT, }) + @Retention(RetentionPolicy.SOURCE) public @interface SimState {} /** @@ -10170,6 +10171,7 @@ public class TelephonyManager { CALL_COMPOSER_STATUS_ON, CALL_COMPOSER_STATUS_OFF, }) + @Retention(RetentionPolicy.SOURCE) public @interface CallComposerStatus {} /** @@ -13157,7 +13159,7 @@ public class TelephonyManager { CARRIER_RESTRICTION_STATUS_RESTRICTED, CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER }) - + @Retention(RetentionPolicy.SOURCE) public @interface CarrierRestrictionStatus { } diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 4b1a72695e50..3e8787281f85 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -951,8 +951,8 @@ public class ApnSetting implements Parcelable { * See 3GPP TS 23.501 section 5.6.13 * * @return True if the PDU session for this APN should always be on and false otherwise - * @hide */ + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public boolean isAlwaysOn() { return mAlwaysOn; } @@ -2282,9 +2282,9 @@ public class ApnSetting implements Parcelable { * See 3GPP TS 23.501 section 5.6.13 * * @param alwaysOn the always on status to set for this APN - * @hide */ - public Builder setAlwaysOn(boolean alwaysOn) { + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) + public @NonNull Builder setAlwaysOn(boolean alwaysOn) { this.mAlwaysOn = alwaysOn; return this; } diff --git a/telephony/java/android/telephony/data/ThrottleStatus.java b/telephony/java/android/telephony/data/ThrottleStatus.java index 0335c6868340..0dff6ff705ca 100644 --- a/telephony/java/android/telephony/data/ThrottleStatus.java +++ b/telephony/java/android/telephony/data/ThrottleStatus.java @@ -27,6 +27,8 @@ import android.os.SystemClock; import android.telephony.AccessNetworkConstants; import android.telephony.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -52,6 +54,7 @@ public final class ThrottleStatus implements Parcelable { ThrottleStatus.THROTTLE_TYPE_NONE, ThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME, }) + @Retention(RetentionPolicy.SOURCE) public @interface ThrottleType { } @@ -76,6 +79,7 @@ public final class ThrottleStatus implements Parcelable { ThrottleStatus.RETRY_TYPE_NEW_CONNECTION, ThrottleStatus.RETRY_TYPE_HANDOVER, }) + @Retention(RetentionPolicy.SOURCE) public @interface RetryType { } diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.java b/telephony/java/android/telephony/ims/MediaQualityStatus.java index 76394feaed66..e2df0d44fda8 100644 --- a/telephony/java/android/telephony/ims/MediaQualityStatus.java +++ b/telephony/java/android/telephony/ims/MediaQualityStatus.java @@ -24,6 +24,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.AccessNetworkConstants.TransportType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -49,6 +51,7 @@ public final class MediaQualityStatus implements Parcelable { MEDIA_SESSION_TYPE_AUDIO, MEDIA_SESSION_TYPE_VIDEO, }) + @Retention(RetentionPolicy.SOURCE) public @interface MediaSessionType {} /** diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java index f367e404a35b..39c9d8b94d3a 100644 --- a/telephony/java/android/telephony/ims/RcsClientConfiguration.java +++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java @@ -22,6 +22,8 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -35,6 +37,7 @@ public final class RcsClientConfiguration implements Parcelable { /**@hide*/ @StringDef(prefix = "RCS_PROFILE_", value = {RCS_PROFILE_1_0, RCS_PROFILE_2_3, RCS_PROFILE_2_4}) + @Retention(RetentionPolicy.SOURCE) public @interface StringRcsProfile {} /** diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 15a20cb91d04..4c53f8ab9bca 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -3147,4 +3147,16 @@ interface ITelephony { + "android.Manifest.permission.SATELLITE_COMMUNICATION)") void unregisterForSatelliteCapabilitiesChanged(int subId, in ISatelliteCapabilitiesCallback callback); + + /** + * This API can be used by only CTS to override the cached value for the device overlay config + * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether + * outgoing satellite datagrams should be sent to modem in demo mode. + * + * @param shouldSendToDemoMode Whether send datagram in demo mode should be sent to satellite + * modem or not. + * + * @return {@code true} if the operation is successful, {@code false} otherwise. + */ + boolean setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode); } diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java index 421ceb797c15..07b733830bd3 100644 --- a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java +++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java @@ -50,7 +50,7 @@ import org.junit.runners.JUnit4; public class VibratorManagerServicePermissionTest { private static final String PACKAGE_NAME = "com.android.framework.permission.tests"; - private static final int DISPLAY_ID = 1; + private static final int DEVICE_ID = 1; private static final CombinedVibration EFFECT = CombinedVibration.createParallel( VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); @@ -107,7 +107,7 @@ public class VibratorManagerServicePermissionTest { @Test public void testVibrateWithoutPermissionFails() throws RemoteException { expectSecurityException("VIBRATE"); - mVibratorService.vibrate(Process.myUid(), DISPLAY_ID, PACKAGE_NAME, EFFECT, ATTRS, + mVibratorService.vibrate(Process.myUid(), DEVICE_ID, PACKAGE_NAME, EFFECT, ATTRS, "testVibrate", new Binder()); } @@ -117,7 +117,7 @@ public class VibratorManagerServicePermissionTest { throws RemoteException { getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( Manifest.permission.VIBRATE); - mVibratorService.vibrate(Process.myUid(), DISPLAY_ID, PACKAGE_NAME, EFFECT, ATTRS, + mVibratorService.vibrate(Process.myUid(), DEVICE_ID, PACKAGE_NAME, EFFECT, ATTRS, "testVibrate", new Binder()); } @@ -127,7 +127,7 @@ public class VibratorManagerServicePermissionTest { expectSecurityException("UPDATE_APP_OPS_STATS"); getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( Manifest.permission.VIBRATE); - mVibratorService.vibrate(Process.SYSTEM_UID, DISPLAY_ID, "android", EFFECT, ATTRS, + mVibratorService.vibrate(Process.SYSTEM_UID, DEVICE_ID, "android", EFFECT, ATTRS, "testVibrate", new Binder()); } @@ -137,7 +137,7 @@ public class VibratorManagerServicePermissionTest { getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( Manifest.permission.VIBRATE, Manifest.permission.UPDATE_APP_OPS_STATS); - mVibratorService.vibrate(Process.SYSTEM_UID, DISPLAY_ID, "android", EFFECT, ATTRS, + mVibratorService.vibrate(Process.SYSTEM_UID, DEVICE_ID, "android", EFFECT, ATTRS, "testVibrate", new Binder()); } diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 159c6fd9ef58..c638873873dc 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -2514,6 +2514,28 @@ int LinkCommand::Action(const std::vector<std::string>& args) { } } + // Parse the feature flag values. An argument that starts with '@' points to a file to read flag + // values from. + std::vector<std::string> all_feature_flags_args; + for (const std::string& arg : feature_flags_args_) { + if (util::StartsWith(arg, "@")) { + const std::string path = arg.substr(1, arg.size() - 1); + std::string error; + if (!file::AppendArgsFromFile(path, &all_feature_flags_args, &error)) { + context.GetDiagnostics()->Error(android::DiagMessage(path) << error); + return 1; + } + } else { + all_feature_flags_args.push_back(arg); + } + } + + for (const std::string& arg : all_feature_flags_args) { + if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) { + return 1; + } + } + if (context.GetPackageType() != PackageType::kStaticLib && stable_id_file_path_) { if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path_.value(), &options_.stable_id_map)) { diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index a08f385b2270..26713fd92264 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -17,11 +17,17 @@ #ifndef AAPT2_LINK_H #define AAPT2_LINK_H +#include <optional> #include <regex> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> #include "Command.h" #include "Resource.h" #include "androidfw/IDiagnostics.h" +#include "cmd/Util.h" #include "format/binary/TableFlattener.h" #include "format/proto/ProtoSerialize.h" #include "link/ManifestFixer.h" @@ -72,6 +78,7 @@ struct LinkOptions { bool use_sparse_encoding = false; std::unordered_set<std::string> extensions_to_not_compress; std::optional<std::regex> regex_to_not_compress; + FeatureFlagValues feature_flag_values; // Static lib options. bool no_static_lib_packages = false; diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index a92f24b82547..678d84628015 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -113,6 +113,56 @@ std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std return std::move(filter); } +bool ParseFeatureFlagsParameter(StringPiece arg, android::IDiagnostics* diag, + FeatureFlagValues* out_feature_flag_values) { + if (arg.empty()) { + return true; + } + + for (StringPiece flag_and_value : util::Tokenize(arg, ',')) { + std::vector<std::string> parts = util::Split(flag_and_value, '='); + if (parts.empty()) { + continue; + } + + if (parts.size() > 2) { + diag->Error(android::DiagMessage() + << "Invalid feature flag and optional value '" << flag_and_value + << "'. Must be in the format 'flag_name[=true|false]"); + return false; + } + + StringPiece flag_name = util::TrimWhitespace(parts[0]); + if (flag_name.empty()) { + diag->Error(android::DiagMessage() << "No name given for one or more flags in: " << arg); + return false; + } + + std::optional<bool> flag_value = {}; + if (parts.size() == 2) { + StringPiece str_flag_value = util::TrimWhitespace(parts[1]); + if (!str_flag_value.empty()) { + flag_value = ResourceUtils::ParseBool(parts[1]); + if (!flag_value.has_value()) { + diag->Error(android::DiagMessage() << "Invalid value for feature flag '" << flag_and_value + << "'. Value must be 'true' or 'false'"); + return false; + } + } + } + + if (auto [it, inserted] = + out_feature_flag_values->try_emplace(std::string(flag_name), flag_value); + !inserted) { + // We are allowing the same flag to appear multiple times, last value wins. + diag->Note(android::DiagMessage() + << "Value for feature flag '" << flag_name << "' was given more than once"); + it->second = flag_value; + } + } + return true; +} + // Adjust the SplitConstraints so that their SDK version is stripped if it // is less than or equal to the minSdk. Otherwise the resources that have had // their SDK version stripped due to minSdk won't ever match. diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h index 712c07b71695..9ece5dd4d720 100644 --- a/tools/aapt2/cmd/Util.h +++ b/tools/aapt2/cmd/Util.h @@ -17,8 +17,13 @@ #ifndef AAPT_SPLIT_UTIL_H #define AAPT_SPLIT_UTIL_H +#include <functional> +#include <map> +#include <memory> +#include <optional> #include <regex> #include <set> +#include <string> #include <unordered_set> #include "AppInfo.h" @@ -32,6 +37,8 @@ namespace aapt { +using FeatureFlagValues = std::map<std::string, std::optional<bool>, std::less<>>; + // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc). // Returns Nothing and logs a human friendly error message if the string was not legal. std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg, @@ -48,6 +55,13 @@ bool ParseSplitParameter(android::StringPiece arg, android::IDiagnostics* diag, std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args, android::IDiagnostics* diag); +// Parses a feature flags parameter, which can contain one or more pairs of flag names and optional +// values, and fills in `out_feature_flag_values` with the parsed values. The pairs in the argument +// are separated by ',' and the name is separated from the value by '=' if there is a value given. +// Example arg: "flag1=true,flag2=false,flag3=,flag4" where flag3 and flag4 have no given value. +bool ParseFeatureFlagsParameter(android::StringPiece arg, android::IDiagnostics* diag, + FeatureFlagValues* out_feature_flag_values); + // Adjust the SplitConstraints so that their SDK version is stripped if it // is less than or equal to the min_sdk. Otherwise the resources that have had // their SDK version stripped due to min_sdk won't ever match. diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp index 139bfbcd0f41..723d87ed0af3 100644 --- a/tools/aapt2/cmd/Util_test.cpp +++ b/tools/aapt2/cmd/Util_test.cpp @@ -25,6 +25,7 @@ #include "util/Files.h" using ::android::ConfigDescription; +using testing::Pair; using testing::UnorderedElementsAre; namespace aapt { @@ -354,6 +355,51 @@ TEST (UtilTest, ParseSplitParameters) { EXPECT_CONFIG_EQ(constraints, expected_configuration); } +TEST(UtilTest, ParseFeatureFlagsParameter_Empty) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_TRUE(ParseFeatureFlagsParameter("", diagnostics, &feature_flag_values)); + EXPECT_TRUE(feature_flag_values.empty()); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_TooManyParts) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_FALSE(ParseFeatureFlagsParameter("foo=bar=baz", diagnostics, &feature_flag_values)); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_NoNameGiven) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,=false", diagnostics, &feature_flag_values)); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_InvalidValue) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,bar=42", diagnostics, &feature_flag_values)); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_DuplicateFlag) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_TRUE( + ParseFeatureFlagsParameter("foo=true,bar=true,foo=false", diagnostics, &feature_flag_values)); + EXPECT_THAT(feature_flag_values, UnorderedElementsAre(Pair("foo", std::optional<bool>(false)), + Pair("bar", std::optional<bool>(true)))); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_Valid) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_TRUE(ParseFeatureFlagsParameter("foo= true, bar =FALSE,baz=, quux", diagnostics, + &feature_flag_values)); + EXPECT_THAT(feature_flag_values, + UnorderedElementsAre(Pair("foo", std::optional<bool>(true)), + Pair("bar", std::optional<bool>(false)), + Pair("baz", std::nullopt), Pair("quux", std::nullopt))); +} + TEST (UtilTest, AdjustSplitConstraintsForMinSdk) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java index 25abbace85de..c770b9ccc800 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java +++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java @@ -65,7 +65,7 @@ public class HostTestUtils { */ public static void onThrowMethodCalled() { // TODO: Maybe add call tracking? - throw new AssumptionViolatedException("This method is not supported on the host side"); + throw new RuntimeException("This method is not supported on the host side"); } /** diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt index b133c2a1f26c..248121c63d78 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt @@ -377,7 +377,7 @@ class AnnotationBasedFilter( throw HostStubGenInternalException("Policy $policy shouldn't show up here") } - val suffix = getAnnotationField(an, "suffix") ?: return@let + val suffix = getAnnotationField(an, "suffix", false) ?: "\$ravenwood" val renameFrom = mn.name + suffix val renameTo = mn.name @@ -387,13 +387,17 @@ class AnnotationBasedFilter( } // This mn has "SubstituteWith". This means, - // 1. Re move the "rename-to" method, so add it to substitutedMethods. + // 1. Re move the "rename-to" method, so add it to substitutedMethods. policiesFromSubstitution[MethodKey(renameTo, mn.desc)] = FilterPolicy.Remove.withReason("substitute-to") + // If the policy is "stub", use "stub". + // Otherwise, it must be "keep" or "throw", but there's no point in using + // "throw", so let's use "keep". + val newPolicy = if (policy.needsInStub) policy else FilterPolicy.Keep // 2. We also keep the from-to in the map. policiesFromSubstitution[MethodKey(renameFrom, mn.desc)] = - policy.withReason("substitute-from") + newPolicy.withReason("substitute-from") substituteToMethods[MethodKey(renameFrom, mn.desc)] = renameTo log.v("Substitution found: %s%s -> %s", renameFrom, mn.desc, renameTo) @@ -405,10 +409,11 @@ class AnnotationBasedFilter( /** * Return the (String) value of 'value' parameter from an annotation. */ - private fun getAnnotationField(an: AnnotationNode, name: String): String? { + private fun getAnnotationField(an: AnnotationNode, name: String, + required: Boolean = true): String? { try { val suffix = findAnnotationValueAsString(an, name) - if (suffix == null) { + if (suffix == null && required) { errors.onErrorFound("Annotation \"${an.desc}\" must have field $name") } return suffix @@ -438,4 +443,4 @@ class AnnotationBasedFilter( return ret } } -}
\ No newline at end of file +} diff --git a/tools/lint/README.md b/tools/lint/README.md index b235ad60c799..ff8e44229189 100644 --- a/tools/lint/README.md +++ b/tools/lint/README.md @@ -103,10 +103,15 @@ out/soong/.intermediates/frameworks/base/services/autofill/services.autofill/and As noted above, this baseline file contains warnings too, which might be undesirable. For example, CI tools might surface these warnings in code reviews. In order to create this file without -warnings, we need to pass another flag to lint: `--nowarn`. The easiest way to do this is to -locally change the soong code in -[lint.go](http://cs/aosp-master/build/soong/java/lint.go;l=451;rcl=2e778d5bc4a8d1d77b4f4a3029a4a254ad57db75) -adding `cmd.Flag("--nowarn")` and running lint again. +warnings, we need to pass another flag to lint: `--nowarn`. One option is to add the flag to your +Android.bp file and then run lint again: + +``` + lint: { + extra_check_modules: ["AndroidFrameworkLintChecker"], + flags: ["--nowarn"], + } +``` # Documentation diff --git a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt index d41fee3fc0dc..24d203fd1116 100644 --- a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt +++ b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt @@ -24,33 +24,31 @@ import com.intellij.psi.PsiReferenceList import org.jetbrains.uast.UMethod /** - * Given a UMethod, determine if this method is - * the entrypoint to an interface generated by AIDL, - * returning the interface name if so, otherwise returning null + * Given a UMethod, determine if this method is the entrypoint to an interface + * generated by AIDL, returning the interface name if so, otherwise returning + * null */ fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? { - if (!isContainedInSubclassOfStub(context, node)) return null - for (superMethod in node.findSuperMethods()) { - for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements - ?: continue) { - if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) { - return superMethod.containingClass?.name - } - } - } - return null + val containingStub = containingStub(context, node) ?: return null + val superMethod = node.findSuperMethods(containingStub) + if (superMethod.isEmpty()) return null + return containingStub.containingClass?.name } -fun isContainedInSubclassOfStub(context: JavaContext, node: UMethod?): Boolean { +/* Returns the containing Stub class if any. This is not sufficient to infer + * that the method itself extends an AIDL generated method. See + * getContainingAidlInterface for that purpose. + */ +fun containingStub(context: JavaContext, node: UMethod?): PsiClass? { var superClass = node?.containingClass?.superClass while (superClass != null) { - if (isStub(context, superClass)) return true + if (isStub(context, superClass)) return superClass superClass = superClass.superClass } - return false + return null } -fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean { +private fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean { if (psiClass == null) return false if (psiClass.name != "Stub") return false if (!context.evaluator.isStatic(psiClass)) return false diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt index 935badecf8d5..624a1987638e 100644 --- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -20,6 +20,7 @@ import com.android.tools.lint.client.api.IssueRegistry import com.android.tools.lint.client.api.Vendor import com.android.tools.lint.detector.api.CURRENT_API import com.google.android.lint.parcel.SaferParcelChecker +import com.google.android.lint.aidl.PermissionAnnotationDetector import com.google.auto.service.AutoService @AutoService(IssueRegistry::class) @@ -37,6 +38,7 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, // TODO: Currently crashes due to OOM issue // PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS, + PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE, PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD, ) diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt new file mode 100644 index 000000000000..6b50cfd9e5ab --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt @@ -0,0 +1,87 @@ +/* + * 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.google.android.lint.aidl + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UMethod + +/** + * Ensures all AIDL-generated methods are annotated. + * + * This detector is run on system_server to validate that any method that may + * be exposed via an AIDL interface is permission-annotated. That is, it must + * have one of the following annotation: + * - @EnforcePermission + * - @RequiresNoPermission + * - @PermissionManuallyEnforced + */ +class PermissionAnnotationDetector : AidlImplementationDetector() { + + override fun visitAidlMethod( + context: JavaContext, + node: UMethod, + interfaceName: String, + body: UBlockExpression + ) { + if (context.evaluator.isAbstract(node)) return + + if (AIDL_PERMISSION_ANNOTATIONS.any { node.hasAnnotation(it) }) return + + context.report( + ISSUE_MISSING_PERMISSION_ANNOTATION, + node, + context.getLocation(node), + "The method ${node.name} is not permission-annotated." + ) + } + + companion object { + + private val EXPLANATION_MISSING_PERMISSION_ANNOTATION = """ + Interfaces that are exposed by system_server are required to have an annotation which + denotes the type of permission enforced. There are 3 possible options: + - @EnforcePermission + - @RequiresNoPermission + - @PermissionManuallyEnforced + See the documentation of each annotation for further details. + + The annotation on the Java implementation must be the same that the AIDL interface + definition. This is verified by a lint in the build system. + """.trimIndent() + + @JvmField + val ISSUE_MISSING_PERMISSION_ANNOTATION = Issue.create( + id = "MissingPermissionAnnotation", + briefDescription = "No permission annotation on exposed AIDL interface.", + explanation = EXPLANATION_MISSING_PERMISSION_ANNOTATION, + category = Category.CORRECTNESS, + priority = 5, + severity = Severity.ERROR, + implementation = Implementation( + PermissionAnnotationDetector::class.java, + Scope.JAVA_FILE_SCOPE + ), + enabledByDefault = false + ) + } +} diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt new file mode 100644 index 000000000000..bce848a2e3a7 --- /dev/null +++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt @@ -0,0 +1,134 @@ +/* + * 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.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class PermissionAnnotationDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = PermissionAnnotationDetector() + + override fun getIssues(): List<Issue> = listOf( + PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + /** No issue scenario */ + + fun testDoesNotDetectIssuesInCorrectScenario() { + lint().files( + java( + """ + public class Foo extends IFoo.Stub { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void testMethod() { } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testMissingAnnotation() { + lint().files( + java( + """ + public class Bar extends IBar.Stub { + public void testMethod() { } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Bar.java:2: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation] + public void testMethod() { } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + ) + } + + fun testNoIssueWhenExtendingWithAnotherSubclass() { + lint().files( + java( + """ + public class Foo extends IFoo.Stub { + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod() { } + // not an AIDL method, just another method + public void someRandomMethod() { } + } + """).indented(), + java( + """ + public class Baz extends Bar { + @Override + public void someRandomMethod() { } + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + /* Stubs */ + + // A service with permission annotation on the method. + private val interfaceIFoo: TestFile = java( + """ + public interface IFoo extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IFoo { + } + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod(); + @Override + @android.annotation.RequiresNoPermission + public void testMethodNoPermission(); + @Override + @android.annotation.PermissionManuallyEnforced + public void testMethodManual(); + } + """ + ).indented() + + // A service with no permission annotation. + private val interfaceIBar: TestFile = java( + """ + public interface IBar extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IBar { + } + public void testMethod(); + } + """ + ).indented() + + private val stubs = arrayOf(interfaceIFoo, interfaceIBar) +} diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt index 83b8f163abee..4455a9cda3a8 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt @@ -168,7 +168,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { annotationInfo.origin == AnnotationOrigin.METHOD) { /* Ignore implementations that are not a sub-class of Stub (i.e., Proxy). */ val uMethod = element as? UMethod ?: return - if (!isContainedInSubclassOfStub(context, uMethod)) { + if (getContainingAidlInterface(context, uMethod) == null) { return } val overridingMethod = element.sourcePsi as PsiMethod @@ -184,7 +184,8 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { if (context.evaluator.isAbstract(node)) return if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return - if (!isContainedInSubclassOfStub(context, node)) { + val stubClass = containingStub(context, node) + if (stubClass == null) { context.report( ISSUE_MISUSING_ENFORCE_PERMISSION, node, @@ -196,7 +197,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { /* Check that we are connected to the super class */ val overridingMethod = node as PsiMethod - val parents = overridingMethod.findSuperMethods() + val parents = overridingMethod.findSuperMethods(stubClass) if (parents.isEmpty()) { context.report( ISSUE_MISUSING_ENFORCE_PERMISSION, diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt index d8afcb977594..2afca05f8130 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt @@ -176,6 +176,29 @@ class EnforcePermissionDetectorTest : LintDetectorTest() { """.addLineContinuation()) } + fun testDetectNoIssuesAnnotationOnNonStubMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass43 extends IFooMethod.Stub { + public void aRegularMethodNotPartOfStub() { + } + } + """).indented(), java( + """ + package test.pkg; + public class TestClass44 extends TestClass43 { + @Override + public void aRegularMethodNotPartOfStub() { + } + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + fun testDetectIssuesEmptyAnnotationOnMethod() { lint().files(java( """ diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java index ebda6f1c5826..4f5e0e48c793 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java @@ -70,14 +70,8 @@ public abstract class SharedConnectivityService extends Service { private List<HotspotNetwork> mHotspotNetworks = Collections.emptyList(); private List<KnownNetwork> mKnownNetworks = Collections.emptyList(); private SharedConnectivitySettingsState mSettingsState = null; - private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus = - new HotspotNetworkConnectionStatus.Builder() - .setStatus(HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN) - .setExtras(Bundle.EMPTY).build(); - private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus = - new KnownNetworkConnectionStatus.Builder() - .setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN) - .setExtras(Bundle.EMPTY).build(); + private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus = null; + private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus = null; // Used for testing private CountDownLatch mCountDownLatch; diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java index c6f67987746a..48ac82dc54a8 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java @@ -394,6 +394,26 @@ public class SharedConnectivityServiceTest { verify(mCallback, never()).onKnownNetworkConnectionStatusChanged(any()); } + @Test + public void getHotspotNetworkConnectionStatus_withoutUpdate_returnsNull() + throws RemoteException { + SharedConnectivityService service = createService(); + ISharedConnectivityService.Stub binder = + (ISharedConnectivityService.Stub) service.onBind(new Intent()); + + assertThat(binder.getHotspotNetworkConnectionStatus()).isNull(); + } + + @Test + public void getKnownNetworkConnectionStatus_withoutUpdate_returnsNull() + throws RemoteException { + SharedConnectivityService service = createService(); + ISharedConnectivityService.Stub binder = + (ISharedConnectivityService.Stub) service.onBind(new Intent()); + + assertThat(binder.getKnownNetworkConnectionStatus()).isNull(); + } + private FakeSharedConnectivityService createService() { FakeSharedConnectivityService service = new FakeSharedConnectivityService(); service.attachBaseContext(mContext); |