diff options
237 files changed, 6539 insertions, 1161 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index bfe188d49ddb..3f834fa883c1 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -22,6 +22,7 @@ aconfig_declarations_group { "aconfig_mediacodec_flags_java_lib", "android.adaptiveauth.flags-aconfig-java", "android.app.flags-aconfig-java", + "android.app.ondeviceintelligence-aconfig-java", "android.app.smartspace.flags-aconfig-java", "android.app.usage.flags-aconfig-java", "android.app.wearable.flags-aconfig-java", @@ -545,6 +546,19 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// OnDeviceIntelligence +aconfig_declarations { + name: "android.app.ondeviceintelligence-aconfig", + package: "android.app.ondeviceintelligence.flags", + srcs: ["core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig"], +} + +java_aconfig_library { + name: "android.app.ondeviceintelligence-aconfig-java", + aconfig_declarations: "android.app.ondeviceintelligence-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Permissions aconfig_declarations { name: "android.permission.flags-aconfig", diff --git a/core/api/current.txt b/core/api/current.txt index c141490e6202..4a2abf69c503 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -2167,11 +2167,6 @@ package android { field public static final int notification_large_icon_width = 17104901; // 0x1050005 field public static final int system_app_widget_background_radius = 17104904; // 0x1050008 field public static final int system_app_widget_inner_radius = 17104905; // 0x1050009 - field public static final int system_corner_radius_large; - field public static final int system_corner_radius_medium; - field public static final int system_corner_radius_small; - field public static final int system_corner_radius_xlarge; - field public static final int system_corner_radius_xsmall; field public static final int thumbnail_height = 17104897; // 0x1050001 field public static final int thumbnail_width = 17104898; // 0x1050002 } @@ -10737,7 +10732,6 @@ package android.content { field public static final String DROPBOX_SERVICE = "dropbox"; field public static final String EUICC_SERVICE = "euicc"; field public static final String FILE_INTEGRITY_SERVICE = "file_integrity"; - field public static final String FINGERPRINT_SERVICE = "fingerprint"; field public static final String GAME_SERVICE = "game"; field public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection"; field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties"; @@ -20367,54 +20361,6 @@ package android.hardware.display { } -package android.hardware.fingerprint { - - @Deprecated public class FingerprintManager { - method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler); - method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints(); - method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected(); - field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 - field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 - field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 - field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1 - field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5 - field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4 - field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 - field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc - field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 - field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 - field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9 - field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb - field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 - field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 - field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 - field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa - field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8 - } - - @Deprecated public abstract static class FingerprintManager.AuthenticationCallback { - ctor @Deprecated public FingerprintManager.AuthenticationCallback(); - method @Deprecated public void onAuthenticationError(int, CharSequence); - method @Deprecated public void onAuthenticationFailed(); - method @Deprecated public void onAuthenticationHelp(int, CharSequence); - method @Deprecated public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult); - } - - @Deprecated public static class FingerprintManager.AuthenticationResult { - method @Deprecated public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject(); - } - - @Deprecated public static final class FingerprintManager.CryptoObject { - ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature); - ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher); - ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac); - method @Deprecated public javax.crypto.Cipher getCipher(); - method @Deprecated public javax.crypto.Mac getMac(); - method @Deprecated public java.security.Signature getSignature(); - } - -} - package android.hardware.input { public final class HostUsiVersion implements android.os.Parcelable { @@ -43248,6 +43194,7 @@ package android.telecom { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS) public java.util.List<android.telecom.PhoneAccountHandle> getOwnSelfManagedPhoneAccounts(); method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle); + method @FlaggedApi("com.android.server.telecom.flags.get_registered_phone_accounts") @NonNull public java.util.List<android.telecom.PhoneAccount> getRegisteredPhoneAccounts(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts(); method public android.telecom.PhoneAccountHandle getSimCallManager(); method @Nullable public android.telecom.PhoneAccountHandle getSimCallManagerForSubscription(int); diff --git a/core/api/removed.txt b/core/api/removed.txt index 3c7c0d6e6ea1..c61f16333fe8 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -35,6 +35,7 @@ package android.content { method @Deprecated @Nullable public String getFeatureId(); method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int); method public abstract java.io.File getSharedPreferencesPath(String); + field public static final String FINGERPRINT_SERVICE = "fingerprint"; } public class ContextWrapper extends android.content.Context { @@ -145,6 +146,54 @@ package android.hardware { } +package android.hardware.fingerprint { + + @Deprecated public class FingerprintManager { + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler); + method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints(); + method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected(); + field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 + field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 + field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 + field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1 + field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5 + field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4 + field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 + field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc + field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 + field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 + field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9 + field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb + field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 + field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 + field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 + field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa + field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8 + } + + @Deprecated public abstract static class FingerprintManager.AuthenticationCallback { + ctor public FingerprintManager.AuthenticationCallback(); + method public void onAuthenticationError(int, CharSequence); + method public void onAuthenticationFailed(); + method public void onAuthenticationHelp(int, CharSequence); + method public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult); + } + + @Deprecated public static class FingerprintManager.AuthenticationResult { + method public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject(); + } + + @Deprecated public static final class FingerprintManager.CryptoObject { + ctor public FingerprintManager.CryptoObject(@NonNull java.security.Signature); + ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher); + ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac); + method public javax.crypto.Cipher getCipher(); + method public javax.crypto.Mac getMac(); + method public java.security.Signature getSignature(); + } + +} + package android.media { public final class AudioFormat implements android.os.Parcelable { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a34e6677bd27..a5e62ace7278 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -69,6 +69,8 @@ package android { field public static final String BIND_MUSIC_RECOGNITION_SERVICE = "android.permission.BIND_MUSIC_RECOGNITION_SERVICE"; field public static final String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"; field public static final String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE"; + field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_INTELLIGENCE_SERVICE = "android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE"; + field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_TRUSTED_SERVICE = "android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE"; field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"; field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE"; field public static final String BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE = "android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE"; @@ -403,6 +405,7 @@ package android { field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY"; field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String USE_BACKGROUND_FACE_AUTHENTICATION = "android.permission.USE_BACKGROUND_FACE_AUTHENTICATION"; field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS"; + field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String USE_ON_DEVICE_INTELLIGENCE = "android.permission.USE_ON_DEVICE_INTELLIGENCE"; field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK"; field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED"; field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS"; @@ -2190,6 +2193,138 @@ package android.app.job { } +package android.app.ondeviceintelligence { + + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Content implements android.os.Parcelable { + ctor public Content(@NonNull android.os.Bundle); + method public int describeContents(); + method @NonNull public android.os.Bundle getData(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Content> CREATOR; + } + + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback { + method public void onDownloadCompleted(@NonNull android.os.PersistableBundle); + method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle); + method public default void onDownloadProgress(long); + method public default void onDownloadStarted(long); + field public static final int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3; // 0x3 + field public static final int DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE = 2; // 0x2 + field public static final int DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE = 1; // 0x1 + field public static final int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4; // 0x4 + field public static final int DOWNLOAD_FAILURE_STATUS_UNKNOWN = 0; // 0x0 + } + + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Feature implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.os.PersistableBundle getFeatureParams(); + method public int getId(); + method @Nullable public String getModelName(); + method @Nullable public String getName(); + method public int getType(); + method public int getVariant(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Feature> CREATOR; + } + + public static final class Feature.Builder { + ctor public Feature.Builder(int, int, int, @NonNull android.os.PersistableBundle); + method @NonNull public android.app.ondeviceintelligence.Feature build(); + method @NonNull public android.app.ondeviceintelligence.Feature.Builder setFeatureParams(@NonNull android.os.PersistableBundle); + method @NonNull public android.app.ondeviceintelligence.Feature.Builder setId(int); + method @NonNull public android.app.ondeviceintelligence.Feature.Builder setModelName(@NonNull String); + method @NonNull public android.app.ondeviceintelligence.Feature.Builder setName(@NonNull String); + method @NonNull public android.app.ondeviceintelligence.Feature.Builder setType(int); + method @NonNull public android.app.ondeviceintelligence.Feature.Builder setVariant(int); + } + + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable { + ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int, @NonNull android.os.PersistableBundle); + ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int); + method public int describeContents(); + method @NonNull public android.os.PersistableBundle getFeatureDetailParams(); + method @android.app.ondeviceintelligence.FeatureDetails.Status public int getStatus(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR; + field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3 + field public static final int FEATURE_STATUS_DOWNLOADABLE = 1; // 0x1 + field public static final int FEATURE_STATUS_DOWNLOADING = 2; // 0x2 + field public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4; // 0x4 + field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0 + } + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status { + } + + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FilePart implements android.os.Parcelable { + ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull String) throws java.io.FileNotFoundException; + ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull java.io.FileInputStream) throws java.io.IOException; + method public int describeContents(); + method @NonNull public java.io.FileInputStream getFileInputStream(); + method @NonNull public String getFilePartKey(); + method @NonNull public android.os.PersistableBundle getFilePartParams(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FilePart> CREATOR; + } + + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager { + method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); + method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); + method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer); + method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); + method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>); + method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>); + method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback); + method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenCount(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); + field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2 + field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0 + field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1 + } + + public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException extends java.lang.Exception { + ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull String, @NonNull android.os.PersistableBundle); + ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull android.os.PersistableBundle); + method public int getErrorCode(); + method @NonNull public android.os.PersistableBundle getErrorParams(); + field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000; // 0x3e8 + } + + public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException extends android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException { + ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull String, @NonNull android.os.PersistableBundle); + ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull android.os.PersistableBundle); + field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2 + field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3 + field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9 + field public static final int PROCESSING_ERROR_CANCELLED = 7; // 0x7 + field public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5; // 0x5 + field public static final int PROCESSING_ERROR_INTERNAL = 14; // 0xe + field public static final int PROCESSING_ERROR_IPC_ERROR = 6; // 0x6 + field public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8; // 0x8 + field public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4; // 0x4 + field public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12; // 0xc + field public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11; // 0xb + field public static final int PROCESSING_ERROR_SAFETY_ERROR = 10; // 0xa + field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf + field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd + field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1 + } + + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal { + ctor public ProcessingSignal(); + method public void sendSignal(@NonNull android.os.PersistableBundle); + method public void setOnProcessingSignalCallback(@NonNull java.util.concurrent.Executor, @Nullable android.app.ondeviceintelligence.ProcessingSignal.OnProcessingSignalCallback); + } + + public static interface ProcessingSignal.OnProcessingSignalCallback { + method public void onSignalReceived(@NonNull android.os.PersistableBundle); + } + + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingResponseReceiver<R, T, E extends java.lang.Throwable> extends android.os.OutcomeReceiver<R,E> { + method public void onNewContent(@NonNull T); + } + +} + package android.app.people { public final class PeopleManager { @@ -3637,6 +3772,7 @@ package android.content { field public static final String NETD_SERVICE = "netd"; field @Deprecated public static final String NETWORK_SCORE_SERVICE = "network_score"; field public static final String OEM_LOCK_SERVICE = "oem_lock"; + field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String ON_DEVICE_INTELLIGENCE_SERVICE = "on_device_intelligence"; field public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller"; field public static final String PERMISSION_SERVICE = "permission"; field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block"; @@ -13453,6 +13589,7 @@ package android.service.voice { @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public final class VisualQueryDetectedResult implements android.os.Parcelable { method public int describeContents(); + method @Nullable public byte[] getAccessibilityDetectionData(); method public static int getMaxSpeakerId(); method @NonNull public String getPartialQuery(); method public int getSpeakerId(); @@ -13463,6 +13600,7 @@ package android.service.voice { public static final class VisualQueryDetectedResult.Builder { ctor public VisualQueryDetectedResult.Builder(); method @NonNull public android.service.voice.VisualQueryDetectedResult build(); + method @NonNull public android.service.voice.VisualQueryDetectedResult.Builder setAccessibilityDetectionData(@NonNull byte...); method @NonNull public android.service.voice.VisualQueryDetectedResult.Builder setPartialQuery(@NonNull String); method @NonNull public android.service.voice.VisualQueryDetectedResult.Builder setSpeakerId(int); } @@ -13500,7 +13638,10 @@ package android.service.voice { } public class VisualQueryDetector { + method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public void clearAccessibilityDetectionEnabledListener(); method public void destroy(); + method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public boolean isAccessibilityDetectionEnabled(); + method @FlaggedApi("android.service.voice.flags.allow_complex_results_egress_from_vqds") public void setAccessibilityDetectionEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean startRecognition(); method @RequiresPermission(allOf={android.Manifest.permission.CAMERA, android.Manifest.permission.RECORD_AUDIO}) public boolean stopRecognition(); method public void updateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory); diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index ca9fab815167..ccfb17a12261 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -1877,6 +1877,8 @@ Todo: android.Manifest.permission#READ_PEOPLE_DATA: Documentation mentions 'TODO' Todo: android.app.NotificationManager#isNotificationAssistantAccessGranted(android.content.ComponentName): Documentation mentions 'TODO' +Todo: android.app.ondeviceintelligence.OnDeviceIntelligenceManager#requestFeatureDownload(android.app.ondeviceintelligence.Feature, android.app.ondeviceintelligence.CancellationSignal, java.util.concurrent.Executor, android.app.ondeviceintelligence.DownloadCallback): + Documentation mentions 'TODO' Todo: android.hardware.camera2.params.StreamConfigurationMap: Documentation mentions 'TODO' Todo: android.hardware.location.ContextHubManager#getNanoAppInstanceInfo(int): diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 334d9e63bb98..4ec476ed4ee4 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1548,6 +1548,7 @@ package android.hardware.biometrics { public static class BiometricPrompt.Builder { method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean); + method @FlaggedApi("android.multiuser.enable_biometrics_to_unlock_private_space") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean, boolean); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowedSensorIds(@NonNull java.util.List<java.lang.Integer>); } @@ -1706,15 +1707,6 @@ package android.hardware.display { } -package android.hardware.fingerprint { - - @Deprecated public class FingerprintManager { - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int); - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties(); - } - -} - package android.hardware.hdmi { public final class HdmiControlServiceWrapper { diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index c1181f5b8233..27808cb3af88 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -1931,6 +1931,8 @@ Todo: android.Manifest.permission#READ_PEOPLE_DATA: Documentation mentions 'TODO' Todo: android.app.NotificationManager#isNotificationAssistantAccessGranted(android.content.ComponentName): Documentation mentions 'TODO' +Todo: android.app.ondeviceintelligence.OnDeviceIntelligenceManager#requestFeatureDownload(android.app.ondeviceintelligence.Feature, android.app.ondeviceintelligence.CancellationSignal, java.util.concurrent.Executor, android.app.ondeviceintelligence.DownloadCallback): + Documentation mentions 'TODO' Todo: android.hardware.camera2.params.StreamConfigurationMap: Documentation mentions 'TODO' Todo: android.hardware.location.ContextHubManager#getNanoAppInstanceInfo(int): diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt index d802177e249b..2e44176f342e 100644 --- a/core/api/test-removed.txt +++ b/core/api/test-removed.txt @@ -1 +1,10 @@ // Signature format: 2.0 +package android.hardware.fingerprint { + + @Deprecated public class FingerprintManager { + method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int); + method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties(); + } + +} + diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index f7d75222a6f7..42c32723fd72 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -69,6 +69,7 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityWindowInfo; import android.view.inputmethod.EditorInfo; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.inputmethod.IAccessibilityInputMethodSession; import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; @@ -1388,7 +1389,9 @@ public abstract class AccessibilityService extends Service { getFingerprintGestureController().onGesture(gesture); } - int getConnectionId() { + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public int getConnectionId() { return mConnectionId; } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 237d31c67fe2..f3585229d823 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -5967,14 +5967,19 @@ public class ActivityManager { } /** - * Used by {@link com.android.systemui.theme.ThemeOverlayController} to notify of color - * palette readiness. + * Used by ThemeOverlayController to notify when color + * palette is ready. + * + * @param userId The ID of the user where ThemeOverlayController is ready. + * + * @throws RemoteException + * * @hide */ @RequiresPermission(Manifest.permission.SET_THEME_OVERLAY_CONTROLLER_READY) - public void setThemeOverlayReady(boolean readiness) { + public void setThemeOverlayReady(@UserIdInt int userId) { try { - getService().setThemeOverlayReady(readiness); + getService().setThemeOverlayReady(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 0ae2e01a45fa..062b89ed57ba 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -1264,5 +1264,5 @@ public abstract class ActivityManagerInternal { * palette readiness. * @hide */ - public abstract boolean getThemeOverlayReadiness(); + public abstract boolean isThemeOverlayReady(int userId); } diff --git a/core/java/android/app/ComponentCaller.java b/core/java/android/app/ComponentCaller.java index 14bc0038883a..7e6a9ac9ff8e 100644 --- a/core/java/android/app/ComponentCaller.java +++ b/core/java/android/app/ComponentCaller.java @@ -137,16 +137,18 @@ public final class ComponentCaller { * <li>This is not a real time check, i.e. the permissions have been computed at launch * time. * <li>This method will return the correct result for content URIs passed at launch time, - * specifically the ones from {@link Intent#getData()}, and {@link Intent#getClipData()} in - * the intent of {@code startActivity(intent)}. For others, it will throw an - * {@link IllegalArgumentException}. + * specifically the ones from {@link Intent#getData()}, {@link Intent#EXTRA_STREAM}, and + * {@link Intent#getClipData()} in the intent of {@code startActivity(intent)}. For others, + * it will throw an {@link IllegalArgumentException}. * </ul> * * @param uri The content uri that is being checked * @param modeFlags The access modes to check * @return {@link PackageManager#PERMISSION_GRANTED} if this activity caller is allowed to * access that uri, or {@link PackageManager#PERMISSION_DENIED} if it is not - * @throws IllegalArgumentException if uri is a non-content URI or it wasn't passed at launch + * @throws IllegalArgumentException if uri is a non-content URI or it wasn't passed at launch in + * {@link Intent#getData()}, {@link Intent#EXTRA_STREAM}, and + * {@link Intent#getClipData()} * @throws SecurityException if you don't have access to uri * * @see android.content.Context#checkContentUriPermissionFull(Uri, int, int, int) diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index cc0aafde1f5c..7a95720c1cf4 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -554,11 +554,14 @@ interface IActivityManager { void bootAnimationComplete(); /** - * Used by {@link com.android.systemui.theme.ThemeOverlayController} to notify of color - * palette readiness. + * Used by {@link com.android.systemui.theme.ThemeOverlayController} to notify when color + * palette is ready. + * + * @param userId The ID of the user where ThemeOverlayController is ready. + * * @throws RemoteException */ - void setThemeOverlayReady(boolean readiness); + void setThemeOverlayReady(int userId); @UnsupportedAppUsage void registerTaskStackListener(in ITaskStackListener listener); diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 24a51573b48a..625526047212 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -550,7 +550,7 @@ public class ResourcesManager { @UnsupportedAppUsage protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) { - final AssetManager.Builder builder = new AssetManager.Builder(); + final AssetManager.Builder builder = new AssetManager.Builder().setNoInit(); final ArrayList<ApkKey> apkKeys = extractApkKeys(key); for (int i = 0, n = apkKeys.size(); i < n; i++) { @@ -1555,7 +1555,7 @@ public class ResourcesManager { } else if(overlayPaths == null) { return ArrayUtils.cloneOrNull(resourceDirs); } else { - final ArrayList<String> paths = new ArrayList<>(); + final var paths = new ArrayList<String>(overlayPaths.length + resourceDirs.length); for (final String path : overlayPaths) { paths.add(path); } diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/core/java/android/app/ondeviceintelligence/Content.aidl new file mode 100644 index 000000000000..40f0ef9a8541 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/Content.aidl @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2024, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +/** + * @hide + */ +parcelable Content; diff --git a/core/java/android/app/ondeviceintelligence/Content.java b/core/java/android/app/ondeviceintelligence/Content.java new file mode 100644 index 000000000000..51bd156fc946 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/Content.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Represents content sent to and received from the on-device inference service. + * Can contain a collection of text, image, and binary parts or any combination of these. + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +public final class Content implements Parcelable { + //TODO: Improve javadoc after adding validation logic. + private static final String TAG = "Content"; + private final Bundle mData; + + /** + * Create a content object using a Bundle of only known types that are read-only. + */ + public Content(@NonNull Bundle data) { + Objects.requireNonNull(data); + validateBundleData(data); + this.mData = data; + } + + /** + * Returns the Content's data represented as a Bundle. + */ + @NonNull + public Bundle getData() { + return mData; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBundle(mData); + } + + @Override + public int describeContents() { + int mask = 0; + mask |= mData.describeContents(); + return mask; + } + + @NonNull + public static final Creator<Content> CREATOR = new Creator<>() { + @Override + @NonNull + public Content createFromParcel(@NonNull Parcel in) { + return new Content(in.readBundle(getClass().getClassLoader())); + } + + @Override + @NonNull + public Content[] newArray(int size) { + return new Content[size]; + } + }; + + private void validateBundleData(Bundle unused) { + // TODO: Validate there are only known types. + } +} diff --git a/core/java/android/app/ondeviceintelligence/DownloadCallback.java b/core/java/android/app/ondeviceintelligence/DownloadCallback.java new file mode 100644 index 000000000000..684c71f9144c --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/DownloadCallback.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.PersistableBundle; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Callback functions used for feature downloading via the + * {@link OnDeviceIntelligenceManager#requestFeatureDownload}. + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +public interface DownloadCallback { + int DOWNLOAD_FAILURE_STATUS_UNKNOWN = 0; + + /** + * Sent when feature download could not succeed due to there being no available disk space on + * the device. + */ + int DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE = 1; + + /** + * Sent when feature download could not succeed due to a network error. + */ + int DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE = 2; + + /** + * Sent when feature download has been initiated already, hence no need to request download + * again. Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check if + * download has been completed. + */ + int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3; + + /** + * Sent when feature download did not start due to errors (e.g. remote exception of features not + * available). Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check + * if feature-status is {@link FeatureDetails#FEATURE_STATUS_DOWNLOADABLE}. + */ + int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4; + + /** @hide */ + @IntDef(value = { + DOWNLOAD_FAILURE_STATUS_UNKNOWN, + DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE, + DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE, + DOWNLOAD_FAILURE_STATUS_DOWNLOADING, + DOWNLOAD_FAILURE_STATUS_UNAVAILABLE + }, open = true) + @Retention(RetentionPolicy.SOURCE) + @interface DownloadFailureStatus { + } + + /** + * Called when model download started properly. + * + * @param bytesToDownload the total bytes to be downloaded for this {@link Feature} + */ + default void onDownloadStarted(long bytesToDownload) { + } + + /** + * Called when model download failed. + * + * @param failureStatus the download failure status + * @param errorMessage the error message associated with the download failure + */ + void onDownloadFailed( + @DownloadFailureStatus int failureStatus, + @Nullable String errorMessage, + @NonNull PersistableBundle errorParams); + + /** + * Called when model download is in progress. + * + * @param totalBytesDownloaded the already downloaded bytes for this {@link Feature} + */ + default void onDownloadProgress(long totalBytesDownloaded) { + } + + /** + * Called when model download via MDD completed. The remote implementation can populate any + * associated download params like file stats etc. in this callback to inform the client. + * + * @param downloadParams params containing info about the completed download. + */ + void onDownloadCompleted(@NonNull PersistableBundle downloadParams); +} diff --git a/core/java/android/app/ondeviceintelligence/Feature.aidl b/core/java/android/app/ondeviceintelligence/Feature.aidl new file mode 100644 index 000000000000..18494d754674 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/Feature.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +/** + * @hide + */ +parcelable Feature; diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java new file mode 100644 index 000000000000..510735461553 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/Feature.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; + +/** + * Represents a typical feature associated with on-device intelligence. + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +public final class Feature implements Parcelable { + // TODO(b/325315604) - Check if we can expose non-hidden IntDefs in Framework. + private final int mId; + @Nullable + private final String mName; + @Nullable + private final String mModelName; + private final int mType; + private final int mVariant; + @NonNull + private final PersistableBundle mFeatureParams; + + /* package-private */ Feature( + int id, + @Nullable String name, + @Nullable String modelName, + int type, + int variant, + @NonNull PersistableBundle featureParams) { + this.mId = id; + this.mName = name; + this.mModelName = modelName; + this.mType = type; + this.mVariant = variant; + this.mFeatureParams = featureParams; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mFeatureParams); + } + + /** Returns the unique and immutable identifier of this feature. */ + public int getId() { + return mId; + } + + /** Returns human-readable name of this feature. */ + public @Nullable String getName() { + return mName; + } + + /** Returns base model name of this feature. */ + public @Nullable String getModelName() { + return mModelName; + } + + /** Returns type identifier of this feature. */ + public int getType() { + return mType; + } + + /** Returns variant kind for this feature. */ + public int getVariant() { + return mVariant; + } + + public @NonNull PersistableBundle getFeatureParams() { + return mFeatureParams; + } + + @Override + public String toString() { + return "Feature { " + + "id = " + mId + ", " + + "name = " + mName + ", " + + "modelName = " + mModelName + ", " + + "type = " + mType + ", " + + "variant = " + mVariant + ", " + + "featureParams = " + mFeatureParams + + " }"; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + Feature that = (Feature) o; + //noinspection PointlessBooleanExpression + return true + && mId == that.mId + && java.util.Objects.equals(mName, that.mName) + && java.util.Objects.equals(mModelName, that.mModelName) + && mType == that.mType + && mVariant == that.mVariant + && java.util.Objects.equals(mFeatureParams, that.mFeatureParams); + } + + @Override + public int hashCode() { + int _hash = 1; + _hash = 31 * _hash + mId; + _hash = 31 * _hash + java.util.Objects.hashCode(mName); + _hash = 31 * _hash + java.util.Objects.hashCode(mModelName); + _hash = 31 * _hash + mType; + _hash = 31 * _hash + mVariant; + _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureParams); + return _hash; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + byte flg = 0; + if (mName != null) flg |= 0x2; + if (mModelName != null) flg |= 0x4; + dest.writeByte(flg); + dest.writeInt(mId); + if (mName != null) dest.writeString8(mName); + if (mModelName != null) dest.writeString8(mModelName); + dest.writeInt(mType); + dest.writeInt(mVariant); + dest.writeTypedObject(mFeatureParams, flags); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + /* package-private */ Feature(@NonNull Parcel in) { + byte flg = in.readByte(); + int id = in.readInt(); + String name = (flg & 0x2) == 0 ? null : in.readString(); + String modelName = (flg & 0x4) == 0 ? null : in.readString(); + int type = in.readInt(); + int variant = in.readInt(); + PersistableBundle featureParams = (PersistableBundle) in.readTypedObject( + PersistableBundle.CREATOR); + + this.mId = id; + this.mName = name; + this.mModelName = modelName; + this.mType = type; + this.mVariant = variant; + this.mFeatureParams = featureParams; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mFeatureParams); + } + + public static final @NonNull Parcelable.Creator<Feature> CREATOR + = new Parcelable.Creator<Feature>() { + @Override + public Feature[] newArray(int size) { + return new Feature[size]; + } + + @Override + public Feature createFromParcel(@NonNull Parcel in) { + return new Feature(in); + } + }; + + /** + * A builder for {@link Feature} + */ + @SuppressWarnings("WeakerAccess") + public static final class Builder { + private int mId; + private @Nullable String mName; + private @Nullable String mModelName; + private int mType; + private int mVariant; + private @NonNull PersistableBundle mFeatureParams; + + private long mBuilderFieldsSet = 0L; + + public Builder( + int id, + int type, + int variant, + @NonNull PersistableBundle featureParams) { + mId = id; + mType = type; + mVariant = variant; + mFeatureParams = featureParams; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mFeatureParams); + } + + public @NonNull Builder setId(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mId = value; + return this; + } + + public @NonNull Builder setName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mName = value; + return this; + } + + public @NonNull Builder setModelName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mModelName = value; + return this; + } + + public @NonNull Builder setType(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mType = value; + return this; + } + + public @NonNull Builder setVariant(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mVariant = value; + return this; + } + + public @NonNull Builder setFeatureParams(@NonNull PersistableBundle value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mFeatureParams = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull Feature build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x40; // Mark builder used + + Feature o = new Feature( + mId, + mName, + mModelName, + mType, + mVariant, + mFeatureParams); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x40) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } +} diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl new file mode 100644 index 000000000000..0589bf8bacb9 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +/** + * @hide + */ +parcelable FeatureDetails; diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java new file mode 100644 index 000000000000..92f351362f70 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcelable; +import android.os.PersistableBundle; + +import androidx.annotation.IntDef; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.text.MessageFormat; + +/** + * Represents a status of a requested {@link Feature}. + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +public final class FeatureDetails implements Parcelable { + @Status + private final int mStatus; + @NonNull + private final PersistableBundle mFeatureDetailParams; + + /** Invalid or unavailable {@code AiFeature}. */ + public static final int FEATURE_STATUS_UNAVAILABLE = 0; + + /** Feature can be downloaded on request. */ + public static final int FEATURE_STATUS_DOWNLOADABLE = 1; + + /** Feature is being downloaded. */ + public static final int FEATURE_STATUS_DOWNLOADING = 2; + + /** Feature is fully downloaded and ready to use. */ + public static final int FEATURE_STATUS_AVAILABLE = 3; + + /** Underlying service is unavailable and feature status cannot be fetched. */ + public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4; + + @IntDef(value = { + FEATURE_STATUS_UNAVAILABLE, + FEATURE_STATUS_DOWNLOADABLE, + FEATURE_STATUS_DOWNLOADING, + FEATURE_STATUS_AVAILABLE, + FEATURE_STATUS_SERVICE_UNAVAILABLE + }, open = true) + @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) + @Retention(RetentionPolicy.SOURCE) + public @interface Status { + } + + public FeatureDetails( + @Status int status, + @NonNull PersistableBundle featureDetailParams) { + this.mStatus = status; + com.android.internal.util.AnnotationValidations.validate( + Status.class, null, mStatus); + this.mFeatureDetailParams = featureDetailParams; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mFeatureDetailParams); + } + + public FeatureDetails( + @Status int status) { + this.mStatus = status; + com.android.internal.util.AnnotationValidations.validate( + Status.class, null, mStatus); + this.mFeatureDetailParams = new PersistableBundle(); + } + + + /** + * Returns an integer value associated with the feature status. + */ + public @Status int getStatus() { + return mStatus; + } + + + /** + * Returns a persistable bundle contain any additional status related params. + */ + public @NonNull PersistableBundle getFeatureDetailParams() { + return mFeatureDetailParams; + } + + @Override + public String toString() { + return MessageFormat.format("FeatureDetails '{' status = {0}, " + + "persistableBundle = {1} '}'", + mStatus, + mFeatureDetailParams); + } + + @Override + public boolean equals(@android.annotation.Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + FeatureDetails that = (FeatureDetails) o; + return mStatus == that.mStatus + && java.util.Objects.equals(mFeatureDetailParams, that.mFeatureDetailParams); + } + + @Override + public int hashCode() { + int _hash = 1; + _hash = 31 * _hash + mStatus; + _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureDetailParams); + return _hash; + } + + @Override + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + dest.writeInt(mStatus); + dest.writeTypedObject(mFeatureDetailParams, flags); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + FeatureDetails(@NonNull android.os.Parcel in) { + int status = in.readInt(); + PersistableBundle persistableBundle = (PersistableBundle) in.readTypedObject( + PersistableBundle.CREATOR); + + this.mStatus = status; + com.android.internal.util.AnnotationValidations.validate( + Status.class, null, mStatus); + this.mFeatureDetailParams = persistableBundle; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mFeatureDetailParams); + } + + + public static final @NonNull Parcelable.Creator<FeatureDetails> CREATOR = + new Parcelable.Creator<>() { + @Override + public FeatureDetails[] newArray(int size) { + return new FeatureDetails[size]; + } + + @Override + public FeatureDetails createFromParcel(@NonNull android.os.Parcel in) { + return new FeatureDetails(in); + } + }; + +} diff --git a/core/java/android/app/ondeviceintelligence/FilePart.java b/core/java/android/app/ondeviceintelligence/FilePart.java new file mode 100644 index 000000000000..e9fb5f2cb440 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/FilePart.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; + +import android.annotation.FlaggedApi; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import android.os.PersistableBundle; + +import android.annotation.NonNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Objects; + +/** + * Represents file data with an associated file descriptor sent to and received from remote + * processing. The interface ensures that the underlying file-descriptor is always opened in + * read-only mode. + * + * @hide + */ +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +@SystemApi +public final class FilePart implements Parcelable { + private final String mPartKey; + private final PersistableBundle mPartParams; + private final ParcelFileDescriptor mParcelFileDescriptor; + + private FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams, + @NonNull ParcelFileDescriptor parcelFileDescriptor) { + Objects.requireNonNull(partKey); + Objects.requireNonNull(partParams); + this.mPartKey = partKey; + this.mPartParams = partParams; + this.mParcelFileDescriptor = Objects.requireNonNull(parcelFileDescriptor); + } + + /** + * Create a file part using a filePath and any additional params. + */ + public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams, + @NonNull String filePath) + throws FileNotFoundException { + this(partKey, partParams, Objects.requireNonNull(ParcelFileDescriptor.open( + new File(Objects.requireNonNull(filePath)), ParcelFileDescriptor.MODE_READ_ONLY))); + } + + /** + * Create a file part using a file input stream and any additional params. + * It is the caller's responsibility to close the stream. It is safe to do so as soon as this + * call returns. + */ + public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams, + @NonNull FileInputStream fileInputStream) + throws IOException { + this(partKey, partParams, ParcelFileDescriptor.dup(fileInputStream.getFD())); + } + + /** + * Returns a FileInputStream for the associated File. + * Caller must close the associated stream when done reading from it. + * + * @return the FileInputStream associated with the FilePart. + */ + @NonNull + public FileInputStream getFileInputStream() { + return new FileInputStream(mParcelFileDescriptor.getFileDescriptor()); + } + + /** + * Returns the unique key associated with the part. Each Part key added to a content object + * should be ensured to be unique. + */ + @NonNull + public String getFilePartKey() { + return mPartKey; + } + + /** + * Returns the params associated with Part. + */ + @NonNull + public PersistableBundle getFilePartParams() { + return mPartParams; + } + + + @Override + public int describeContents() { + return CONTENTS_FILE_DESCRIPTOR; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(getFilePartKey()); + dest.writePersistableBundle(getFilePartParams()); + mParcelFileDescriptor.writeToParcel(dest, flags + | Parcelable.PARCELABLE_WRITE_RETURN_VALUE); // This flag ensures that the sender's + // copy of the Pfd is closed as soon as the Binder call succeeds. + } + + @NonNull + public static final Creator<FilePart> CREATOR = new Creator<>() { + @Override + public FilePart createFromParcel(Parcel in) { + return new FilePart(in.readString(), in.readTypedObject(PersistableBundle.CREATOR), + in.readParcelable( + getClass().getClassLoader(), ParcelFileDescriptor.class)); + } + + @Override + public FilePart[] newArray(int size) { + return new FilePart[size]; + } + }; +} diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl new file mode 100644 index 000000000000..aba563f84e1b --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +import android.app.ondeviceintelligence.IProcessingSignal; +import android.os.PersistableBundle; + +/** + * Interface for Download callback to passed onto service implementation, + * + * @hide + */ +oneway interface IDownloadCallback { + void onDownloadStarted(long bytesToDownload) = 1; + void onDownloadProgress(long bytesDownloaded) = 2; + void onDownloadFailed(int failureStatus, String errorMessage, in PersistableBundle errorParams) = 3; + void onDownloadCompleted(in PersistableBundle downloadParams) = 4; +}
\ No newline at end of file diff --git a/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl b/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl new file mode 100644 index 000000000000..93a84ec96757 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl @@ -0,0 +1,14 @@ +package android.app.ondeviceintelligence; + +import android.app.ondeviceintelligence.Feature; +import android.os.PersistableBundle; + +/** + * Interface for receiving a feature for the given identifier. + * + * @hide + */ +interface IFeatureCallback { + void onSuccess(in Feature result) = 1; + void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2; +} diff --git a/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl b/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl new file mode 100644 index 000000000000..d95029059f4a --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl @@ -0,0 +1,14 @@ +package android.app.ondeviceintelligence; + +import android.app.ondeviceintelligence.FeatureDetails; +import android.os.PersistableBundle; + +/** + * Interface for receiving details about a given feature. . + * + * @hide + */ +interface IFeatureDetailsCallback { + void onSuccess(in FeatureDetails result) = 1; + void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2; +} diff --git a/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl b/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl new file mode 100644 index 000000000000..374cb71977d5 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl @@ -0,0 +1,15 @@ +package android.app.ondeviceintelligence; + +import java.util.List; +import android.app.ondeviceintelligence.Feature; +import android.os.PersistableBundle; + +/** + * Interface for receiving list of supported features. + * + * @hide + */ +interface IListFeaturesCallback { + void onSuccess(in List<Feature> result) = 1; + void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2; +} diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl new file mode 100644 index 000000000000..b925f4863de2 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl @@ -0,0 +1,70 @@ +/* + * 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.app.ondeviceintelligence; + + import com.android.internal.infra.AndroidFuture; + import android.os.ICancellationSignal; + import android.os.ParcelFileDescriptor; + import android.os.PersistableBundle; + import android.os.RemoteCallback; + import android.app.ondeviceintelligence.Content; + import android.app.ondeviceintelligence.Feature; + import android.app.ondeviceintelligence.FeatureDetails; + import android.app.ondeviceintelligence.IDownloadCallback; + import android.app.ondeviceintelligence.IListFeaturesCallback; + import android.app.ondeviceintelligence.IFeatureCallback; + import android.app.ondeviceintelligence.IFeatureDetailsCallback; + import android.app.ondeviceintelligence.IResponseCallback; + import android.app.ondeviceintelligence.IStreamingResponseCallback; + import android.app.ondeviceintelligence.IProcessingSignal; + import android.app.ondeviceintelligence.ITokenCountCallback; + + + /** + * Interface for a OnDeviceIntelligenceManager for managing OnDeviceIntelligenceService and OnDeviceSandboxedInferenceService. + * + * @hide + */ + oneway interface IOnDeviceIntelligenceManager { + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)") + void getVersion(in RemoteCallback remoteCallback) = 1; + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)") + void getFeature(in int featureId, in IFeatureCallback remoteCallback) = 2; + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)") + void listFeatures(in IListFeaturesCallback listFeaturesCallback) = 3; + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)") + void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback) = 4; + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)") + void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5; + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)") + void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal signal, + in ITokenCountCallback tokenCountcallback) = 6; + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)") + void processRequest(in Feature feature, in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal, + in IResponseCallback responseCallback) = 7; + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)") + void processRequestStreaming(in Feature feature, + in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal, + in IStreamingResponseCallback streamingCallback) = 8; + } diff --git a/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl b/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl new file mode 100644 index 000000000000..03946eebd40b --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl @@ -0,0 +1,14 @@ +package android.app.ondeviceintelligence; + +import android.os.PersistableBundle; + +/** +* Signal to provide to the remote implementation in context of a given request or +* feature specific event. +* +* @hide +*/ + +oneway interface IProcessingSignal { + void sendSignal(in PersistableBundle actionParams) = 2; +} diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl new file mode 100644 index 000000000000..9848e1ddda5a --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl @@ -0,0 +1,15 @@ +package android.app.ondeviceintelligence; + +import android.app.ondeviceintelligence.Content; +import android.app.ondeviceintelligence.IProcessingSignal; +import android.os.PersistableBundle; + +/** + * Interface for a IResponseCallback for receiving response from on-device intelligence service. + * + * @hide + */ +interface IResponseCallback { + void onSuccess(in Content result) = 1; + void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2; +} diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl new file mode 100644 index 000000000000..a6805749fa04 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl @@ -0,0 +1,18 @@ +package android.app.ondeviceintelligence; + +import android.app.ondeviceintelligence.Content; +import android.app.ondeviceintelligence.IResponseCallback; +import android.app.ondeviceintelligence.IProcessingSignal; +import android.os.PersistableBundle; + + +/** + * This callback is a streaming variant of {@link IResponseCallback}. + * + * @hide + */ +interface IStreamingResponseCallback { + void onNewContent(in Content result) = 1; + void onSuccess(in Content result) = 2; + void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3; +} diff --git a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl new file mode 100644 index 000000000000..b724e03fbca4 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl @@ -0,0 +1,13 @@ +package android.app.ondeviceintelligence; + +import android.os.PersistableBundle; + +/** + * Interface for receiving the token count of a request for a given features. + * + * @hide + */ +interface ITokenCountCallback { + void onSuccess(long tokenCount) = 1; + void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2; +} diff --git a/core/java/android/app/ondeviceintelligence/OWNERS b/core/java/android/app/ondeviceintelligence/OWNERS index 6932ba23a8ac..85e9e653e6fb 100644 --- a/core/java/android/app/ondeviceintelligence/OWNERS +++ b/core/java/android/app/ondeviceintelligence/OWNERS @@ -4,4 +4,3 @@ sandeepbandaru@google.com shivanker@google.com hackz@google.com volnov@google.com - diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java new file mode 100644 index 000000000000..d34ab60068c8 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java @@ -0,0 +1,620 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; + +import android.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.os.Binder; +import android.os.CancellationSignal; +import android.os.ICancellationSignal; +import android.os.OutcomeReceiver; +import android.os.PersistableBundle; +import android.os.RemoteCallback; +import android.os.RemoteException; + +import androidx.annotation.IntDef; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.LongConsumer; + +/** + * Allows granted apps to manage on-device intelligence service configured on the device. Typical + * calling pattern will be to query and setup a required feature before proceeding to request + * processing. + * + * The contracts in this Manager class are designed to be open-ended in general, to allow + * interoperability. Therefore, it is recommended that implementations of this system-service + * expose this API to the clients via a separate sdk or library which has more defined contract. + * + * @hide + */ +@SystemApi +@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE) +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +public class OnDeviceIntelligenceManager { + private static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey"; + private final Context mContext; + private final IOnDeviceIntelligenceManager mService; + + /** + * @hide + */ + public OnDeviceIntelligenceManager(Context context, IOnDeviceIntelligenceManager service) { + mContext = context; + mService = service; + } + + /** + * Asynchronously get the version of the underlying remote implementation. + * + * @param versionConsumer consumer to populate the version of remote implementation. + * @param callbackExecutor executor to run the callback on. + */ + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void getVersion( + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull LongConsumer versionConsumer) { + // TODO explore modifying this method into getServicePackageDetails and return both + // version and package name of the remote service implementing this. + try { + RemoteCallback callback = new RemoteCallback(result -> { + long version = result.getLong(API_VERSION_BUNDLE_KEY); + Binder.withCleanCallingIdentity( + () -> callbackExecutor.execute(() -> versionConsumer.accept(version))); + }); + mService.getVersion(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Asynchronously get feature for a given id. + * + * @param featureId the identifier pointing to the feature. + * @param featureReceiver callback to populate the feature object for given identifier. + * @param callbackExecutor executor to run the callback on. + */ + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void getFeature( + int featureId, + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceManagerException> featureReceiver) { + try { + IFeatureCallback callback = + new IFeatureCallback.Stub() { + @Override + public void onSuccess(Feature result) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> featureReceiver.onResult(result))); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> featureReceiver.onError( + new OnDeviceIntelligenceManagerException( + errorCode, errorMessage, errorParams)))); + } + }; + mService.getFeature(featureId, callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Asynchronously get a list of features that are supported for the caller. + * + * @param featureListReceiver callback to populate the list of features. + * @param callbackExecutor executor to run the callback on. + */ + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void listFeatures( + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceManagerException> featureListReceiver) { + try { + IListFeaturesCallback callback = + new IListFeaturesCallback.Stub() { + @Override + public void onSuccess(List<Feature> result) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> featureListReceiver.onResult(result))); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> featureListReceiver.onError( + new OnDeviceIntelligenceManagerException( + errorCode, errorMessage, errorParams)))); + } + }; + mService.listFeatures(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * This method should be used to fetch details about a feature which need some additional + * computation, that can be inefficient to return in all calls to {@link #getFeature}. Callers + * and implementation can utilize the {@link Feature#getFeatureParams()} to pass hint on what + * details are expected by the caller. + * + * @param feature the feature to check status for. + * @param featureDetailsReceiver callback to populate the feature details to. + * @param callbackExecutor executor to run the callback on. + */ + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void getFeatureDetails(@NonNull Feature feature, + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceManagerException> featureDetailsReceiver) { + try { + IFeatureDetailsCallback callback = new IFeatureDetailsCallback.Stub() { + + @Override + public void onSuccess(FeatureDetails result) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> featureDetailsReceiver.onResult(result))); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> featureDetailsReceiver.onError( + new OnDeviceIntelligenceManagerException(errorCode, + errorMessage, errorParams)))); + } + }; + mService.getFeatureDetails(feature, callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * This method handles downloading all model and config files required to process requests + * sent against a given feature. The caller can listen to updates on the download status via + * the callback. + * + * Note: If a feature was already requested for downloaded previously, the onDownloadFailed + * callback would be invoked with {@link DownloadCallback#DOWNLOAD_FAILURE_STATUS_DOWNLOADING}. + * In such cases, clients should query the feature status via {@link #getFeatureStatus} to + * check + * on the feature's download status. + * + * @param feature feature to request download for. + * @param callback callback to populate updates about download status. + * @param cancellationSignal signal to invoke cancellation on the operation in the remote + * implementation. + * @param callbackExecutor executor to run the callback on. + */ + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void requestFeatureDownload(@NonNull Feature feature, + @Nullable CancellationSignal cancellationSignal, + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull DownloadCallback callback) { + try { + IDownloadCallback downloadCallback = new IDownloadCallback.Stub() { + + @Override + public void onDownloadStarted(long bytesToDownload) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> callback.onDownloadStarted(bytesToDownload))); + } + + @Override + public void onDownloadProgress(long bytesDownloaded) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> callback.onDownloadProgress(bytesDownloaded))); + } + + @Override + public void onDownloadFailed(int failureStatus, String errorMessage, + PersistableBundle errorParams) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> callback.onDownloadFailed(failureStatus, errorMessage, + errorParams))); + } + + @Override + public void onDownloadCompleted(PersistableBundle downloadParams) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> onDownloadCompleted(downloadParams))); + } + }; + + ICancellationSignal transport = null; + if (cancellationSignal != null) { + transport = CancellationSignal.createTransport(); + cancellationSignal.setRemote(transport); + } + + mService.requestFeatureDownload(feature, transport, downloadCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * The methods computes the token-count for a given request payload using the provided Feature + * details. + * + * @param feature feature associated with the request. + * @param request request that contains the content data and associated params. + * @param outcomeReceiver callback to populate the token count or exception in case of + * failure. + * @param cancellationSignal signal to invoke cancellation on the operation in the remote + * implementation. + * @param callbackExecutor executor to run the callback on. + */ + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void requestTokenCount(@NonNull Feature feature, @NonNull Content request, + @Nullable CancellationSignal cancellationSignal, + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull OutcomeReceiver<Long, + OnDeviceIntelligenceManagerException> outcomeReceiver) { + try { + ITokenCountCallback callback = new ITokenCountCallback.Stub() { + @Override + public void onSuccess(long tokenCount) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> outcomeReceiver.onResult(tokenCount))); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> outcomeReceiver.onError( + new OnDeviceIntelligenceManagerProcessingException( + errorCode, errorMessage, errorParams)))); + } + }; + + ICancellationSignal transport = null; + if (cancellationSignal != null) { + transport = CancellationSignal.createTransport(); + cancellationSignal.setRemote(transport); + } + + mService.requestTokenCount(feature, request, transport, callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + + /** + * Asynchronously Process a request based on the associated params, to populate a + * response in + * {@link OutcomeReceiver#onResult} callback or failure callback status code if there + * was a + * failure. + * + * @param feature feature associated with the request. + * @param request request that contains the Content data and + * associated params. + * @param requestType type of request being sent for processing the content. + * @param responseOutcomeReceiver callback to populate the response content and + * associated + * params. + * @param processingSignal signal to invoke custom actions in the + * remote implementation. + * @param cancellationSignal signal to invoke cancellation or + * @param callbackExecutor executor to run the callback on. + */ + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + + public void processRequest(@NonNull Feature feature, @NonNull Content request, + @RequestType int requestType, + @Nullable CancellationSignal cancellationSignal, + @Nullable ProcessingSignal processingSignal, + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull OutcomeReceiver<Content, + OnDeviceIntelligenceManagerProcessingException> responseOutcomeReceiver) { + try { + IResponseCallback callback = new IResponseCallback.Stub() { + @Override + public void onSuccess(Content result) { + Binder.withCleanCallingIdentity(() -> { + callbackExecutor.execute(() -> responseOutcomeReceiver.onResult(result)); + }); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) { + Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + () -> responseOutcomeReceiver.onError( + new OnDeviceIntelligenceManagerProcessingException( + errorCode, errorMessage, errorParams)))); + } + }; + + IProcessingSignal transport = null; + if (processingSignal != null) { + transport = ProcessingSignal.createTransport(); + processingSignal.setRemote(transport); + } + + ICancellationSignal cancellationTransport = null; + if (cancellationSignal != null) { + cancellationTransport = CancellationSignal.createTransport(); + cancellationSignal.setRemote(cancellationTransport); + } + + mService.processRequest(feature, request, requestType, cancellationTransport, transport, + callback); + + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Variation of {@link #processRequest} that asynchronously processes a request in a streaming + * fashion, where new content is pushed to caller in chunks via the + * {@link StreamingResponseReceiver#onNewContent}. After the streaming is complete, + * the service should call {@link StreamingResponseReceiver#onResult} and can optionally + * populate the complete {@link Response}'s Content as part of the callback when the final + * {@link Response} contains an enhanced aggregation of the Contents already streamed. + * + * @param feature feature associated with the request. + * @param request request that contains the Content data and associated + * params. + * @param requestType type of request being sent for processing the content. + * @param processingSignal signal to invoke other custom actions in the + * remote implementation. + * @param cancellationSignal signal to invoke cancellation + * @param streamingResponseReceiver streaming callback to populate the response content and + * associated params. + * @param callbackExecutor executor to run the callback on. + */ + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void processRequestStreaming(@NonNull Feature feature, @NonNull Content request, + @RequestType int requestType, + @Nullable CancellationSignal cancellationSignal, + @Nullable ProcessingSignal processingSignal, + @NonNull @CallbackExecutor Executor callbackExecutor, + @NonNull StreamingResponseReceiver<Content, Content, + OnDeviceIntelligenceManagerProcessingException> streamingResponseReceiver) { + try { + IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() { + @Override + public void onNewContent(Content result) { + Binder.withCleanCallingIdentity(() -> { + callbackExecutor.execute( + () -> streamingResponseReceiver.onNewContent(result)); + }); + } + + @Override + public void onSuccess(Content result) { + Binder.withCleanCallingIdentity(() -> { + callbackExecutor.execute(() -> streamingResponseReceiver.onResult(result)); + }); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) { + Binder.withCleanCallingIdentity(() -> { + callbackExecutor.execute( + () -> streamingResponseReceiver.onError( + new OnDeviceIntelligenceManagerProcessingException( + errorCode, errorMessage, errorParams))); + }); + } + }; + + IProcessingSignal transport = null; + if (processingSignal != null) { + transport = ProcessingSignal.createTransport(); + processingSignal.setRemote(transport); + } + + ICancellationSignal cancellationTransport = null; + if (cancellationSignal != null) { + cancellationTransport = CancellationSignal.createTransport(); + cancellationSignal.setRemote(cancellationTransport); + } + + mService.processRequestStreaming( + feature, request, requestType, cancellationTransport, transport, callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + + /** Request inference with provided Content and Params. */ + public static final int REQUEST_TYPE_INFERENCE = 0; + + /** + * Prepares the remote implementation environment for e.g.loading inference runtime etc.which + * are time consuming beforehand to remove overhead and allow quick processing of requests + * thereof. + */ + public static final int REQUEST_TYPE_PREPARE = 1; + + /** Request Embeddings of the passed-in Content. */ + public static final int REQUEST_TYPE_EMBEDDINGS = 2; + + /** + * @hide + */ + @IntDef(value = { + REQUEST_TYPE_INFERENCE, + REQUEST_TYPE_PREPARE, + REQUEST_TYPE_EMBEDDINGS + }, open = true) + @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) + @Retention(RetentionPolicy.SOURCE) + public @interface RequestType { + } + + + /** + * Exception type to be populated in callbacks to the methods under + * {@link OnDeviceIntelligenceManager}. + */ + public static class OnDeviceIntelligenceManagerException extends Exception { + /** + * Error code returned when the OnDeviceIntelligenceManager service is unavailable. + */ + public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000; + + private final int mErrorCode; + private final PersistableBundle errorParams; + + public OnDeviceIntelligenceManagerException(int errorCode, @NonNull String errorMessage, + @NonNull PersistableBundle errorParams) { + super(errorMessage); + this.mErrorCode = errorCode; + this.errorParams = errorParams; + } + + public OnDeviceIntelligenceManagerException(int errorCode, + @NonNull PersistableBundle errorParams) { + this.mErrorCode = errorCode; + this.errorParams = errorParams; + } + + public int getErrorCode() { + return mErrorCode; + } + + @NonNull + public PersistableBundle getErrorParams() { + return errorParams; + } + } + + /** + * Exception type to be populated in callbacks to the methods under + * {@link OnDeviceIntelligenceManager#processRequest} or + * {@link OnDeviceIntelligenceManager#processRequestStreaming} . + */ + public static class OnDeviceIntelligenceManagerProcessingException extends + OnDeviceIntelligenceManagerException { + + public static final int PROCESSING_ERROR_UNKNOWN = 1; + + /** Request passed contains bad data for e.g. format. */ + public static final int PROCESSING_ERROR_BAD_DATA = 2; + + /** Bad request for inputs. */ + public static final int PROCESSING_ERROR_BAD_REQUEST = 3; + + /** Whole request was classified as not safe, and no response will be generated. */ + public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4; + + /** Underlying processing encountered an error and failed to compute results. */ + public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5; + + /** Encountered an error while performing IPC */ + public static final int PROCESSING_ERROR_IPC_ERROR = 6; + + /** Request was cancelled either by user signal or by the underlying implementation. */ + public static final int PROCESSING_ERROR_CANCELLED = 7; + + /** Underlying processing in the remote implementation is not available. */ + public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8; + + /** The service is currently busy. Callers should retry with exponential backoff. */ + public static final int PROCESSING_ERROR_BUSY = 9; + + /** Something went wrong with safety classification service. */ + public static final int PROCESSING_ERROR_SAFETY_ERROR = 10; + + /** Response generated was classified unsafe. */ + public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11; + + /** Request is too large to be processed. */ + public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12; + + /** Inference suspended so that higher-priority inference can run. */ + public static final int PROCESSING_ERROR_SUSPENDED = 13; + + /** Underlying processing encountered an internal error, like a violated precondition. */ + public static final int PROCESSING_ERROR_INTERNAL = 14; + + /** + * The processing was not able to be passed on to the remote implementation, as the service + * was unavailable. + */ + public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; + + /** + * Error code of failed processing request. + * + * @hide + */ + @IntDef( + value = { + PROCESSING_ERROR_UNKNOWN, + PROCESSING_ERROR_BAD_DATA, + PROCESSING_ERROR_BAD_REQUEST, + PROCESSING_ERROR_REQUEST_NOT_SAFE, + PROCESSING_ERROR_COMPUTE_ERROR, + PROCESSING_ERROR_IPC_ERROR, + PROCESSING_ERROR_CANCELLED, + PROCESSING_ERROR_NOT_AVAILABLE, + PROCESSING_ERROR_BUSY, + PROCESSING_ERROR_SAFETY_ERROR, + PROCESSING_ERROR_RESPONSE_NOT_SAFE, + PROCESSING_ERROR_REQUEST_TOO_LARGE, + PROCESSING_ERROR_SUSPENDED, + PROCESSING_ERROR_INTERNAL, + PROCESSING_ERROR_SERVICE_UNAVAILABLE + }, open = true) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + @interface ProcessingError { + } + + public OnDeviceIntelligenceManagerProcessingException( + @ProcessingError int errorCode, @NonNull String errorMessage, + @NonNull PersistableBundle errorParams) { + super(errorCode, errorMessage, errorParams); + } + + public OnDeviceIntelligenceManagerProcessingException( + @ProcessingError int errorCode, + @NonNull PersistableBundle errorParams) { + super(errorCode, errorParams); + } + } +} diff --git a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java new file mode 100644 index 000000000000..3e543d27143a --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; + +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.PersistableBundle; +import android.os.RemoteException; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayDeque; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * A signal to perform orchestration actions on the inference and optionally receive a output about + * the result of the signal. This is an extension of {@link android.os.CancellationSignal}. + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +public final class ProcessingSignal { + private final Object mLock = new Object(); + + private static final int MAX_QUEUE_SIZE = 10; + + @GuardedBy("mLock") + private final ArrayDeque<PersistableBundle> mActionParamsQueue; + + @GuardedBy("mLock") + private IProcessingSignal mRemote; + + private OnProcessingSignalCallback mOnProcessingSignalCallback; + private Executor mExecutor; + + public ProcessingSignal() { + mActionParamsQueue = new ArrayDeque<>(MAX_QUEUE_SIZE); + } + + /** + * Interface definition for a callback to be invoked when processing signals are received. + */ + public interface OnProcessingSignalCallback { + /** + * Called when a custom signal was received. + * This method allows the receiver to provide logic to be executed based on the signal + * received. + * + * @param actionParams Parameters for the signal in the form of a {@link PersistableBundle}. + */ + + void onSignalReceived(@NonNull PersistableBundle actionParams); + } + + + /** + * Sends a custom signal with the provided parameters. It also signals the remote callback + * with the same params if already configured, if not the action is queued to be sent when a + * remote is configured. Similarly, on the receiver side, the callback will be invoked if + * already set, if not all actions are queued to be sent to callback when it is set. + * + * @param actionParams Parameters for the signal. + */ + public void sendSignal(@NonNull PersistableBundle actionParams) { + final OnProcessingSignalCallback callback; + final IProcessingSignal remote; + synchronized (mLock) { + if (mActionParamsQueue.size() > MAX_QUEUE_SIZE) { + throw new RuntimeException( + "Maximum actions that can be queued are : " + MAX_QUEUE_SIZE); + } + + mActionParamsQueue.add(actionParams); + callback = mOnProcessingSignalCallback; + remote = mRemote; + + if (callback != null) { + while (!mActionParamsQueue.isEmpty()) { + PersistableBundle params = mActionParamsQueue.removeFirst(); + mExecutor.execute( + () -> callback.onSignalReceived(params)); + } + } + if (remote != null) { + while (!mActionParamsQueue.isEmpty()) { + try { + remote.sendSignal(mActionParamsQueue.removeFirst()); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + } + } + + + /** + * Sets the processing signal callback to be called when signals are received. + * + * This method is intended to be used by the recipient of a processing signal + * such as the remote implementation for {@link OnDeviceIntelligenceManager} to handle + * cancellation requests while performing a long-running operation. This method is not + * intended + * to be used by applications themselves. + * + * If {@link ProcessingSignal#sendSignal} has already been called, then the provided callback + * is invoked immediately and all previously queued actions are passed to remote signal. + * + * This method is guaranteed that the callback will not be called after it + * has been removed. + * + * @param callback The processing signal callback, or null to remove the current callback. + * @param executor Executor to the run the callback methods on. + */ + public void setOnProcessingSignalCallback( + @NonNull @CallbackExecutor Executor executor, + @Nullable OnProcessingSignalCallback callback) { + Objects.requireNonNull(executor); + synchronized (mLock) { + if (mOnProcessingSignalCallback == callback) { + return; + } + + mOnProcessingSignalCallback = callback; + mExecutor = executor; + if (callback == null || mActionParamsQueue.isEmpty()) { + return; + } + + while (!mActionParamsQueue.isEmpty()) { + PersistableBundle params = mActionParamsQueue.removeFirst(); + mExecutor.execute(() -> callback.onSignalReceived(params)); + } + } + } + + /** + * Sets the remote transport. + * + * If there are actions queued from {@link ProcessingSignal#sendSignal}, they are also + * sequentially sent to the remote. + * + * This method is guaranteed that the remote transport will not be called after it + * has been removed. + * + * @param remote The remote transport, or null to remove. + * @hide + */ + void setRemote(IProcessingSignal remote) { + synchronized (mLock) { + mRemote = remote; + if (mActionParamsQueue.isEmpty() || remote == null) { + return; + } + + while (!mActionParamsQueue.isEmpty()) { + try { + remote.sendSignal(mActionParamsQueue.removeFirst()); + } catch (RemoteException e) { + throw new RuntimeException("Failed to send action to remote signal", e); + } + } + } + } + + /** + * Creates a transport that can be returned back to the caller of + * a Binder function and subsequently used to dispatch a processing signal. + * + * @return The new processing signal transport. + * @hide + */ + public static IProcessingSignal createTransport() { + return new Transport(); + } + + /** + * Given a locally created transport, returns its associated cancellation signal. + * + * @param transport The locally created transport, or null if none. + * @return The associated processing signal, or null if none. + * @hide + */ + public static ProcessingSignal fromTransport(IProcessingSignal transport) { + if (transport instanceof Transport) { + return ((Transport) transport).mProcessingSignal; + } + return null; + } + + private static final class Transport extends IProcessingSignal.Stub { + final ProcessingSignal mProcessingSignal = new ProcessingSignal(); + + @Override + public void sendSignal(PersistableBundle actionParams) { + mProcessingSignal.sendSignal(actionParams); + } + } + +}
\ No newline at end of file diff --git a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java b/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java new file mode 100644 index 000000000000..ebcf61c8c0c2 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.OutcomeReceiver; + +/** + * Streaming variant of outcome receiver to populate response while processing a given request, + * possibly in + * chunks to provide a async processing behaviour to the caller. + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +public interface StreamingResponseReceiver<R, T, E extends Throwable> extends + OutcomeReceiver<R, E> { + /** + * Callback to be invoked when a part of the response i.e. some {@link Content} is already + * processed and + * needs to be passed onto the caller. + */ + void onNewContent(@NonNull T content); +} diff --git a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig new file mode 100644 index 000000000000..44f33298b1b2 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig @@ -0,0 +1,8 @@ +package: "android.app.ondeviceintelligence.flags" + +flag { + name: "enable_on_device_intelligence" + namespace: "ondeviceintelligence" + description: "Make methods on OnDeviceIntelligenceManager available for local inference." + bug: "304755128" +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index ffe201b055b7..e6e7fa883d54 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5066,6 +5066,7 @@ public abstract class Context { * {@link android.hardware.fingerprint.FingerprintManager} for handling management * of fingerprints. * + * @removed See {@link android.hardware.biometrics.BiometricPrompt} * @see #getSystemService(String) * @see android.hardware.fingerprint.FingerprintManager */ @@ -6450,6 +6451,19 @@ public abstract class Context { @SystemApi public static final String WEARABLE_SENSING_SERVICE = "wearable_sensing"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}. + * + * @see #getSystemService(String) + * @see OnDeviceIntelligenceManager + * @hide + */ + @SystemApi + @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) + public static final String ON_DEVICE_INTELLIGENCE_SERVICE = "on_device_intelligence"; + /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.health.connect.HealthConnectManager}. diff --git a/core/java/android/content/pm/ArchivedActivityInfo.java b/core/java/android/content/pm/ArchivedActivityInfo.java index 166d26503625..9f65f5898c25 100644 --- a/core/java/android/content/pm/ArchivedActivityInfo.java +++ b/core/java/android/content/pm/ArchivedActivityInfo.java @@ -24,6 +24,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.util.Slog; import com.android.internal.util.DataClass; @@ -39,6 +40,7 @@ import java.util.Objects; @DataClass(genBuilder = false, genConstructor = false, genSetters = true) @FlaggedApi(Flags.FLAG_ARCHIVING) public final class ArchivedActivityInfo { + private static final String TAG = "ArchivedActivityInfo"; /** The label for the activity. */ private @NonNull CharSequence mLabel; /** The component name of this activity. */ @@ -138,7 +140,8 @@ public final class ArchivedActivityInfo { bitmap.getByteCount())) { bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); return baos.toByteArray(); - } catch (IOException ignored) { + } catch (IOException e) { + Slog.e(TAG, "Failed to compress bitmap", e); return null; } } @@ -240,10 +243,10 @@ public final class ArchivedActivityInfo { } @DataClass.Generated( - time = 1705615445673L, + time = 1708042076897L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/ArchivedActivityInfo.java", - inputSignatures = "private @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.content.ComponentName mComponentName\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mIcon\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mMonochromeIcon\n @android.annotation.NonNull android.content.pm.ArchivedActivityParcel getParcel()\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable)\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable,int)\npublic static byte[] bytesFromBitmap(android.graphics.Bitmap)\nprivate static android.graphics.drawable.Drawable drawableFromCompressedBitmap(byte[])\nclass ArchivedActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genConstructor=false, genSetters=true)") + inputSignatures = "private static final java.lang.String TAG\nprivate @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate @android.annotation.NonNull android.content.ComponentName mComponentName\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mIcon\nprivate @android.annotation.Nullable android.graphics.drawable.Drawable mMonochromeIcon\n @android.annotation.NonNull android.content.pm.ArchivedActivityParcel getParcel()\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable)\npublic static android.graphics.Bitmap drawableToBitmap(android.graphics.drawable.Drawable,int)\npublic static byte[] bytesFromBitmap(android.graphics.Bitmap)\nprivate static android.graphics.drawable.Drawable drawableFromCompressedBitmap(byte[])\nclass ArchivedActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=false, genConstructor=false, genSetters=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index 087a7952acd7..55d0bc1deedc 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -115,6 +115,11 @@ public abstract class ShortcutServiceInternal { public abstract boolean hasShortcutHostPermission(int launcherUserId, @NonNull String callingPackage, int callingPid, int callingUid); + /** + * Returns whether or not Shortcuts are supported on Launcher Home Screen. + */ + public abstract boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId); + public abstract void setShortcutHostPackage(@NonNull String type, @Nullable String packageName, int userId); diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 6696ba035f30..56849291e52a 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -156,3 +156,10 @@ flag { description: "Set power mode during a user switch." bug: "325249845" } + +flag { + name: "disable_private_space_items_on_home" + namespace: "profile_experiences" + description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically" + bug: "287975131" +} diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 23b9d0b7c9a7..d259e9755a41 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -137,6 +137,8 @@ public final class AssetManager implements AutoCloseable { private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>(); private ArrayList<ResourcesLoader> mLoaders = new ArrayList<>(); + private boolean mNoInit = false; + public Builder addApkAssets(ApkAssets apkAssets) { mUserApkAssets.add(apkAssets); return this; @@ -147,6 +149,11 @@ public final class AssetManager implements AutoCloseable { return this; } + public Builder setNoInit() { + mNoInit = true; + return this; + } + public AssetManager build() { // Retrieving the system ApkAssets forces their creation as well. final ApkAssets[] systemApkAssets = getSystem().getApkAssets(); @@ -188,7 +195,7 @@ public final class AssetManager implements AutoCloseable { final AssetManager assetManager = new AssetManager(false /*sentinel*/); assetManager.mApkAssets = apkAssets; AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets, - false /*invalidateCaches*/); + false /*invalidateCaches*/, mNoInit /*preset*/); assetManager.mLoaders = mLoaders.isEmpty() ? null : mLoaders.toArray(new ResourcesLoader[0]); @@ -329,7 +336,7 @@ public final class AssetManager implements AutoCloseable { synchronized (this) { ensureOpenLocked(); mApkAssets = newApkAssets; - nativeSetApkAssets(mObject, mApkAssets, invalidateCaches); + nativeSetApkAssets(mObject, mApkAssets, invalidateCaches, false); if (invalidateCaches) { // Invalidate all caches. invalidateCachesLocked(-1); @@ -496,7 +503,7 @@ public final class AssetManager implements AutoCloseable { mApkAssets = Arrays.copyOf(mApkAssets, count + 1); mApkAssets[count] = assets; - nativeSetApkAssets(mObject, mApkAssets, true); + nativeSetApkAssets(mObject, mApkAssets, true, false); invalidateCachesLocked(-1); return count + 1; } @@ -1503,12 +1510,29 @@ public final class AssetManager implements AutoCloseable { int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender, int majorVersion) { + setConfigurationInternal(mcc, mnc, defaultLocale, locales, orientation, + touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth, + screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp, + screenLayout, uiMode, colorMode, grammaticalGender, majorVersion, false); + } + + /** + * Change the configuration used when retrieving resources, and potentially force a refresh of + * the state. Not for use by applications. + * @hide + */ + void setConfigurationInternal(int mcc, int mnc, String defaultLocale, String[] locales, + int orientation, int touchscreen, int density, int keyboard, int keyboardHidden, + int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp, + int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode, + int grammaticalGender, int majorVersion, boolean forceRefresh) { synchronized (this) { ensureValidLocked(); nativeSetConfiguration(mObject, mcc, mnc, defaultLocale, locales, orientation, touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth, screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp, - screenLayout, uiMode, colorMode, grammaticalGender, majorVersion); + screenLayout, uiMode, colorMode, grammaticalGender, majorVersion, + forceRefresh); } } @@ -1593,13 +1617,13 @@ public final class AssetManager implements AutoCloseable { private static native long nativeCreate(); private static native void nativeDestroy(long ptr); private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets, - boolean invalidateCaches); + boolean invalidateCaches, boolean preset); private static native void nativeSetConfiguration(long ptr, int mcc, int mnc, @Nullable String defaultLocale, @NonNull String[] locales, int orientation, int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender, - int majorVersion); + int majorVersion, boolean forceRefresh); private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers( long ptr, boolean includeOverlays, boolean includeLoaders); diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 5e442b819774..079c2c1ab7c9 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -200,7 +200,7 @@ public class ResourcesImpl { mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); - updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo()); + updateConfigurationImpl(config, metrics, displayAdjustments.getCompatibilityInfo(), true); } public DisplayAdjustments getDisplayAdjustments() { @@ -402,7 +402,12 @@ public class ResourcesImpl { } public void updateConfiguration(Configuration config, DisplayMetrics metrics, - CompatibilityInfo compat) { + CompatibilityInfo compat) { + updateConfigurationImpl(config, metrics, compat, false); + } + + private void updateConfigurationImpl(Configuration config, DisplayMetrics metrics, + CompatibilityInfo compat, boolean forceAssetsRefresh) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration"); try { synchronized (mAccessLock) { @@ -528,7 +533,7 @@ public class ResourcesImpl { keyboardHidden = mConfiguration.keyboardHidden; } - mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, + mAssets.setConfigurationInternal(mConfiguration.mcc, mConfiguration.mnc, defaultLocale, selectedLocales, mConfiguration.orientation, @@ -539,7 +544,7 @@ public class ResourcesImpl { mConfiguration.screenWidthDp, mConfiguration.screenHeightDp, mConfiguration.screenLayout, mConfiguration.uiMode, mConfiguration.colorMode, mConfiguration.getGrammaticalGender(), - Build.VERSION.RESOURCES_SDK_INT); + Build.VERSION.RESOURCES_SDK_INT, forceAssetsRefresh); if (DEBUG_CONFIG) { Slog.i(TAG, "**** Updating config of " + this + ": final config is " diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index d4c58b239c84..0208fed6040f 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -22,8 +22,9 @@ import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.hardware.biometrics.BiometricManager.Authenticators; import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT; -import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT; import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT; +import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT; +import static android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE; import android.annotation.CallbackExecutor; import android.annotation.DrawableRes; @@ -501,6 +502,28 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Remove {@link Builder#setAllowBackgroundAuthentication(boolean)} once + * FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE is enabled. + * + * @param allow If true, allows authentication when the calling package is not in the + * foreground. This is set to false by default. + * @param useParentProfileForDeviceCredential If true, uses parent profile for device + * credential IME request + * @return This builder + * @hide + */ + @FlaggedApi(FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE) + @TestApi + @NonNull + @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}) + public Builder setAllowBackgroundAuthentication(boolean allow, + boolean useParentProfileForDeviceCredential) { + mPromptInfo.setAllowBackgroundAuthentication(allow); + mPromptInfo.setUseParentProfileForDeviceCredential(useParentProfileForDeviceCredential); + return this; + } + + /** * If set check the Device Policy Manager for disabled biometrics. * * @param checkDevicePolicyManager diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index 2236660ee388..8bb958553673 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -55,6 +55,7 @@ public class PromptInfo implements Parcelable { private boolean mIgnoreEnrollmentState; private boolean mIsForLegacyFingerprintManager = false; private boolean mShowEmergencyCallButton = false; + private boolean mUseParentProfileForDeviceCredential = false; public PromptInfo() { @@ -85,6 +86,7 @@ public class PromptInfo implements Parcelable { mIgnoreEnrollmentState = in.readBoolean(); mIsForLegacyFingerprintManager = in.readBoolean(); mShowEmergencyCallButton = in.readBoolean(); + mUseParentProfileForDeviceCredential = in.readBoolean(); } public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() { @@ -129,6 +131,7 @@ public class PromptInfo implements Parcelable { dest.writeBoolean(mIgnoreEnrollmentState); dest.writeBoolean(mIsForLegacyFingerprintManager); dest.writeBoolean(mShowEmergencyCallButton); + dest.writeBoolean(mUseParentProfileForDeviceCredential); } // LINT.IfChange @@ -181,6 +184,13 @@ public class PromptInfo implements Parcelable { } return false; } + + /** + * Returns if parent profile needs to be used for device credential. + */ + public boolean shouldUseParentProfileForDeviceCredential() { + return mUseParentProfileForDeviceCredential; + } // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java) // Setters @@ -281,6 +291,11 @@ public class PromptInfo implements Parcelable { mShowEmergencyCallButton = showEmergencyCallButton; } + public void setUseParentProfileForDeviceCredential( + boolean useParentProfileForDeviceCredential) { + mUseParentProfileForDeviceCredential = useParentProfileForDeviceCredential; + } + // Getters @DrawableRes public int getLogoRes() { diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java index 86293540dd36..e35e80126388 100644 --- a/core/java/android/hardware/devicestate/DeviceState.java +++ b/core/java/android/hardware/devicestate/DeviceState.java @@ -202,7 +202,7 @@ public final class DeviceState { * primary display area. * * Note: This does not necessarily mean that the outer display area is the - * @link Display#DEFAULT_DISPLAY}. + * {@link Display#DEFAULT_DISPLAY}. */ public static final int PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY = 11; diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index fe7de8333784..5a38a34fde11 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -83,7 +83,8 @@ import javax.crypto.Mac; /** * A class that coordinates access to the fingerprint hardware. - * @deprecated See {@link BiometricPrompt} which shows a system-provided dialog upon starting + * + * @removed See {@link BiometricPrompt} which shows a system-provided dialog upon starting * authentication. In a world where devices may have different types of biometric authentication, * it's much more realistic to have a system-provided authentication dialog since the method may * vary by vendor/device. @@ -94,7 +95,6 @@ import javax.crypto.Mac; @RequiresFeature(PackageManager.FEATURE_FINGERPRINT) public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants { private static final String TAG = "FingerprintManager"; - private static final boolean DEBUG = true; private static final int MSG_ENROLL_RESULT = 100; private static final int MSG_ACQUIRED = 101; private static final int MSG_AUTHENTICATION_SUCCEEDED = 102; @@ -196,6 +196,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Retrieves a test session for FingerprintManager. + * * @hide */ @TestApi @@ -254,9 +255,10 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** - * A wrapper class for the crypto objects supported by FingerprintManager. Currently the + * A wrapper class for the crypto objects supported by FingerprintManager. Currently, the * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects. - * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} + * + * @removed See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} */ @Deprecated public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { @@ -330,7 +332,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Container for callback data from {@link FingerprintManager#authenticate(CryptoObject, * CancellationSignal, int, AuthenticationCallback, Handler)}. - * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult} + * + * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult} */ @Deprecated public static class AuthenticationResult { @@ -392,7 +395,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * FingerprintManager#authenticate(CryptoObject, CancellationSignal, * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to * fingerprint events. - * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback} + * + * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback} */ @Deprecated public static abstract class AuthenticationCallback @@ -455,6 +459,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Callback structure provided for {@link #detectFingerprint(CancellationSignal, * FingerprintDetectionCallback, int, Surface)}. + * * @hide */ public interface FingerprintDetectionCallback { @@ -608,7 +613,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * by <a href="{@docRoot}training/articles/keystore.html">Android Keystore * facility</a>. * @throws IllegalStateException if the crypto primitive is not initialized. - * @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor, + * + * @removed See {@link BiometricPrompt#authenticate(CancellationSignal, Executor, * BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate( * BiometricPrompt.CryptoObject, CancellationSignal, Executor, * BiometricPrompt.AuthenticationCallback)} @@ -623,6 +629,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Per-user version of authenticate. * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}. + * * @hide */ @Deprecated @@ -635,6 +642,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Per-user and per-sensor version of authenticate. * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}. + * * @hide */ @Deprecated @@ -651,6 +659,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Version of authenticate with additional options. + * * @hide */ @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) @@ -698,6 +707,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Uses the fingerprint hardware to detect for the presence of a finger, without giving details * about accept/reject/lockout. + * * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) @@ -740,6 +750,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @param callback an object to receive enrollment events * @param shouldLogMetrics a flag that indicates if enrollment failure/success metrics * should be logged. + * * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) @@ -809,6 +820,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first * enumerated sensor. + * * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) @@ -823,6 +835,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Revokes the specified challenge. + * * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) @@ -848,6 +861,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @param sensorId Sensor ID that this operation takes effect for * @param userId User ID that this operation takes effect for. * @param hardwareAuthToken An opaque token returned by password confirmation. + * * @hide */ @RequiresPermission(RESET_FINGERPRINT_LOCKOUT) @@ -885,6 +899,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Removes all fingerprint templates for the given user. + * * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) @@ -1004,6 +1019,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Forwards BiometricStateListener to FingerprintService * @param listener new BiometricStateListener being added + * * @hide */ public void registerBiometricStateListener(@NonNull BiometricStateListener listener) { @@ -1155,7 +1171,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** - * This is triggered by SideFpsEventHandler + * This is triggered by SideFpsEventHandler. + * * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) @@ -1168,7 +1185,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * Determine if there is at least one fingerprint enrolled. * * @return true if at least one fingerprint is enrolled, false otherwise - * @deprecated See {@link BiometricPrompt} and + * + * @removed See {@link BiometricPrompt} and * {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS} */ @Deprecated @@ -1202,7 +1220,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * Determine if fingerprint hardware is present and functional. * * @return true if hardware is present and functional, false otherwise. - * @deprecated See {@link BiometricPrompt} and + * + * @removed See {@link BiometricPrompt} and * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE} */ @Deprecated @@ -1228,6 +1247,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Get statically configured sensor properties. + * * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) @@ -1246,6 +1266,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Returns whether the device has a power button fingerprint sensor. * @return boolean indicating whether power button is fingerprint sensor + * * @hide */ public boolean isPowerbuttonFps() { diff --git a/core/java/android/service/voice/VisualQueryDetectedResult.java b/core/java/android/service/voice/VisualQueryDetectedResult.java index 322148acdca5..3b617948b41e 100644 --- a/core/java/android/service/voice/VisualQueryDetectedResult.java +++ b/core/java/android/service/voice/VisualQueryDetectedResult.java @@ -68,6 +68,22 @@ public final class VisualQueryDetectedResult implements Parcelable { return 15; } + /** + * Detected signal representing the arbitrary data that will make accessibility detections work. + * + * This field should only be set if the device setting for allowing accessibility data is on + * based on the result of {@link VisualQueryDetector#isAccessibilityDetectionEnabled()}. If the + * enable bit return by the method is {@code false}, it would suggest a failure to egress the + * {@link VisualQueryDetectedResult} object with this field set. The system server will prevent + * egress and invoke + * {@link VisualQueryDetector.Callback#onFailure(VisualQueryDetectionServiceFailure)}. + */ + @Nullable + private final byte[] mAccessibilityDetectionData; + private static byte[] defaultAccessibilityDetectionData() { + return null; + } + private void onConstructed() { Preconditions.checkArgumentInRange(mSpeakerId, 0, getMaxSpeakerId(), "speakerId"); } @@ -78,7 +94,10 @@ public final class VisualQueryDetectedResult implements Parcelable { * @hide */ public Builder buildUpon() { - return new Builder().setPartialQuery(mPartialQuery).setSpeakerId(mSpeakerId); + return new Builder() + .setPartialQuery(mPartialQuery) + .setSpeakerId(mSpeakerId) + .setAccessibilityDetectionData(mAccessibilityDetectionData); } @@ -98,11 +117,13 @@ public final class VisualQueryDetectedResult implements Parcelable { @DataClass.Generated.Member /* package-private */ VisualQueryDetectedResult( @NonNull String partialQuery, - int speakerId) { + int speakerId, + @Nullable byte[] accessibilityDetectionData) { this.mPartialQuery = partialQuery; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPartialQuery); this.mSpeakerId = speakerId; + this.mAccessibilityDetectionData = accessibilityDetectionData; onConstructed(); } @@ -125,6 +146,16 @@ public final class VisualQueryDetectedResult implements Parcelable { return mSpeakerId; } + /** + * Detected signal representing the data for allowing accessibility feature. This field can + * only be set when the secure device settings is set to true by either settings page UI or + * {@link VisualQueryDetector@setAccessibilityDetectionEnabled(boolean)} + */ + @DataClass.Generated.Member + public @Nullable byte[] getAccessibilityDetectionData() { + return mAccessibilityDetectionData; + } + @Override @DataClass.Generated.Member public String toString() { @@ -133,7 +164,8 @@ public final class VisualQueryDetectedResult implements Parcelable { return "VisualQueryDetectedResult { " + "partialQuery = " + mPartialQuery + ", " + - "speakerId = " + mSpeakerId + + "speakerId = " + mSpeakerId + ", " + + "accessibilityDetectionData = " + java.util.Arrays.toString(mAccessibilityDetectionData) + " }"; } @@ -151,7 +183,8 @@ public final class VisualQueryDetectedResult implements Parcelable { //noinspection PointlessBooleanExpression return true && Objects.equals(mPartialQuery, that.mPartialQuery) - && mSpeakerId == that.mSpeakerId; + && mSpeakerId == that.mSpeakerId + && java.util.Arrays.equals(mAccessibilityDetectionData, that.mAccessibilityDetectionData); } @Override @@ -163,6 +196,7 @@ public final class VisualQueryDetectedResult implements Parcelable { int _hash = 1; _hash = 31 * _hash + Objects.hashCode(mPartialQuery); _hash = 31 * _hash + mSpeakerId; + _hash = 31 * _hash + java.util.Arrays.hashCode(mAccessibilityDetectionData); return _hash; } @@ -174,6 +208,7 @@ public final class VisualQueryDetectedResult implements Parcelable { dest.writeString(mPartialQuery); dest.writeInt(mSpeakerId); + dest.writeByteArray(mAccessibilityDetectionData); } @Override @@ -189,11 +224,13 @@ public final class VisualQueryDetectedResult implements Parcelable { String partialQuery = in.readString(); int speakerId = in.readInt(); + byte[] accessibilityDetectionData = in.createByteArray(); this.mPartialQuery = partialQuery; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mPartialQuery); this.mSpeakerId = speakerId; + this.mAccessibilityDetectionData = accessibilityDetectionData; onConstructed(); } @@ -221,6 +258,7 @@ public final class VisualQueryDetectedResult implements Parcelable { private @NonNull String mPartialQuery; private int mSpeakerId; + private @Nullable byte[] mAccessibilityDetectionData; private long mBuilderFieldsSet = 0L; @@ -251,10 +289,23 @@ public final class VisualQueryDetectedResult implements Parcelable { return this; } + /** + * Detected signal representing the data for allowing accessibility feature. This field can + * only be set when the secure device settings is set to true by either settings page UI or + * {@link VisualQueryDetector@setAccessibilityDetectionEnabled(boolean)} + */ + @DataClass.Generated.Member + public @NonNull Builder setAccessibilityDetectionData(@NonNull byte... value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mAccessibilityDetectionData = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull VisualQueryDetectedResult build() { checkNotUsed(); - mBuilderFieldsSet |= 0x4; // Mark builder used + mBuilderFieldsSet |= 0x8; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mPartialQuery = defaultPartialQuery(); @@ -262,14 +313,18 @@ public final class VisualQueryDetectedResult implements Parcelable { if ((mBuilderFieldsSet & 0x2) == 0) { mSpeakerId = defaultSpeakerId(); } + if ((mBuilderFieldsSet & 0x4) == 0) { + mAccessibilityDetectionData = defaultAccessibilityDetectionData(); + } VisualQueryDetectedResult o = new VisualQueryDetectedResult( mPartialQuery, - mSpeakerId); + mSpeakerId, + mAccessibilityDetectionData); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x4) != 0) { + if ((mBuilderFieldsSet & 0x8) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -277,10 +332,10 @@ public final class VisualQueryDetectedResult implements Parcelable { } @DataClass.Generated( - time = 1704949386772L, + time = 1707429290528L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/voice/VisualQueryDetectedResult.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPartialQuery\nprivate final int mSpeakerId\nprivate static java.lang.String defaultPartialQuery()\nprivate static int defaultSpeakerId()\npublic static int getMaxSpeakerId()\nprivate void onConstructed()\npublic android.service.voice.VisualQueryDetectedResult.Builder buildUpon()\nclass VisualQueryDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPartialQuery\nprivate final int mSpeakerId\nprivate final @android.annotation.Nullable byte[] mAccessibilityDetectionData\nprivate static java.lang.String defaultPartialQuery()\nprivate static int defaultSpeakerId()\npublic static int getMaxSpeakerId()\nprivate static byte[] defaultAccessibilityDetectionData()\nprivate void onConstructed()\npublic android.service.voice.VisualQueryDetectedResult.Builder buildUpon()\nclass VisualQueryDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java index 1eb4d6730e1e..bf8de06fd244 100644 --- a/core/java/android/service/voice/VisualQueryDetector.java +++ b/core/java/android/service/voice/VisualQueryDetector.java @@ -39,6 +39,7 @@ import android.text.TextUtils; import android.util.Slog; import com.android.internal.app.IHotwordRecognitionStatusCallback; +import com.android.internal.app.IVoiceInteractionAccessibilitySettingsListener; import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.infra.AndroidFuture; @@ -61,6 +62,8 @@ import java.util.function.Consumer; public class VisualQueryDetector { private static final String TAG = VisualQueryDetector.class.getSimpleName(); private static final boolean DEBUG = false; + private static final int SETTINGS_DISABLE_BIT = 0; + private static final int SETTINGS_ENABLE_BIT = 1; private final Callback mCallback; private final Executor mExecutor; @@ -68,6 +71,8 @@ public class VisualQueryDetector { private final IVoiceInteractionManagerService mManagerService; private final VisualQueryDetectorInitializationDelegate mInitializationDelegate; private final String mAttributionTag; + // Used to manage the internal mapping of exposed listener API and internal aidl impl + private AccessibilityDetectionEnabledListenerWrapper mActiveAccessibilityListenerWrapper = null; VisualQueryDetector( IVoiceInteractionManagerService managerService, @@ -174,6 +179,108 @@ public class VisualQueryDetector { } } + /** + * Gets the binary value that controls the egress of accessibility data from + * {@link VisualQueryDetectedResult#setAccessibilityDetectionData(byte[])} is enabled. + * + * @return boolean value denoting if the setting is on. Default is {@code false}. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS) + public boolean isAccessibilityDetectionEnabled() { + Slog.d(TAG, "Fetching accessibility setting"); + synchronized (mInitializationDelegate.getLock()) { + try { + return mManagerService.getAccessibilityDetectionEnabled(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + } + + /** + * Sets a listener subscribing to the value of the system setting that controls the egress of + * accessibility data from + * {@link VisualQueryDetectedResult#setAccessibilityDetectionData(byte[])} is enabled. + * + * Only one listener can be set at a time. The listener set must be unset with + * {@link clearAccessibilityDetectionEnabledListener(Consumer<Boolean>)} + * in order to set a new listener. Otherwise, this method will throw a + * {@link IllegalStateException}. + * + * @param listener Listener of type {@code Consumer<Boolean>} to subscribe to the value update. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS) + public void setAccessibilityDetectionEnabledListener(@NonNull Consumer<Boolean> listener) { + Slog.d(TAG, "Registering Accessibility settings listener."); + synchronized (mInitializationDelegate.getLock()) { + try { + if (mActiveAccessibilityListenerWrapper != null) { + Slog.e(TAG, "Fail to register accessibility setting listener: " + + "already registered and not unregistered."); + throw new IllegalStateException( + "Cannot register listener with listeners already set."); + } + mActiveAccessibilityListenerWrapper = + new AccessibilityDetectionEnabledListenerWrapper(listener); + mManagerService.registerAccessibilityDetectionSettingsListener( + mActiveAccessibilityListenerWrapper); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + /** + * Clear the listener that has been set with + * {@link setAccessibilityDetectionEnabledListener(Consumer<Boolean>)} such that when the value + * of the setting that controls the egress of accessibility data is changed the listener gets + * notified. + * + * If there is not listener that has been registered, the call to this method will lead to a + * {@link IllegalStateException}. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ALLOW_COMPLEX_RESULTS_EGRESS_FROM_VQDS) + public void clearAccessibilityDetectionEnabledListener() { + Slog.d(TAG, "Unregistering Accessibility settings listener."); + synchronized (mInitializationDelegate.getLock()) { + try { + if (mActiveAccessibilityListenerWrapper == null) { + Slog.e(TAG, "Not able to remove the listener: listener does not exist."); + throw new IllegalStateException("Cannot clear listener since it is not set."); + } + mManagerService.unregisterAccessibilityDetectionSettingsListener( + mActiveAccessibilityListenerWrapper); + mActiveAccessibilityListenerWrapper = null; + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + + private final class AccessibilityDetectionEnabledListenerWrapper + extends IVoiceInteractionAccessibilitySettingsListener.Stub { + + private Consumer<Boolean> mListener; + + AccessibilityDetectionEnabledListenerWrapper(Consumer<Boolean> listener) { + mListener = listener; + } + + @Override + public void onAccessibilityDetectionChanged(boolean enabled) { + mListener.accept(enabled); + } + } + /** @hide */ public void dump(String prefix, PrintWriter pw) { synchronized (mInitializationDelegate.getLock()) { diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java index 5cbbbef2cf88..2226881c82cf 100644 --- a/core/java/android/util/MemoryIntArray.java +++ b/core/java/android/util/MemoryIntArray.java @@ -58,6 +58,7 @@ public final class MemoryIntArray implements Parcelable, Closeable { private final boolean mIsOwner; private final long mMemoryAddr; + private final int mSize; private int mFd = -1; /** @@ -75,6 +76,9 @@ public final class MemoryIntArray implements Parcelable, Closeable { final String name = UUID.randomUUID().toString(); mFd = nativeCreate(name, size); mMemoryAddr = nativeOpen(mFd, mIsOwner); + // Note that we use the effective size after allocation, rather than the provided size, + // preserving compat with the original behavior. In practice these should be equivalent. + mSize = nativeSize(mFd); mCloseGuard.open("MemoryIntArray.close"); } @@ -86,6 +90,7 @@ public final class MemoryIntArray implements Parcelable, Closeable { } mFd = pfd.detachFd(); mMemoryAddr = nativeOpen(mFd, mIsOwner); + mSize = nativeSize(mFd); mCloseGuard.open("MemoryIntArray.close"); } @@ -127,13 +132,11 @@ public final class MemoryIntArray implements Parcelable, Closeable { } /** - * Gets the array size. - * - * @throws IOException If an error occurs while accessing the shared memory. + * @return Gets the array size. */ - public int size() throws IOException { + public int size() { enforceNotClosed(); - return nativeSize(mFd); + return mSize; } /** @@ -210,11 +213,10 @@ public final class MemoryIntArray implements Parcelable, Closeable { } } - private void enforceValidIndex(int index) throws IOException { - final int size = size(); - if (index < 0 || index > size - 1) { + private void enforceValidIndex(int index) { + if (index < 0 || index > mSize - 1) { throw new IndexOutOfBoundsException( - index + " not between 0 and " + (size - 1)); + index + " not between 0 and " + (mSize - 1)); } } diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java index 4fe53c2410f5..a8d4e2d2c70a 100644 --- a/core/java/android/view/KeyCharacterMap.java +++ b/core/java/android/view/KeyCharacterMap.java @@ -579,6 +579,17 @@ public class KeyCharacterMap implements Parcelable { } /** + * Get the combining character that corresponds with the provided accent. + * + * @param accent The accent character. eg. '`' + * @return The combining character + * @hide + */ + public static int getCombiningChar(int accent) { + return sAccentToCombining.get(accent); + } + + /** * Describes the character mappings associated with a key. * * @deprecated instead use {@link KeyCharacterMap#getDisplayLabel(int)}, diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java index 715f1befb046..13b9c4518711 100644 --- a/core/java/android/view/PointerIcon.java +++ b/core/java/android/view/PointerIcon.java @@ -430,6 +430,11 @@ public final class PointerIcon implements Parcelable { VectorDrawable vectorDrawable) { Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + // BitmapDrawables and Bitmap have a default density of DisplayMetrics.DENSITY_DEVICE, + // (which is deprecated in favor of DENSITY_DEVICE_STABLE/resources.densityDpi). In + // rare cases when device density differs from the resource density, the bitmap will + // scale as the BitmapDrawable is created. Avoid by explicitly setting density here. + bitmap.setDensity(resources.getDisplayMetrics().densityDpi); Canvas canvas = new Canvas(bitmap); vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); vectorDrawable.draw(canvas); diff --git a/core/java/com/android/internal/app/IVoiceInteractionAccessibilitySettingsListener.aidl b/core/java/com/android/internal/app/IVoiceInteractionAccessibilitySettingsListener.aidl new file mode 100644 index 000000000000..a9190353dc66 --- /dev/null +++ b/core/java/com/android/internal/app/IVoiceInteractionAccessibilitySettingsListener.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +oneway interface IVoiceInteractionAccessibilitySettingsListener { + /** + * Called when the value of secure setting has changed. + */ + void onAccessibilityDetectionChanged(boolean enable); +} diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 314ed69cb885..98d393977958 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -35,6 +35,7 @@ import android.service.voice.VisibleActivityInfo; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.app.IVoiceActionCheckCallback; +import com.android.internal.app.IVoiceInteractionAccessibilitySettingsListener; import com.android.internal.app.IVoiceInteractionSessionListener; import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractionSoundTriggerSession; @@ -382,4 +383,21 @@ interface IVoiceInteractionManagerService { oneway void notifyActivityEventChanged( in IBinder activityToken, int type); + + /** + * rely on the system server to get the secure settings + */ + boolean getAccessibilityDetectionEnabled(); + + /** + * register the listener + */ + oneway void registerAccessibilityDetectionSettingsListener( + in IVoiceInteractionAccessibilitySettingsListener listener); + + /** + * unregister the listener + */ + oneway void unregisterAccessibilityDetectionSettingsListener( + in IVoiceInteractionAccessibilitySettingsListener listener); } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index ed43b8190f93..d463b62e62a3 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -376,4 +376,10 @@ oneway interface IStatusBar * @param packageName of the session for which the output switcher is shown. */ void showMediaOutputSwitcher(String packageName); + + /** Enters desktop mode. + * + * @param displayId the id of the current display. + */ + void enterDesktop(int displayId); } diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 3ee15ab734b9..3d0ab4ef0981 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -314,7 +314,8 @@ static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { } static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr, - jobjectArray apk_assets_array, jboolean invalidate_caches) { + jobjectArray apk_assets_array, jboolean invalidate_caches, + jboolean preset) { ATRACE_NAME("AssetManager::SetApkAssets"); const jsize apk_assets_len = env->GetArrayLength(apk_assets_array); @@ -343,7 +344,11 @@ static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr, } auto assetmanager = LockAndStartAssetManager(ptr); - assetmanager->SetApkAssets(apk_assets, invalidate_caches); + if (preset) { + assetmanager->PresetApkAssets(apk_assets); + } else { + assetmanager->SetApkAssets(apk_assets, invalidate_caches); + } } static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc, @@ -353,7 +358,7 @@ static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jin jint screen_height, jint smallest_screen_width_dp, jint screen_width_dp, jint screen_height_dp, jint screen_layout, jint ui_mode, jint color_mode, jint grammatical_gender, - jint major_version) { + jint major_version, jboolean force_refresh) { ATRACE_NAME("AssetManager::SetConfiguration"); const jsize locale_count = (locales == NULL) ? 0 : env->GetArrayLength(locales); @@ -413,7 +418,7 @@ static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jin } auto assetmanager = LockAndStartAssetManager(ptr); - assetmanager->SetConfigurations(configs); + assetmanager->SetConfigurations(std::move(configs), force_refresh != JNI_FALSE); assetmanager->SetDefaultLocale(default_locale_int); } @@ -1522,8 +1527,8 @@ static const JNINativeMethod gAssetManagerMethods[] = { // AssetManager setup methods. {"nativeCreate", "()J", (void*)NativeCreate}, {"nativeDestroy", "(J)V", (void*)NativeDestroy}, - {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets}, - {"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIII)V", + {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;ZZ)V", (void*)NativeSetApkAssets}, + {"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIIIZ)V", (void*)NativeSetConfiguration}, {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;", (void*)NativeGetAssignedPackageIdentifiers}, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a79b29c67c2c..0b3a06545648 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7937,6 +7937,33 @@ <permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an app to use the on-device intelligence service. + <p>Protection level: signature|privileged + @hide + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") + --> + <permission android:name="android.permission.USE_ON_DEVICE_INTELLIGENCE" + android:protectionLevel="signature|privileged" /> + + + <!-- @SystemApi Allows an app to bind the on-device intelligence service. + <p>Protection level: signature|privileged + @hide + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") + --> + <permission android:name="android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE" + android:protectionLevel="signature|privileged" /> + + + <!-- @SystemApi Allows an app to bind the on-device trusted service. + <p>Protection level: signature|privileged + @hide + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") + --> + <permission android:name="android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE" + android:protectionLevel="signature"/> + + <!-- Allows applications to use the user-initiated jobs API. For more details see {@link android.app.job.JobInfo.Builder#setUserInitiated}. <p>Protection level: normal diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml index fa15c3fa4cff..972fe7ed91de 100644 --- a/core/res/res/values/dimens_material.xml +++ b/core/res/res/values/dimens_material.xml @@ -204,11 +204,4 @@ <dimen name="progress_bar_size_small">16dip</dimen> <dimen name="progress_bar_size_medium">48dp</dimen> <dimen name="progress_bar_size_large">76dp</dimen> - - <!-- System corner radius baseline sizes. Used by Material styling of rounded corner shapes--> - <dimen name="system_corner_radius_xsmall">4dp</dimen> - <dimen name="system_corner_radius_small">8dp</dimen> - <dimen name="system_corner_radius_medium">16dp</dimen> - <dimen name="system_corner_radius_large">26dp</dimen> - <dimen name="system_corner_radius_xlarge">36dp</dimen> </resources> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index c797210345a2..5987f6ea05d4 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -184,11 +184,11 @@ <staging-public-group type="dimen" first-id="0x01b90000"> <!-- System corner radius baseline sizes. Used by Material styling of rounded corner shapes--> - <public name="system_corner_radius_xsmall" /> - <public name="system_corner_radius_small" /> - <public name="system_corner_radius_medium" /> - <public name="system_corner_radius_large" /> - <public name="system_corner_radius_xlarge" /> + <public name="removed_system_corner_radius_xsmall" /> + <public name="removed_system_corner_radius_small" /> + <public name="removed_system_corner_radius_medium" /> + <public name="removed_system_corner_radius_large" /> + <public name="removed_system_corner_radius_xlarge" /> </staging-public-group> <staging-public-group type="color" first-id="0x01b80000"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7c290b1ba3e1..9d7acffee68b 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5356,11 +5356,4 @@ <java-symbol type="drawable" name="ic_satellite_alt_24px" /> <java-symbol type="bool" name="config_watchlistUseFileHashesCache" /> - - <!-- System corner radius baseline sizes. Used by Material styling of rounded corner shapes--> - <java-symbol type="dimen" name="system_corner_radius_xsmall" /> - <java-symbol type="dimen" name="system_corner_radius_small" /> - <java-symbol type="dimen" name="system_corner_radius_medium" /> - <java-symbol type="dimen" name="system_corner_radius_large" /> - <java-symbol type="dimen" name="system_corner_radius_xlarge" /> </resources> diff --git a/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java b/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java new file mode 100644 index 000000000000..aaa199d4c814 --- /dev/null +++ b/core/tests/coretests/src/android/accessibilityservice/BrailleDisplayControllerImplTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accessibilityservice; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.hardware.usb.UsbDevice; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityInteractionClient; +import android.view.accessibility.Flags; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.Executor; + +/** + * Tests for internal details of BrailleDisplayControllerImpl. + * + * <p>Prefer adding new tests in CTS where possible. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +@RequiresFlagsEnabled(Flags.FLAG_BRAILLE_DISPLAY_HID) +public class BrailleDisplayControllerImplTest { + private static final int TEST_SERVICE_CONNECTION_ID = 123; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private BrailleDisplayController mBrailleDisplayController; + + @Mock + private IAccessibilityServiceConnection mAccessibilityServiceConnection; + @Mock + private BrailleDisplayController.BrailleDisplayCallback mBrailleDisplayCallback; + + public static class TestAccessibilityService extends AccessibilityService { + public void onAccessibilityEvent(AccessibilityEvent event) { + } + + public void onInterrupt() { + } + } + + @Before + public void test() { + MockitoAnnotations.initMocks(this); + AccessibilityService accessibilityService = spy(new TestAccessibilityService()); + doReturn((Executor) Runnable::run).when(accessibilityService).getMainExecutor(); + doReturn(TEST_SERVICE_CONNECTION_ID).when(accessibilityService).getConnectionId(); + AccessibilityInteractionClient.addConnection(TEST_SERVICE_CONNECTION_ID, + mAccessibilityServiceConnection, /*initializeCache=*/false); + mBrailleDisplayController = accessibilityService.getBrailleDisplayController(); + } + + // Automated CTS tests only use the BluetoothDevice version of BrailleDisplayController#connect + // because fake UsbDevice objects cannot be created in CTS. This internal test can mock the + // UsbDevice object and at least validate that the correct system_server AIDL call is made. + @Test + public void connect_withUsbDevice_callsConnectUsbBrailleDisplay() throws Exception { + UsbDevice usbDevice = Mockito.mock(UsbDevice.class); + + mBrailleDisplayController.connect(usbDevice, mBrailleDisplayCallback); + + verify(mAccessibilityServiceConnection).connectUsbBrailleDisplay(eq(usbDevice), any()); + } + + @Test + public void connect_serviceNotConnected_throwsIllegalStateException() { + AccessibilityInteractionClient.removeConnection(TEST_SERVICE_CONNECTION_ID); + UsbDevice usbDevice = Mockito.mock(UsbDevice.class); + + assertThrows(IllegalStateException.class, + () -> mBrailleDisplayController.connect(usbDevice, mBrailleDisplayCallback)); + } +} diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java index fb5e5120c354..c2877217a529 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java @@ -57,19 +57,15 @@ public final class DeviceStateTest { @Test public void testConstruct_tooLargeIdentifier() { - assertThrows(IllegalArgumentException.class, () -> { - final DeviceState state = new DeviceState( - MAXIMUM_DEVICE_STATE_IDENTIFIER + 1 /* identifier */, - null /* name */, 0 /* flags */); - }); + assertThrows(IllegalArgumentException.class, + () -> new DeviceState(MAXIMUM_DEVICE_STATE_IDENTIFIER + 1 /* identifier */, + null /* name */, 0 /* flags */)); } @Test public void testConstruct_tooSmallIdentifier() { - assertThrows(IllegalArgumentException.class, () -> { - final DeviceState state = new DeviceState( - MINIMUM_DEVICE_STATE_IDENTIFIER - 1 /* identifier */, - null /* name */, 0 /* flags */); - }); + assertThrows(IllegalArgumentException.class, + () -> new DeviceState(MINIMUM_DEVICE_STATE_IDENTIFIER - 1 /* identifier */, + null /* name */, 0 /* flags */)); } } diff --git a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java index 51013e4b4f00..8093af9bb004 100644 --- a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java +++ b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java @@ -123,7 +123,7 @@ public class MemoryIntArrayTest { parcel.recycle(); assertNotNull("Should marshall file descriptor", secondArray); - + assertEquals("Marshalled size must be three", 3, secondArray.size()); assertEquals("First element should be 1", 1, secondArray.get(0)); assertEquals("First element should be 2", 2, secondArray.get(1)); assertEquals("First element should be 3", 3, secondArray.get(2)); diff --git a/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java b/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java index 9264c6c86c3f..32dda6be1e2b 100644 --- a/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java +++ b/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java @@ -84,11 +84,7 @@ public class RemoteMemoryIntArrayService extends Service { @Override public int size() { synchronized (mLock) { - try { - return mArray.size(); - } catch (IOException e) { - throw new IllegalStateException(e); - } + return mArray.size(); } } diff --git a/graphics/java/android/graphics/pdf/TEST_MAPPING b/graphics/java/android/graphics/pdf/TEST_MAPPING index d763598f5ba0..afec35c76371 100644 --- a/graphics/java/android/graphics/pdf/TEST_MAPPING +++ b/graphics/java/android/graphics/pdf/TEST_MAPPING @@ -1,7 +1,12 @@ { "presubmit": [ { - "name": "CtsPdfTestCases" + "name": "CtsPdfTestCases", + "options": [ + { + "include-filter": "android.graphics.pdf.cts.PdfDocumentTest" + } + ] } ] } diff --git a/libs/WindowManager/Shell/aconfig/OWNERS b/libs/WindowManager/Shell/aconfig/OWNERS new file mode 100644 index 000000000000..9eba0f2dea7b --- /dev/null +++ b/libs/WindowManager/Shell/aconfig/OWNERS @@ -0,0 +1,3 @@ +# Owners for flag changes +madym@google.com +hwwang@google.com
\ No newline at end of file diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 9a66c0fa9eb9..0967f4e83c74 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -64,3 +64,10 @@ flag { description: "Enables the new bubble bar UI for tablets" bug: "286246694" } + +flag { + name: "enable_bubbles_long_press_nav_handle" + namespace: "multitasking" + description: "Enables long-press action for nav handle when a bubble is expanded" + bug: "324910035" +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index 4e995bc7c92e..8946f41e96a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -15,6 +15,7 @@ */ package com.android.wm.shell.bubbles.bar; +import static android.view.View.ALPHA; import static android.view.View.SCALE_X; import static android.view.View.SCALE_Y; import static android.view.View.TRANSLATION_X; @@ -69,6 +70,7 @@ public class BubbleBarAnimationHelper { private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.2f; private static final float EXPANDED_VIEW_DRAG_SCALE = 0.4f; private static final float DISMISS_VIEW_SCALE = 1.25f; + private static final int HANDLE_ALPHA_ANIMATION_DURATION = 100; /** Spring config for the expanded view scale-in animation. */ private final PhysicsAnimator.SpringConfig mScaleInSpringConfig = @@ -248,15 +250,22 @@ public class BubbleBarAnimationHelper { return; } setDragPivot(bbev); - AnimatorSet animatorSet = new AnimatorSet(); // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE; - animatorSet.playTogether( + + AnimatorSet contentAnim = new AnimatorSet(); + contentAnim.playTogether( ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_DRAG_SCALE), ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_DRAG_SCALE), ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius) ); - animatorSet.setDuration(EXPANDED_VIEW_DRAG_ANIMATION_DURATION).setInterpolator(EMPHASIZED); + contentAnim.setDuration(EXPANDED_VIEW_DRAG_ANIMATION_DURATION).setInterpolator(EMPHASIZED); + + ObjectAnimator handleAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 0f) + .setDuration(HANDLE_ALPHA_ANIMATION_DURATION); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(contentAnim, handleAnim); animatorSet.addListener(new DragAnimatorListenerAdapter(bbev)); startNewDragAnimation(animatorSet); } @@ -297,15 +306,21 @@ public class BubbleBarAnimationHelper { } Point restPoint = getExpandedViewRestPosition(getExpandedViewSize()); - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether( + AnimatorSet contentAnim = new AnimatorSet(); + contentAnim.playTogether( ObjectAnimator.ofFloat(bbev, X, restPoint.x), ObjectAnimator.ofFloat(bbev, Y, restPoint.y), ObjectAnimator.ofFloat(bbev, SCALE_X, 1f), ObjectAnimator.ofFloat(bbev, SCALE_Y, 1f), ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, bbev.getRestingCornerRadius()) ); - animatorSet.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION).setInterpolator(EMPHASIZED); + contentAnim.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION).setInterpolator(EMPHASIZED); + + ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 1f) + .setDuration(HANDLE_ALPHA_ANIMATION_DURATION); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(contentAnim, handleAlphaAnim); animatorSet.addListener(new DragAnimatorListenerAdapter(bbev) { @Override public void onAnimationEnd(Animator animation) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java index e732a0354806..8305fa6b0fbf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java @@ -47,4 +47,8 @@ public interface DesktopMode { default void addDesktopGestureExclusionRegionListener(Consumer<Region> listener, Executor callbackExecutor) { } + + /** Called when requested to go to desktop mode from the current focused app. */ + void enterDesktop(int displayId); + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 11304ec587e7..6de5d741f436 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -21,6 +21,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context @@ -240,6 +241,43 @@ class DesktopTasksController( return desktopModeTaskRepository.getVisibleTaskCount(displayId) } + /** Enter desktop by using the focused task in given `displayId` */ + fun enterDesktop(displayId: Int) { + val allFocusedTasks = + shellTaskOrganizer.getRunningTasks(displayId).filter { taskInfo -> + taskInfo.isFocused && + (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN || + taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) && + taskInfo.activityType != ACTIVITY_TYPE_HOME + } + if (allFocusedTasks.isNotEmpty()) { + when (allFocusedTasks.size) { + 2 -> { + // Split-screen case where there are two focused tasks, then we find the child + // task to move to desktop. + val splitFocusedTask = findChildFocusedTask(allFocusedTasks) + moveToDesktop(splitFocusedTask) + } + 1 -> { + // Fullscreen case where we move the current focused task. + moveToDesktop(allFocusedTasks[0].taskId) + } + else -> { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: Cannot enter desktop expected less " + + "than 3 focused tasks but found " + allFocusedTasks.size + ) + } + } + } + } + + private fun findChildFocusedTask(allFocusedTasks: List<RunningTaskInfo>): RunningTaskInfo { + if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId) return allFocusedTasks[1] + return allFocusedTasks[0] + } + /** Move a task with given `taskId` to desktop */ fun moveToDesktop( taskId: Int, @@ -1012,6 +1050,12 @@ class DesktopTasksController( this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor) } } + + override fun enterDesktop(displayId: Int) { + mainExecutor.execute { + this@DesktopTasksController.enterDesktop(displayId) + } + } } /** The interface for calls from outside the host process. */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 63618f4d0673..cb64c52444ac 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -734,6 +734,46 @@ class DesktopTasksControllerTest : ShellTestCase() { shellExecutor.flushAll() verify(launchAdjacentController).launchAdjacentEnabled = true } + @Test + fun enterDesktop_fullscreenTaskIsMovedToDesktop() { + val task1 = setUpFullscreenTask() + val task2 = setUpFullscreenTask() + val task3 = setUpFullscreenTask() + + task1.isFocused = true + task2.isFocused = false + task3.isFocused = false + + controller.enterDesktop(DEFAULT_DISPLAY) + + val wct = getLatestMoveToDesktopWct() + assertThat(wct.changes[task1.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test + fun enterDesktop_splitScreenTaskIsMovedToDesktop() { + val task1 = setUpSplitScreenTask() + val task2 = setUpFullscreenTask() + val task3 = setUpFullscreenTask() + val task4 = setUpSplitScreenTask() + + task1.isFocused = true + task2.isFocused = false + task3.isFocused = false + task4.isFocused = true + + task4.parentTaskId = task1.taskId + + controller.enterDesktop(DEFAULT_DISPLAY) + + val wct = getLatestMoveToDesktopWct() + assertThat(wct.changes[task4.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(), + eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE) + ) + } private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createFreeformTask(displayId) diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 8748dab581bb..46f636e2ae7f 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -117,6 +117,10 @@ bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_cache return true; } +void AssetManager2::PresetApkAssets(ApkAssetsList apk_assets) { + BuildDynamicRefTable(apk_assets); +} + bool AssetManager2::SetApkAssets(std::initializer_list<ApkAssetsPtr> apk_assets, bool invalidate_caches) { return SetApkAssets(ApkAssetsList(apk_assets.begin(), apk_assets.size()), invalidate_caches); @@ -432,13 +436,18 @@ bool AssetManager2::ContainsAllocatedTable() const { return false; } -void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) { +void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations, + bool force_refresh) { int diff = 0; - if (configurations_.size() != configurations.size()) { + if (force_refresh) { diff = -1; } else { - for (int i = 0; i < configurations_.size(); i++) { - diff |= configurations_[i].diff(configurations[i]); + if (configurations_.size() != configurations.size()) { + diff = -1; + } else { + for (int i = 0; i < configurations_.size(); i++) { + diff |= configurations_[i].diff(configurations[i]); + } } } configurations_ = std::move(configurations); @@ -775,8 +784,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry( bool has_locale = false; if (result->config.locale == 0) { if (default_locale_ != 0) { - ResTable_config conf; - conf.locale = default_locale_; + ResTable_config conf = {.locale = default_locale_}; // Since we know conf has a locale and only a locale, match will tell us if that locale // matches has_locale = conf.match(config); diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index d9ff35b49e0a..17a8ba6c03bd 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -124,6 +124,9 @@ class AssetManager2 { // new resource IDs. bool SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches = true); bool SetApkAssets(std::initializer_list<ApkAssetsPtr> apk_assets, bool invalidate_caches = true); + // This one is an optimization - it skips all calculations for applying the currently set + // configuration, expecting a configuration update later with a forced refresh. + void PresetApkAssets(ApkAssetsList apk_assets); const ApkAssetsPtr& GetApkAssets(ApkAssetsCookie cookie) const; int GetApkAssetsCount() const { @@ -156,7 +159,7 @@ class AssetManager2 { // Sets/resets the configuration for this AssetManager. This will cause all // caches that are related to the configuration change to be invalidated. - void SetConfigurations(std::vector<ResTable_config> configurations); + void SetConfigurations(std::vector<ResTable_config> configurations, bool force_refresh = false); inline const std::vector<ResTable_config>& GetConfigurations() const { return configurations_; diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index 71f7926930fc..27ea15075682 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -378,10 +378,17 @@ static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) { break; case kAlpha_8_SkColorType: formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support(); - formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8_UNORM; - formatInfo.format = GL_RED; - formatInfo.type = GL_UNSIGNED_BYTE; - formatInfo.vkFormat = VK_FORMAT_R8_UNORM; + if (formatInfo.isSupported) { + formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8_UNORM; + formatInfo.format = GL_RED; + formatInfo.type = GL_UNSIGNED_BYTE; + formatInfo.vkFormat = VK_FORMAT_R8_UNORM; + } else { + formatInfo.type = GL_UNSIGNED_BYTE; + formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; + formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; + formatInfo.format = GL_RGBA; + } break; default: ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType()); diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 8344a86923ee..185436160349 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -147,11 +147,7 @@ sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info, } sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(const SkBitmap& bitmap) { -#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration - if (bitmap.colorType() == kAlpha_8_SkColorType && - !uirenderer::HardwareBitmapUploader::hasAlpha8Support()) { - return nullptr; - } +#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration return uirenderer::HardwareBitmapUploader::allocateHardwareBitmap(bitmap); #else return Bitmap::allocateHeapBitmap(bitmap.info()); diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java index 2795cfe4ba61..f05ea9c2684f 100644 --- a/media/java/android/media/audiofx/Visualizer.java +++ b/media/java/android/media/audiofx/Visualizer.java @@ -336,8 +336,9 @@ public class Visualizer { * This method must not be called when the Visualizer is enabled. * @param size requested capture size * @return {@link #SUCCESS} in case of success, - * {@link #ERROR_BAD_VALUE} in case of failure. - * @throws IllegalStateException + * {@link #ERROR_INVALID_OPERATION} if Visualizer effect enginer not enabled. + * @throws IllegalStateException if the effect is not in proper state. + * @throws IllegalArgumentException if the size parameter is invalid (out of supported range). */ public int setCaptureSize(int size) throws IllegalStateException { @@ -345,7 +346,13 @@ public class Visualizer { if (mState != STATE_INITIALIZED) { throw(new IllegalStateException("setCaptureSize() called in wrong state: "+mState)); } - return native_setCaptureSize(size); + + int ret = native_setCaptureSize(size); + if (ret == ERROR_BAD_VALUE) { + throw(new IllegalArgumentException("setCaptureSize to " + size + " failed")); + } + + return ret; } } diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index e92c7b3ba8b5..d17b341782ce 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -107,11 +107,15 @@ public abstract class MediaBrowserService extends Service { private final ServiceState mServiceState = new ServiceState(); + // Holds the connection record associated with the currently executing callback operation, if + // any. See getCurrentBrowserInfo for an example. Must only be accessed on mHandler. + @Nullable private ConnectionRecord mCurrentConnectionOnHandler; + /** * All the info about a connection. */ private static class ConnectionRecord implements IBinder.DeathRecipient { - public final MediaBrowserService service; + public final ServiceState serviceState; public final String pkg; public final int pid; public final int uid; @@ -121,9 +125,14 @@ public abstract class MediaBrowserService extends Service { public final HashMap<String, List<Pair<IBinder, Bundle>>> subscriptions = new HashMap<>(); ConnectionRecord( - MediaBrowserService service, String pkg, int pid, int uid, Bundle rootHints, - IMediaBrowserServiceCallbacks callbacks, BrowserRoot root) { - this.service = service; + ServiceState serviceState, + String pkg, + int pid, + int uid, + Bundle rootHints, + IMediaBrowserServiceCallbacks callbacks, + BrowserRoot root) { + this.serviceState = serviceState; this.pkg = pkg; this.pid = pid; this.uid = uid; @@ -134,12 +143,8 @@ public abstract class MediaBrowserService extends Service { @Override public void binderDied() { - service.mHandler.post(new Runnable() { - @Override - public void run() { - service.mServiceState.mConnections.remove(callbacks.asBinder()); - } - }); + serviceState.postOnHandler( + () -> serviceState.mConnections.remove(callbacks.asBinder())); } } @@ -461,12 +466,12 @@ public abstract class MediaBrowserService extends Service { * @see MediaBrowserService.BrowserRoot#EXTRA_SUGGESTED */ public final Bundle getBrowserRootHints() { - ConnectionRecord curConnection = mServiceState.mCurConnection; - if (curConnection == null) { + ConnectionRecord currentConnection = mCurrentConnectionOnHandler; + if (currentConnection == null) { throw new IllegalStateException("This should be called inside of onGetRoot or" + " onLoadChildren or onLoadItem methods"); } - return curConnection.rootHints == null ? null : new Bundle(curConnection.rootHints); + return currentConnection.rootHints == null ? null : new Bundle(currentConnection.rootHints); } /** @@ -477,12 +482,13 @@ public abstract class MediaBrowserService extends Service { * @see MediaSessionManager#isTrustedForMediaControl(RemoteUserInfo) */ public final RemoteUserInfo getCurrentBrowserInfo() { - ConnectionRecord curConnection = mServiceState.mCurConnection; - if (curConnection == null) { + ConnectionRecord currentConnection = mCurrentConnectionOnHandler; + if (currentConnection == null) { throw new IllegalStateException("This should be called inside of onGetRoot or" + " onLoadChildren or onLoadItem methods"); } - return new RemoteUserInfo(curConnection.pkg, curConnection.pid, curConnection.uid); + return new RemoteUserInfo( + currentConnection.pkg, currentConnection.pid, currentConnection.uid); } /** @@ -621,7 +627,6 @@ public abstract class MediaBrowserService extends Service { // Fields accessed from mHandler only. @NonNull private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>(); - @Nullable private ConnectionRecord mCurConnection; public void postOnHandler(Runnable runnable) { mHandler.post(runnable); @@ -704,9 +709,9 @@ public abstract class MediaBrowserService extends Service { // Temporarily sets a placeholder ConnectionRecord to make getCurrentBrowserInfo() work // in onGetRoot(). - mCurConnection = + mCurrentConnectionOnHandler = new ConnectionRecord( - /* service= */ MediaBrowserService.this, + /* serviceState= */ this, pkg, pid, uid, @@ -714,7 +719,7 @@ public abstract class MediaBrowserService extends Service { callbacks, /* root= */ null); BrowserRoot root = onGetRoot(pkg, uid, rootHints); - mCurConnection = null; + mCurrentConnectionOnHandler = null; // If they didn't return something, don't allow this client. if (root == null) { @@ -728,7 +733,7 @@ public abstract class MediaBrowserService extends Service { try { ConnectionRecord connection = new ConnectionRecord( - /* service= */ MediaBrowserService.this, + /* serviceState= */ this, pkg, pid, uid, @@ -830,13 +835,13 @@ public abstract class MediaBrowserService extends Service { } }; - mCurConnection = connection; + mCurrentConnectionOnHandler = connection; if (options == null) { onLoadChildren(parentId, result); } else { onLoadChildren(parentId, result, options); } - mCurConnection = null; + mCurrentConnectionOnHandler = null; if (!result.isDone()) { throw new IllegalStateException( @@ -885,9 +890,9 @@ public abstract class MediaBrowserService extends Service { } }; - mCurConnection = connection; + mCurrentConnectionOnHandler = connection; onLoadItem(itemId, result); - mCurConnection = null; + mCurrentConnectionOnHandler = null; if (!result.isDone()) { throw new IllegalStateException( diff --git a/media/jni/audioeffect/Visualizer.cpp b/media/jni/audioeffect/Visualizer.cpp index 09c45ea97e9d..9ae5c991514a 100644 --- a/media/jni/audioeffect/Visualizer.cpp +++ b/media/jni/audioeffect/Visualizer.cpp @@ -25,7 +25,6 @@ #include <limits.h> #include <audio_utils/fixedfft.h> -#include <cutils/bitops.h> #include <utils/Thread.h> #include <android/content/AttributionSourceState.h> @@ -59,8 +58,8 @@ status_t Visualizer::set(int32_t priority, status_t status = AudioEffect::set( SL_IID_VISUALIZATION, nullptr, priority, cbf, user, sessionId, io, device, probe); if (status == NO_ERROR || status == ALREADY_EXISTS) { - initCaptureSize(); - initSampleRate(); + status = initCaptureSize(); + if (status == NO_ERROR) initSampleRate(); } return status; } @@ -152,9 +151,8 @@ status_t Visualizer::setCaptureCallBack(capture_cbk_t cbk, void* user, uint32_t status_t Visualizer::setCaptureSize(uint32_t size) { - if (size > VISUALIZER_CAPTURE_SIZE_MAX || - size < VISUALIZER_CAPTURE_SIZE_MIN || - popcount(size) != 1) { + if (!isCaptureSizeValid(size)) { + ALOGE("%s with invalid capture size %u from HAL", __func__, size); return BAD_VALUE; } @@ -172,7 +170,7 @@ status_t Visualizer::setCaptureSize(uint32_t size) *((int32_t *)p->data + 1)= size; status_t status = setParameter(p); - ALOGV("setCaptureSize size %d status %d p->status %d", size, status, p->status); + ALOGV("setCaptureSize size %u status %d p->status %d", size, status, p->status); if (status == NO_ERROR) { status = p->status; @@ -257,8 +255,8 @@ status_t Visualizer::getIntMeasurements(uint32_t type, uint32_t number, int32_t if ((type != MEASUREMENT_MODE_PEAK_RMS) // for peak+RMS measurement, the results are 2 int32_t values || (number != 2)) { - ALOGE("Cannot retrieve int measurements, MEASUREMENT_MODE_PEAK_RMS returns 2 ints, not %d", - number); + ALOGE("Cannot retrieve int measurements, MEASUREMENT_MODE_PEAK_RMS returns 2 ints, not %u", + number); return BAD_VALUE; } @@ -390,7 +388,7 @@ void Visualizer::periodicCapture() } } -uint32_t Visualizer::initCaptureSize() +status_t Visualizer::initCaptureSize() { uint32_t buf32[sizeof(effect_param_t) / sizeof(uint32_t) + 2]; effect_param_t *p = (effect_param_t *)buf32; @@ -405,14 +403,20 @@ uint32_t Visualizer::initCaptureSize() } uint32_t size = 0; - if (status == NO_ERROR) { - size = *((int32_t *)p->data + 1); + if (status != NO_ERROR) { + ALOGE("%s getParameter failed status %d", __func__, status); + return status; } - mCaptureSize = size; - ALOGV("initCaptureSize size %d status %d", mCaptureSize, status); + size = *((int32_t *)p->data + 1); + if (!isCaptureSizeValid(size)) { + ALOGE("%s with invalid capture size %u from HAL", __func__, size); + return BAD_VALUE; + } - return size; + mCaptureSize = size; + ALOGV("%s size %u status %d", __func__, mCaptureSize, status); + return NO_ERROR; } void Visualizer::initSampleRate() diff --git a/media/jni/audioeffect/Visualizer.h b/media/jni/audioeffect/Visualizer.h index b38c01f62cf1..26d58d0f56e9 100644 --- a/media/jni/audioeffect/Visualizer.h +++ b/media/jni/audioeffect/Visualizer.h @@ -20,6 +20,8 @@ #include <media/AudioEffect.h> #include <system/audio_effects/effect_visualizer.h> #include <utils/Thread.h> +#include <cstdint> +#include <cutils/bitops.h> #include "android/content/AttributionSourceState.h" /** @@ -170,8 +172,12 @@ private: status_t doFft(uint8_t *fft, uint8_t *waveform); void periodicCapture(); - uint32_t initCaptureSize(); + status_t initCaptureSize(); void initSampleRate(); + static constexpr bool isCaptureSizeValid(uint32_t size) { + return size <= VISUALIZER_CAPTURE_SIZE_MAX && size >= VISUALIZER_CAPTURE_SIZE_MIN && + popcount(size) == 1; + } Mutex mCaptureLock; uint32_t mCaptureRate = CAPTURE_RATE_DEF; diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 845a8f97db10..f111327ef619 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -206,9 +206,9 @@ package android.nfc.cardemulation { method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String); method public boolean removeAidsForService(android.content.ComponentName, String); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean); 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); diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl index 791bd8c9e6f4..65d0625f251e 100644 --- a/nfc/java/android/nfc/INfcCardEmulation.aidl +++ b/nfc/java/android/nfc/INfcCardEmulation.aidl @@ -30,7 +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 setDefaultToObserveModeForService(int userId, in android.content.ComponentName service, boolean enable); boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup); boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter); boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement); diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index 1f41b812164c..e681a8568300 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -338,19 +338,20 @@ public final class CardEmulation { } } /** - * Sets whether the system should default to observe mode or not when the service is in the - * foreground or the default payment service. The default is to not enable observe mode when - * a service either the foreground default service or the default payment service so not - * calling this method will preserve that behavior. + * Sets whether when this service becomes the preferred service, if the NFC stack + * should enable observe mode or disable observe mode. The default is to not enable observe + * mode when a service either the foreground default service or the default payment service so + * not calling this method will preserve that behavior. * * @param service The component name of the service - * @param enable Whether the servic should default to observe mode or not + * @param enable Whether the service 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) { + public boolean setDefaultToObserveModeForService(@NonNull ComponentName service, + boolean enable) { try { - return sService.setServiceObserveModeDefault(mContext.getUser().getIdentifier(), + return sService.setDefaultToObserveModeForService(mContext.getUser().getIdentifier(), service, enable); } catch (RemoteException e) { Log.e(TAG, "Failed to reach CardEmulationService."); @@ -359,9 +360,14 @@ public final class CardEmulation { } /** - * Register a polling loop filter for a HostApduService. - * @param service The HostApduService to register the filter for. - * @param pollingLoopFilter The filter to register. + * Register a polling loop filter (PLF) for a HostApduService. The PLF can be sequence of an + * even number of hexadecimal numbers (0-9, A-F or a-f). When non-standard polling loop frame + * matches this sequence exactly, it may be delivered to + * {@link HostApduService#processPollingFrames(List)} if this service is currently + * preferred or there are no other services registered for this filter. + * @param service The HostApduService to register the filter for + * @param pollingLoopFilter The filter to register + * @return true if the filter was registered, false otherwise */ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public boolean registerPollingLoopFilterForService(@NonNull ComponentName service, diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java index b5951e8e7927..9217e7012e7e 100644 --- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java @@ -75,6 +75,8 @@ import java.util.concurrent.TimeUnit; */ public class RescueParty { @VisibleForTesting + static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue"; + @VisibleForTesting static final int LEVEL_NONE = 0; @VisibleForTesting static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1; @@ -123,7 +125,7 @@ public class RescueParty { private static boolean isDisabled() { // Check if we're explicitly enabled for testing - if (CrashRecoveryProperties.enableRescueParty().orElse(false)) { + if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) { return false; } @@ -176,6 +178,29 @@ public class RescueParty { return CrashRecoveryProperties.attemptingReboot().orElse(false); } + protected static long getLastFactoryResetTimeMs() { + return CrashRecoveryProperties.lastFactoryResetTimeMs().orElse(0L); + } + + protected static int getMaxRescueLevelAttempted() { + return CrashRecoveryProperties.maxRescueLevelAttempted().orElse(LEVEL_NONE); + } + + protected static void setFactoryResetProperty(boolean value) { + CrashRecoveryProperties.attemptingFactoryReset(value); + } + protected static void setRebootProperty(boolean value) { + CrashRecoveryProperties.attemptingReboot(value); + } + + protected static void setLastFactoryResetTimeMs(long value) { + CrashRecoveryProperties.lastFactoryResetTimeMs(value); + } + + protected static void setMaxRescueLevelAttempted(int level) { + CrashRecoveryProperties.maxRescueLevelAttempted(level); + } + /** * Called when {@code SettingsProvider} has been published, which is a good * opportunity to reset any settings depending on our rescue level. @@ -432,7 +457,7 @@ public class RescueParty { case LEVEL_WARM_REBOOT: // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog // when device shutting down. - CrashRecoveryProperties.attemptingReboot(true); + setRebootProperty(true); runnable = () -> { try { PowerManager pm = context.getSystemService(PowerManager.class); @@ -454,9 +479,9 @@ public class RescueParty { if (isRebootPropertySet()) { break; } - CrashRecoveryProperties.attemptingFactoryReset(true); + setFactoryResetProperty(true); long now = System.currentTimeMillis(); - CrashRecoveryProperties.lastFactoryResetTimeMs(now); + setLastFactoryResetTimeMs(now); runnable = new Runnable() { @Override public void run() { @@ -515,10 +540,10 @@ public class RescueParty { private static void resetAllSettingsIfNecessary(Context context, int mode, int level) throws Exception { // No need to reset Settings again if they are already reset in the current level once. - if (CrashRecoveryProperties.maxRescueLevelAttempted().orElse(LEVEL_NONE) >= level) { + if (getMaxRescueLevelAttempted() >= level) { return; } - CrashRecoveryProperties.maxRescueLevelAttempted(level); + setMaxRescueLevelAttempted(level); // Try our best to reset all settings possible, and once finished // rethrow any exception that we encountered Exception res = null; @@ -733,7 +758,7 @@ public class RescueParty { * Will return {@code false} if a factory reset was already offered recently. */ private boolean shouldThrottleReboot() { - Long lastResetTime = CrashRecoveryProperties.lastFactoryResetTimeMs().orElse(0L); + Long lastResetTime = getLastFactoryResetTimeMs(); long now = System.currentTimeMillis(); long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG, DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN); diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp index 98a5a674fcdd..b646da44f0aa 100644 --- a/packages/PackageInstaller/Android.bp +++ b/packages/PackageInstaller/Android.bp @@ -59,69 +59,5 @@ android_app { lint: { error_checks: ["Recycle"], }, -} - -android_app { - name: "PackageInstaller_tablet", - defaults: ["platform_app_defaults"], - - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], - - certificate: "platform", - privileged: true, - platform_apis: false, - sdk_version: "system_current", - rename_resources_package: false, - overrides: ["PackageInstaller"], - - static_libs: [ - "xz-java", - "androidx.leanback_leanback", - "androidx.fragment_fragment", - "androidx.lifecycle_lifecycle-livedata", - "androidx.lifecycle_lifecycle-extensions", - "android.content.pm.flags-aconfig-java", - "android.os.flags-aconfig-java", - ], - aaptflags: ["--product tablet"], - - lint: { - error_checks: ["Recycle"], - }, -} - -android_app { - name: "PackageInstaller_tv", - defaults: ["platform_app_defaults"], - - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], - - certificate: "platform", - privileged: true, - platform_apis: false, - sdk_version: "system_current", - rename_resources_package: false, - overrides: ["PackageInstaller"], - - static_libs: [ - "xz-java", - "androidx.leanback_leanback", - "androidx.annotation_annotation", - "androidx.fragment_fragment", - "androidx.lifecycle_lifecycle-livedata", - "androidx.lifecycle_lifecycle-extensions", - "android.content.pm.flags-aconfig-java", - "android.os.flags-aconfig-java", - ], - aaptflags: ["--product tv"], - - lint: { - error_checks: ["Recycle"], - }, + generate_product_characteristics_rro: true, } diff --git a/packages/PackageInstaller/res/values-night/themes.xml b/packages/PackageInstaller/res/values-night/themes.xml index 18320f7626e9..a5b82b39344c 100644 --- a/packages/PackageInstaller/res/values-night/themes.xml +++ b/packages/PackageInstaller/res/values-night/themes.xml @@ -20,6 +20,9 @@ <style name="Theme.AlertDialogActivity" parent="@android:style/Theme.DeviceDefault.Dialog.Alert"> <item name="alertDialogStyle">@style/AlertDialog</item> + <item name="android:windowActionBar">false</item> + <item name="android:windowNoTitle">true</item> + <item name="android:windowAnimationStyle">@null</item> </style> </resources> diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index ec519ca61021..463e9be391b6 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.7.0-alpha01" + extra["jetpackComposeVersion"] = "1.7.0-alpha02" } subprojects { diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml index f6fbc02de3b0..fe378c27523c 100644 --- a/packages/SettingsLib/Spa/gradle/libs.versions.toml +++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml @@ -16,7 +16,7 @@ [versions] agp = "8.2.2" -compose-compiler = "1.5.8" +compose-compiler = "1.5.9" dexmaker-mockito = "2.28.3" jvm = "17" kotlin = "1.9.22" diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index 08a87973468b..2259bd74d56e 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -57,13 +57,13 @@ dependencies { api("androidx.slice:slice-builders:1.1.0-alpha02") api("androidx.slice:slice-core:1.1.0-alpha02") api("androidx.slice:slice-view:1.1.0-alpha02") - api("androidx.compose.material3:material3:1.2.0-rc01") + api("androidx.compose.material3:material3:1.2.0") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") api("androidx.lifecycle:lifecycle-livedata-ktx") api("androidx.lifecycle:lifecycle-runtime-compose") - api("androidx.navigation:navigation-compose:2.8.0-alpha01") + api("androidx.navigation:navigation-compose:2.8.0-alpha02") api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha") api("com.google.android.material:material:1.7.0-alpha03") debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion") diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 1092a16216f9..9588e502f28e 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1129,7 +1129,7 @@ <!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited --> <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging optimized</string> <!-- [CHAR_LIMIT=80] Label for battery charging future pause --> - <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging optimized</string> + <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> <string name="battery_info_status_unknown">Unknown</string> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java index cd35f67a1369..be480b941586 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java @@ -308,11 +308,8 @@ final class GenerationRegistry { final long token = proto.start(GenerationRegistryProto.BACKING_STORES); final int key = mKeyToBackingStoreMap.keyAt(i); proto.write(BackingStoreProto.KEY, key); - try { - proto.write(BackingStoreProto.BACKING_STORE_SIZE, - mKeyToBackingStoreMap.valueAt(i).size()); - } catch (IOException ignore) { - } + proto.write(BackingStoreProto.BACKING_STORE_SIZE, + mKeyToBackingStoreMap.valueAt(i).size()); proto.write(BackingStoreProto.NUM_CACHED_ENTRIES, mKeyToIndexMapMap.get(key).size()); final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key); @@ -357,10 +354,7 @@ final class GenerationRegistry { pw.print("_Backing store for type:"); pw.print(SettingsState.settingTypeToString( SettingsState.getTypeFromKey(key))); pw.print(" user:"); pw.print(SettingsState.getUserIdFromKey(key)); - try { - pw.print(" size:" + mKeyToBackingStoreMap.valueAt(i).size()); - } catch (IOException ignore) { - } + pw.print(" size:" + mKeyToBackingStoreMap.valueAt(i).size()); pw.println(" cachedEntries:" + mKeyToIndexMapMap.get(key).size()); final ArrayMap<String, Integer> indexMap = mKeyToIndexMapMap.get(key); final MemoryIntArray backingStore = getBackingStoreLocked(key, diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 2c285c861829..f2a71050a72c 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -29,6 +29,13 @@ flag { } flag { + name: "notification_color_update_logger" + namespace: "systemui" + description: "Enabled debug logging and dumping of notification color updates." + bug: "294347738" +} + +flag { name: "notifications_footer_view_refactor" namespace: "systemui" description: "Enables the refactored version of the footer view in the notification shade " @@ -67,6 +74,16 @@ flag { } flag { + name: "notifications_background_media_icons" + namespace: "systemui" + description: "Updates icons for media notifications in the background." + bug: "315143160" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "nssl_falsing_fix" namespace: "systemui" description: "Minor touch changes to prevent falsing errors in NSSL" @@ -295,7 +312,7 @@ flag { } flag { - name: "centralized_status_bar_dimens_refactor" + name: "centralized_status_bar_height_fix" namespace: "systemui" description: "Refactors shade header and keyguard status bar to read status bar dimens from a" " central place, instead of reading resources directly. This is to take into account display" @@ -453,3 +470,12 @@ flag { } } +flag { + name: "register_zen_mode_content_observer_background" + namespace: "systemui" + description: "Decide whether to register zen mode content observers in the background thread." + bug: "324515627" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt index 660fc5a98c03..44fe8831729c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt @@ -127,7 +127,7 @@ constructor( } with(notificationSection) { val splitShadeTopMargin: Dp = - if (Flags.centralizedStatusBarDimensRefactor()) { + if (Flags.centralizedStatusBarHeightFix()) { largeScreenHeaderHelper.getLargeScreenHeaderHeight().dp } else { dimensionResource( 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 c8fbad4f4eef..76e7c95f274a 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 @@ -348,6 +348,8 @@ internal class SceneGestureHandler( // Compute the destination scene (and therefore offset) to settle in. val offset = swipeTransition.dragOffset val distance = swipeTransition.distance + var targetScene: Scene + var targetOffset: Float if ( shouldCommitSwipe( offset, @@ -356,12 +358,24 @@ internal class SceneGestureHandler( wasCommitted = swipeTransition._currentScene == toScene, ) ) { - // Animate to the next scene - animateTo(targetScene = toScene, targetOffset = distance) + targetScene = toScene + targetOffset = distance } else { - // Animate to the initial scene - animateTo(targetScene = fromScene, targetOffset = 0f) + targetScene = fromScene + targetOffset = 0f + } + + if ( + targetScene != swipeTransition._currentScene && + !layoutState.canChangeScene(targetScene.key) + ) { + // We wanted to change to a new scene but we are not allowed to, so we animate back + // to the current scene. + targetScene = swipeTransition._currentScene + targetOffset = if (targetScene == fromScene) 0f else distance } + + animateTo(targetScene = targetScene, targetOffset = targetOffset) } else { // We are doing an overscroll animation between scenes. In this case, we can also start // from the idle position. 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 8c5a4720e7fb..08399ff03f63 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 @@ -232,7 +232,12 @@ internal class SceneTransitionLayoutImpl( scene(state.transitionState.currentScene).userActions[Back]?.let { result -> // TODO(b/290184746): Handle predictive back and use result.distance if // specified. - BackHandler { with(state) { coroutineScope.onChangeScene(result.toScene) } } + BackHandler { + val targetScene = result.toScene + if (state.canChangeScene(targetScene)) { + with(state) { coroutineScope.onChangeScene(targetScene) } + } + } } Box { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index a8da55101548..1cdba2d7d6d7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -101,13 +101,30 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState ): TransitionState.Transition? } -/** Return a [MutableSceneTransitionLayoutState] initially idle at [initialScene]. */ +/** + * Return a [MutableSceneTransitionLayoutState] initially idle at [initialScene]. + * + * @param initialScene the initial scene to which this state is initialized. + * @param transitions the [SceneTransitions] used when this state is transitioning between scenes. + * @param canChangeScene whether we can transition to the given scene. This is called when the user + * commits a transition to a new scene because of a [UserAction]. If [canChangeScene] returns + * `true`, then the gesture will be committed and we will animate to the other scene. Otherwise, + * the gesture will be cancelled and we will animate back to the current scene. + * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other + * [SceneTransitionLayoutState]s. + */ fun MutableSceneTransitionLayoutState( initialScene: SceneKey, transitions: SceneTransitions = SceneTransitions.Empty, + canChangeScene: (SceneKey) -> Boolean = { true }, stateLinks: List<StateLink> = emptyList(), ): MutableSceneTransitionLayoutState { - return MutableSceneTransitionLayoutStateImpl(initialScene, transitions, stateLinks) + return MutableSceneTransitionLayoutStateImpl( + initialScene, + transitions, + canChangeScene, + stateLinks, + ) } /** @@ -120,18 +137,32 @@ fun MutableSceneTransitionLayoutState( * This is called when the user commits a transition to a new scene because of a [UserAction], for * instance by triggering back navigation or by swiping to a new scene. * @param transitions the definition of the transitions used to animate a change of scene. + * @param canChangeScene whether we can transition to the given scene. This is called when the user + * commits a transition to a new scene because of a [UserAction]. If [canChangeScene] returns + * `true`, then [onChangeScene] will be called right afterwards with the same [SceneKey]. If it + * returns `false`, the user action will be cancelled and we will animate back to the current + * scene. + * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other + * [SceneTransitionLayoutState]s. */ @Composable fun updateSceneTransitionLayoutState( currentScene: SceneKey, onChangeScene: (SceneKey) -> Unit, transitions: SceneTransitions = SceneTransitions.Empty, + canChangeScene: (SceneKey) -> Boolean = { true }, stateLinks: List<StateLink> = emptyList(), ): SceneTransitionLayoutState { return remember { - HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene, stateLinks) + HoistedSceneTransitionLayoutScene( + currentScene, + transitions, + onChangeScene, + canChangeScene, + stateLinks, + ) } - .apply { update(currentScene, onChangeScene, transitions, stateLinks) } + .apply { update(currentScene, onChangeScene, canChangeScene, transitions, stateLinks) } } @Stable @@ -208,6 +239,9 @@ internal abstract class BaseSceneTransitionLayoutState( private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() + /** Whether we can transition to the given [scene]. */ + internal abstract fun canChangeScene(scene: SceneKey): Boolean + /** * Called when the [current scene][TransitionState.currentScene] should be changed to [scene]. * @@ -334,21 +368,26 @@ internal class HoistedSceneTransitionLayoutScene( initialScene: SceneKey, override var transitions: SceneTransitions, private var changeScene: (SceneKey) -> Unit, + private var canChangeScene: (SceneKey) -> Boolean, stateLinks: List<StateLink> = emptyList(), ) : BaseSceneTransitionLayoutState(initialScene, stateLinks) { private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED) - override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene(scene) + override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene) + + override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene.invoke(scene) @Composable fun update( currentScene: SceneKey, onChangeScene: (SceneKey) -> Unit, + canChangeScene: (SceneKey) -> Boolean, transitions: SceneTransitions, stateLinks: List<StateLink>, ) { SideEffect { this.changeScene = onChangeScene + this.canChangeScene = canChangeScene this.transitions = transitions this.stateLinks = stateLinks @@ -374,6 +413,7 @@ internal class HoistedSceneTransitionLayoutScene( internal class MutableSceneTransitionLayoutStateImpl( initialScene: SceneKey, override var transitions: SceneTransitions, + private val canChangeScene: (SceneKey) -> Boolean = { true }, stateLinks: List<StateLink> = emptyList(), ) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene, stateLinks) { override fun setTargetScene( @@ -388,6 +428,8 @@ internal class MutableSceneTransitionLayoutStateImpl( ) } + override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene) + override fun CoroutineScope.onChangeScene(scene: SceneKey) { setTargetScene(scene, coroutineScope = this) } 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 c91d29880ffb..fe53d5b45420 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 @@ -51,8 +51,13 @@ class SceneGestureHandlerTest { private class TestGestureScope( private val testScope: MonotonicClockTestScope, ) { + var canChangeScene: (SceneKey) -> Boolean = { true } private val layoutState = - MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions) + MutableSceneTransitionLayoutStateImpl( + SceneA, + EmptyTestTransitions, + canChangeScene = { canChangeScene(it) }, + ) val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC) val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA) @@ -890,4 +895,41 @@ class SceneGestureHandlerTest { ) assertThat(transitionState).isNotSameInstanceAs(firstTransition) } + + @Test + fun blockTransition() = runGestureTest { + assertIdle(SceneA) + + // Swipe up to scene B. + onDragStarted(overSlop = up(0.1f)) + assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB) + + // Block the transition when the user release their finger. + canChangeScene = { false } + onDragStopped(velocity = -velocityThreshold) + advanceUntilIdle() + assertIdle(SceneA) + } + + @Test + fun blockInterceptedTransition() = runGestureTest { + assertIdle(SceneA) + + // Swipe up to B. + onDragStarted(overSlop = up(0.1f)) + assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB) + onDragStopped(velocity = -velocityThreshold) + assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB) + + // Intercept the transition and swipe down back to scene A. + assertThat(sceneGestureHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue() + onDragStartedImmediately() + + // Block the transition when the user release their finger. + canChangeScene = { false } + onDragStopped(velocity = velocityThreshold) + + advanceUntilIdle() + assertIdle(SceneB) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index 55cfcc2c6d5b..ab551256cfc3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -210,6 +210,32 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : } @Test + fun shouldHandleTouchesOnDetach() = + testScope.runTest { + val shouldHandleTouches by collectLastValue(mUdfpsOverlayInteractor.shouldHandleTouches) + + // GIVEN view is attached + on the keyguard + mController.onViewAttached() + captureStatusBarStateListeners() + sendStatusBarStateChanged(StatusBarState.KEYGUARD) + whenever(mView.setPauseAuth(true)).thenReturn(true) + whenever(mView.unpausedAlpha).thenReturn(0) + + // WHEN panelViewExpansion changes to expanded + val job = mController.listenForBouncerExpansion(this) + keyguardBouncerRepository.setPrimaryShow(true) + keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) + runCurrent() + + mController.onViewDetached() + + // THEN UDFPS auth is paused and should not handle touches + assertThat(mController.shouldPauseAuth()).isTrue() + assertThat(shouldHandleTouches!!).isFalse() + + job.cancel() + } + @Test fun fadeFromDialogSuggestedAlpha() = testScope.runTest { // GIVEN view is attached and status bar expansion is 1f diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 0ebcf5608bff..63abc8f34668 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -27,7 +27,6 @@ import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.coroutines.collectValues import com.android.systemui.dock.DockManager import com.android.systemui.dock.DockManagerFake import com.android.systemui.flags.FakeFeatureFlags @@ -50,7 +49,6 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker -import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences @@ -59,7 +57,6 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -83,7 +80,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var launchAnimator: DialogTransitionAnimator @Mock private lateinit var devicePolicyManager: DevicePolicyManager - @Mock private lateinit var shadeInteractor: ShadeInteractor @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger private lateinit var underTest: KeyguardQuickAffordanceInteractor @@ -183,7 +179,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { underTest = KeyguardQuickAffordanceInteractor( keyguardInteractor = withDeps.keyguardInteractor, - shadeInteractor = shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, @@ -198,8 +193,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { backgroundDispatcher = testDispatcher, appContext = context, ) - - whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f)) } @Test @@ -346,25 +339,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { } @Test - fun quickAffordance_updateOncePerShadeExpansion() = - testScope.runTest { - val shadeExpansion = MutableStateFlow(0f) - whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion) - - val collectedValue by - collectValues( - underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START) - ) - - val initialSize = collectedValue.size - for (i in 0..10) { - shadeExpansion.value = i / 10f - } - - assertThat(collectedValue.size).isEqualTo(initialSize + 1) - } - - @Test fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() = testScope.runTest { whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 3455050d8542..3104842a9c2a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -18,8 +18,6 @@ package com.android.systemui.keyguard.ui.viewmodel -import android.content.pm.UserInfo -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -27,21 +25,20 @@ import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.communal.domain.interactor.communalSettingsInteractor +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor -import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED -import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.testKosmos -import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -85,35 +82,21 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @EnableFlags(FLAG_COMMUNAL_HUB) @Test - fun leftTransitionSceneKey_communalIsEnabled_communal() = + fun leftTransitionSceneKey_communalIsAvailable_communal() = testScope.runTest { - with(kosmos.fakeUserRepository) { - setUserInfos(listOf(PRIMARY_USER)) - setSelectedUserInfo(PRIMARY_USER) - } - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) - val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey) - assertThat(leftDestinationSceneKey).isEqualTo(SceneKey.Communal) - } - - @DisableFlags(FLAG_COMMUNAL_HUB) - @Test - fun leftTransitionSceneKey_communalIsDisabled_null() = - testScope.runTest { - with(kosmos.fakeUserRepository) { - setUserInfos(listOf(PRIMARY_USER)) - setSelectedUserInfo(PRIMARY_USER) - } - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey) assertThat(leftDestinationSceneKey).isNull() + + kosmos.setCommunalAvailable(true) + runCurrent() + assertThat(leftDestinationSceneKey).isEqualTo(SceneKey.Communal) } private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel { return LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, deviceEntryInteractor = kosmos.deviceEntryInteractor, - communalSettingsInteractor = kosmos.communalSettingsInteractor, + communalInteractor = kosmos.communalInteractor, longPress = KeyguardLongPressViewModel( interactor = mock(), @@ -121,9 +104,4 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { notifications = kosmos.notificationsPlaceholderViewModel, ) } - - private companion object { - val PRIMARY_USER = - UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN) - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 7c30c7eb7f50..9f89d346fd51 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -40,7 +40,7 @@ import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel import com.android.systemui.classifier.domain.interactor.falsingInteractor import com.android.systemui.classifier.falsingCollector -import com.android.systemui.communal.domain.interactor.communalSettingsInteractor +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor @@ -130,7 +130,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val sceneInteractor by lazy { kosmos.sceneInteractor } private val authenticationInteractor by lazy { kosmos.authenticationInteractor } private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } - private val communalSettingsInteractor by lazy { kosmos.communalSettingsInteractor } + private val communalInteractor by lazy { kosmos.communalInteractor } private val transitionState by lazy { MutableStateFlow<ObservableTransitionState>( @@ -155,7 +155,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, - communalSettingsInteractor = communalSettingsInteractor, + communalInteractor = communalInteractor, longPress = KeyguardLongPressViewModel( interactor = mock(), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt index f67c70ce783f..12473cb46793 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt @@ -62,36 +62,23 @@ class NotificationContentDescriptionTest : SysuiTestCase() { assertThat(description).isEqualTo(createDescriptionText(n, "")) } - @Test - fun nullNotification_descriptionIsAppName() { - val description = contentDescForNotification(context, null) - assertThat(description).isEqualTo(createDescriptionText(null, "")) - } - private fun createNotification( title: String? = null, text: String? = null, ticker: String? = null ): Notification = - Notification.Builder(context) + Notification.Builder(context, "channel") .setContentTitle(title) .setContentText(text) .setTicker(ticker) .build() private fun getTestAppName(): String { - return getAppName(createNotification("", "", "")) + return createNotification("", "", "").loadHeaderAppName(mContext) } - private fun getAppName(n: Notification?) = - n?.let { - val builder = Notification.Builder.recoverBuilder(context, it) - builder.loadHeaderAppName() - } - ?: "" - private fun createDescriptionText(n: Notification?, desc: String?): String { - val appName = getAppName(n) + val appName = n?.loadHeaderAppName(mContext) return context.getString(R.string.accessibility_desc_notification_icon, appName, desc) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 4c824c0d130a..87d25ddcc75c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -46,12 +46,12 @@ import android.content.Intent; import android.graphics.Region; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; @@ -75,8 +75,8 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @SmallTest -@RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper +@RunWith(AndroidJUnit4.class) public class BaseHeadsUpManagerTest extends SysuiTestCase { @Rule public MockitoRule rule = MockitoJUnit.rule(); diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt index fdac37b7a2c8..b0d66110deb5 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt @@ -21,8 +21,12 @@ package com.android.systemui.biometrics.shared.model * * If the user's fallback credential is owned by another profile user the [deviceCredentialOwnerId] * will differ from the user's [userId]. + * + * If prompt requests to use the user's parent profile for device credential, + * [userIdForPasswordEntry] might differ from the user's [userId]. */ data class BiometricUserInfo( val userId: Int, val deviceCredentialOwnerId: Int = userId, + val userIdForPasswordEntry: Int = userId, ) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt index f5603ed732a5..c3e781800f9f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt @@ -132,13 +132,13 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>( override fun onViewAttached() { dialogManager.registerListener(dialogListener) dumpManager.registerDumpable(dumpTag, this) - udfpsOverlayInteractor.setHandleTouches(shouldHandle = true) + udfpsOverlayInteractor.setHandleTouches(shouldHandle = !shouldPauseAuth()) } override fun onViewDetached() { dialogManager.unregisterListener(dialogListener) dumpManager.unregisterDumpable(dumpTag) - udfpsOverlayInteractor.setHandleTouches(shouldHandle = true) + udfpsOverlayInteractor.setHandleTouches(shouldHandle = !shouldPauseAuth()) } /** diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 018d92e4e932..ec54e4ce5e86 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -378,7 +378,7 @@ open class UdfpsKeyguardViewControllerLegacy( } } - override fun onViewDetached() { + public override fun onViewDetached() { super.onViewDetached() alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier) faceDetectRunning = false diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt index 191533c8f377..23afb7c71bec 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt @@ -7,9 +7,9 @@ import android.os.UserManager import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockscreenCredential import com.android.internal.widget.VerifyCredentialResponse -import com.android.systemui.res.R import com.android.systemui.biometrics.domain.model.BiometricPromptRequest import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.res.R import com.android.systemui.util.time.SystemClock import javax.inject.Inject import kotlinx.coroutines.delay @@ -29,6 +29,9 @@ interface CredentialInteractor { /** Get the effective user id (profile owner, if one exists) */ fun getCredentialOwnerOrSelfId(userId: Int): Int + /** Get parent user profile (if exists) */ + fun getParentProfileIdOrSelfId(userId: Int): Int + /** * Verifies a credential and returns a stream of results. * @@ -58,6 +61,9 @@ constructor( override fun getCredentialOwnerOrSelfId(userId: Int): Int = userManager.getCredentialOwnerProfile(userId) + override fun getParentProfileIdOrSelfId(userId: Int): Int = + userManager.getProfileParent(userId).id + override fun verifyCredential( request: BiometricPromptRequest.Credential, credential: LockscreenCredential, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt index 359e2e703ee6..e3facff9af12 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt @@ -75,20 +75,32 @@ constructor( PromptKind.Pin -> BiometricPromptRequest.Credential.Pin( info = promptInfo, - userInfo = userInfo(userId), + userInfo = + userInfo( + userId, + promptInfo.shouldUseParentProfileForDeviceCredential() + ), operationInfo = operationInfo(challenge) ) PromptKind.Pattern -> BiometricPromptRequest.Credential.Pattern( info = promptInfo, - userInfo = userInfo(userId), + userInfo = + userInfo( + userId, + promptInfo.shouldUseParentProfileForDeviceCredential() + ), operationInfo = operationInfo(challenge), stealthMode = credentialInteractor.isStealthModeActive(userId) ) PromptKind.Password -> BiometricPromptRequest.Credential.Password( info = promptInfo, - userInfo = userInfo(userId), + userInfo = + userInfo( + userId, + promptInfo.shouldUseParentProfileForDeviceCredential() + ), operationInfo = operationInfo(challenge) ) else -> null @@ -96,10 +108,17 @@ constructor( } .distinctUntilChanged() - private fun userInfo(userId: Int): BiometricUserInfo = + private fun userInfo( + userId: Int, + useParentProfileForDeviceCredential: Boolean + ): BiometricUserInfo = BiometricUserInfo( userId = userId, - deviceCredentialOwnerId = credentialInteractor.getCredentialOwnerOrSelfId(userId) + deviceCredentialOwnerId = credentialInteractor.getCredentialOwnerOrSelfId(userId), + userIdForPasswordEntry = + if (useParentProfileForDeviceCredential) + credentialInteractor.getParentProfileIdOrSelfId(userId) + else credentialInteractor.getCredentialOwnerOrSelfId(userId), ) private fun operationInfo(challenge: Long): BiometricOperationInfo = diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt index 0ad07ba924a0..4ed786be99c9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt @@ -12,12 +12,12 @@ import android.window.OnBackInvokedDispatcher import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R import com.android.systemui.biometrics.ui.CredentialPasswordView import com.android.systemui.biometrics.ui.CredentialView import com.android.systemui.biometrics.ui.IPinPad import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull @@ -42,7 +42,7 @@ object CredentialPasswordViewBinder { view.repeatWhenAttached { // the header info never changes - do it early val header = viewModel.header.first() - passwordField.setTextOperationUser(UserHandle.of(header.user.deviceCredentialOwnerId)) + passwordField.setTextOperationUser(UserHandle.of(header.user.userIdForPasswordEntry)) viewModel.inputFlags.firstOrNull()?.let { flags -> passwordField.inputType = flags } if (requestFocusForInput) { passwordField.requestFocus() diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt index 7150d69e130d..9876fe4482c0 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt @@ -222,13 +222,18 @@ constructor( val buffers = dumpManager.getLogBuffers() val tableBuffers = dumpManager.getTableLogBuffers() - targets.forEach { target -> - findTargetInCollection(target, dumpables, buffers, tableBuffers)?.dump(pw, args) - } + val matches = + if (args.matchAll) { + findAllMatchesInCollection(targets, dumpables, buffers, tableBuffers) + } else { + findBestMatchesInCollection(targets, dumpables, buffers, tableBuffers) + } + matches.forEach { it.dump(pw, args) } } else { if (args.listOnly) { val dumpables = dumpManager.getDumpables() val buffers = dumpManager.getLogBuffers() + val tableBuffers = dumpManager.getTableLogBuffers() pw.println("Dumpables:") listTargetNames(dumpables, pw) @@ -236,18 +241,23 @@ constructor( pw.println("Buffers:") listTargetNames(buffers, pw) + pw.println() + + pw.println("TableBuffers:") + listTargetNames(tableBuffers, pw) } else { pw.println("Nothing to dump :(") } } } + /** Finds the best match for a particular target */ private fun findTargetInCollection( target: String, dumpables: Collection<DumpableEntry>, logBuffers: Collection<LogBufferEntry>, tableBuffers: Collection<TableLogBufferEntry>, - ) = + ): DumpsysEntry? = sequence { findBestTargetMatch(dumpables, target)?.let { yield(it) } findBestTargetMatch(logBuffers, target)?.let { yield(it) } @@ -256,6 +266,31 @@ constructor( .sortedBy { it.name } .minByOrNull { it.name.length } + /** Finds the best match for each target, if any, in the order of the targets */ + private fun findBestMatchesInCollection( + targets: List<String>, + dumpables: Collection<DumpableEntry>, + logBuffers: Collection<LogBufferEntry>, + tableBuffers: Collection<TableLogBufferEntry>, + ): List<DumpsysEntry> = + targets.mapNotNull { target -> + findTargetInCollection(target, dumpables, logBuffers, tableBuffers) + } + + /** Finds all matches for any target, returning in the --list order. */ + private fun findAllMatchesInCollection( + targets: List<String>, + dumpables: Collection<DumpableEntry>, + logBuffers: Collection<LogBufferEntry>, + tableBuffers: Collection<TableLogBufferEntry>, + ): List<DumpsysEntry> = + sequence { + yieldAll(dumpables.filter { it.matchesAny(targets) }) + yieldAll(logBuffers.filter { it.matchesAny(targets) }) + yieldAll(tableBuffers.filter { it.matchesAny(targets) }) + } + .sortedBy { it.name }.toList() + private fun dumpConfig(pw: PrintWriter) { config.dump(pw, arrayOf()) } @@ -272,6 +307,11 @@ constructor( pw.println("etc.") pw.println() + pw.println("Print all matches, instead of the best match:") + pw.println("$ <invocation> --all <targets>") + pw.println("$ <invocation> --all Log") + pw.println() + pw.println("Special commands:") pw.println("$ <invocation> dumpables") pw.println("$ <invocation> buffers") @@ -325,9 +365,10 @@ constructor( "--help" -> { pArgs.command = "help" } - // This flag is passed as part of the proto dump in Bug reports, we can ignore - // it because this is our default behavior. - "-a" -> {} + "-a", + "--all" -> { + pArgs.matchAll = true + } else -> { throw ArgParseException("Unknown flag: $arg") } @@ -386,15 +427,19 @@ constructor( const val DUMPSYS_DUMPABLE_DIVIDER = "----------------------------------------------------------------------------" + private fun DumpsysEntry.matches(target: String) = name.endsWith(target) + private fun DumpsysEntry.matchesAny(targets: Collection<String>) = + targets.any { matches(it) } + private fun findBestTargetMatch(c: Collection<DumpsysEntry>, target: String) = - c.asSequence().filter { it.name.endsWith(target) }.minByOrNull { it.name.length } + c.asSequence().filter { it.matches(target) }.minByOrNull { it.name.length } private fun findBestProtoTargetMatch( c: Collection<DumpableEntry>, target: String ): ProtoDumpable? = c.asSequence() - .filter { it.name.endsWith(target) } + .filter { it.matches(target) } .filter { it.dumpable is ProtoDumpable } .minByOrNull { it.name.length } ?.dumpable as? ProtoDumpable @@ -440,40 +485,34 @@ constructor( } /** - * Utility to write a [DumpableEntry] to the given [PrintWriter] in a - * dumpsys-appropriate format. + * Utility to write a [DumpableEntry] to the given [PrintWriter] in a dumpsys-appropriate + * format. */ private fun dumpDumpable( - entry: DumpableEntry, - pw: PrintWriter, - args: Array<String> = arrayOf(), - ) = pw.wrapSection(entry) { - entry.dumpable.dump(pw, args) - } + entry: DumpableEntry, + pw: PrintWriter, + args: Array<String> = arrayOf(), + ) = pw.wrapSection(entry) { entry.dumpable.dump(pw, args) } /** - * Utility to write a [LogBufferEntry] to the given [PrintWriter] in a - * dumpsys-appropriate format. + * Utility to write a [LogBufferEntry] to the given [PrintWriter] in a dumpsys-appropriate + * format. */ private fun dumpBuffer( - entry: LogBufferEntry, - pw: PrintWriter, - tailLength: Int = 0, - ) = pw.wrapSection(entry) { - entry.buffer.dump(pw, tailLength) - } + entry: LogBufferEntry, + pw: PrintWriter, + tailLength: Int = 0, + ) = pw.wrapSection(entry) { entry.buffer.dump(pw, tailLength) } /** * Utility to write a [TableLogBufferEntry] to the given [PrintWriter] in a * dumpsys-appropriate format. */ private fun dumpTableBuffer( - entry: TableLogBufferEntry, - pw: PrintWriter, - args: Array<String> = arrayOf(), - ) = pw.wrapSection(entry) { - entry.table.dump(pw, args) - } + entry: TableLogBufferEntry, + pw: PrintWriter, + args: Array<String> = arrayOf(), + ) = pw.wrapSection(entry) { entry.table.dump(pw, args) } /** * Zero-arg utility to write a [DumpsysEntry] to the given [PrintWriter] in a @@ -513,6 +552,7 @@ private class ParsedArgs(val rawArgs: Array<String>, val nonFlagArgs: List<Strin var tailLength: Int = 0 var command: String? = null var listOnly = false + var matchAll = false var proto = false } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 8eb1a50086c6..b0a38811cdfc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -46,7 +46,6 @@ import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAfforda import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserTracker -import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController @@ -56,7 +55,6 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -68,7 +66,6 @@ class KeyguardQuickAffordanceInteractor @Inject constructor( private val keyguardInteractor: KeyguardInteractor, - private val shadeInteractor: ShadeInteractor, private val lockPatternUtils: LockPatternUtils, private val keyguardStateController: KeyguardStateController, private val userTracker: UserTracker, @@ -103,10 +100,9 @@ constructor( quickAffordanceAlwaysVisible(position), keyguardInteractor.isDozing, keyguardInteractor.isKeyguardShowing, - shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(), biometricSettingsRepository.isCurrentUserInLockdown, - ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown -> - if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) { + ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown -> + if (!isDozing && isKeyguardShowing && !isUserInLockdown) { affordance } else { KeyguardQuickAffordanceModel.Hidden 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 218af2994f4a..6a3b920f9692 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 @@ -24,7 +24,7 @@ import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP -import com.android.systemui.Flags.centralizedStatusBarDimensRefactor +import com.android.systemui.Flags.centralizedStatusBarHeightFix import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R @@ -80,7 +80,7 @@ constructor( val useLargeScreenHeader = context.resources.getBoolean(R.bool.config_use_large_screen_shade_header) val marginTopLargeScreen = - if (centralizedStatusBarDimensRefactor()) { + if (centralizedStatusBarHeightFix()) { largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight() } else { context.resources.getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index fa185570e7fc..9afe8fcd93d0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -16,7 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor @@ -36,7 +36,7 @@ class LockscreenSceneViewModel constructor( @Application applicationScope: CoroutineScope, deviceEntryInteractor: DeviceEntryInteractor, - communalSettingsInteractor: CommunalSettingsInteractor, + communalInteractor: CommunalInteractor, val longPress: KeyguardLongPressViewModel, val notifications: NotificationsPlaceholderViewModel, ) { @@ -56,7 +56,7 @@ constructor( /** The key of the scene we should switch to when swiping left. */ val leftDestinationSceneKey: StateFlow<SceneKey?> = - communalSettingsInteractor.isCommunalEnabled + communalInteractor.isCommunalAvailable .map { available -> if (available) SceneKey.Communal else null } .stateIn( scope = applicationScope, diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index c0d964405ab5..4ee2db796aef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -18,7 +18,7 @@ package com.android.systemui.qs; import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; -import static com.android.systemui.Flags.centralizedStatusBarDimensRefactor; +import static com.android.systemui.Flags.centralizedStatusBarHeightFix; import android.content.Context; import android.graphics.Canvas; @@ -194,7 +194,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { int topPadding = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext); if (!LargeScreenUtils.shouldUseLargeScreenShadeHeader(mContext.getResources())) { topPadding = - centralizedStatusBarDimensRefactor() + centralizedStatusBarHeightFix() ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(mContext) : mContext.getResources() .getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 1b504a81a651..24b2d8a1d7da 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -52,7 +52,7 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; +import com.android.systemui.qs.tiles.dialog.InternetDialogManager; import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.connectivity.IconState; import com.android.systemui.statusbar.connectivity.MobileDataIndicators; @@ -83,7 +83,7 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> { private int mLastTileState = LAST_STATE_UNKNOWN; protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback(); - private final InternetDialogFactory mInternetDialogFactory; + private final InternetDialogManager mInternetDialogManager; final Handler mHandler; @Inject @@ -99,11 +99,11 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> { QSLogger qsLogger, NetworkController networkController, AccessPointController accessPointController, - InternetDialogFactory internetDialogFactory + InternetDialogManager internetDialogManager ) { super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); - mInternetDialogFactory = internetDialogFactory; + mInternetDialogManager = internetDialogManager; mHandler = mainHandler; mController = networkController; mAccessPointController = accessPointController; @@ -125,7 +125,7 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> { @Override protected void handleClick(@Nullable View view) { - mHandler.post(() -> mInternetDialogFactory.create(true, + mHandler.post(() -> mInternetDialogManager.create(true, mAccessPointController.canConfigMobileData(), mAccessPointController.canConfigWifi(), view)); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt index 13271c32c52f..357743bc2bd7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt @@ -33,7 +33,7 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl -import com.android.systemui.qs.tiles.dialog.InternetDialogFactory +import com.android.systemui.qs.tiles.dialog.InternetDialogManager import com.android.systemui.res.R import com.android.systemui.statusbar.connectivity.AccessPointController import com.android.systemui.statusbar.pipeline.shared.ui.binder.InternetTileBinder @@ -44,18 +44,18 @@ import javax.inject.Inject class InternetTileNewImpl @Inject constructor( - host: QSHost, - uiEventLogger: QsEventLogger, - @Background backgroundLooper: Looper, - @Main private val mainHandler: Handler, - falsingManager: FalsingManager, - metricsLogger: MetricsLogger, - statusBarStateController: StatusBarStateController, - activityStarter: ActivityStarter, - qsLogger: QSLogger, - viewModel: InternetTileViewModel, - private val internetDialogFactory: InternetDialogFactory, - private val accessPointController: AccessPointController, + host: QSHost, + uiEventLogger: QsEventLogger, + @Background backgroundLooper: Looper, + @Main private val mainHandler: Handler, + falsingManager: FalsingManager, + metricsLogger: MetricsLogger, + statusBarStateController: StatusBarStateController, + activityStarter: ActivityStarter, + qsLogger: QSLogger, + viewModel: InternetTileViewModel, + private val internetDialogManager: InternetDialogManager, + private val accessPointController: AccessPointController, ) : QSTileImpl<QSTile.BooleanState>( host, @@ -86,7 +86,7 @@ constructor( override fun handleClick(view: View?) { mainHandler.post { - internetDialogFactory.create( + internetDialogManager.create( aboveStatusBar = true, accessPointController.canConfigMobileData(), accessPointController.canConfigWifi(), diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java index 03e0c1eaf3e2..0dd0a60b128a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java @@ -61,7 +61,6 @@ import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.Prefs; import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; import com.android.systemui.animation.DialogTransitionAnimator; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.res.R; @@ -72,23 +71,30 @@ import com.android.wifitrackerlib.WifiEntry; import java.util.List; import java.util.concurrent.Executor; -import javax.inject.Inject; +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; /** * Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks. */ -@SysUISingleton -public class InternetDialog extends SystemUIDialog implements - InternetDialogController.InternetDialogCallback, Window.Callback { +public class InternetDialogDelegate implements + SystemUIDialog.Delegate, + InternetDialogController.InternetDialogCallback { private static final String TAG = "InternetDialog"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - static final long PROGRESS_DELAY_MS = 1500L; + private static final String ABOVE_STATUS_BAR = "above_status_bar"; + private static final String CAN_CONFIG_MOBILE_DATA = "can_config_mobile_data"; + private static final String CAN_CONFIG_WIFI = "can_config_wifi"; + static final int MAX_NETWORK_COUNT = 4; private final Handler mHandler; private final Executor mBackgroundExecutor; private final DialogTransitionAnimator mDialogTransitionAnimator; + private final boolean mAboveStatusBar; + private final SystemUIDialog.Factory mSystemUIDialogFactory; @VisibleForTesting protected InternetAdapter mAdapter; @@ -97,19 +103,16 @@ public class InternetDialog extends SystemUIDialog implements @VisibleForTesting protected boolean mCanConfigWifi; - private InternetDialogFactory mInternetDialogFactory; - private SubscriptionManager mSubscriptionManager; + private final InternetDialogManager mInternetDialogManager; private TelephonyManager mTelephonyManager; @Nullable private AlertDialog mAlertDialog; - private UiEventLogger mUiEventLogger; - private Context mContext; - private InternetDialogController mInternetDialogController; + private final UiEventLogger mUiEventLogger; + private final InternetDialogController mInternetDialogController; private TextView mInternetDialogTitle; private TextView mInternetDialogSubTitle; private View mDivider; private ProgressBar mProgressBar; - private LinearLayout mInternetDialogLayout; private LinearLayout mConnectedWifListLayout; private LinearLayout mMobileNetworkLayout; private LinearLayout mSecondaryMobileNetworkLayout; @@ -127,8 +130,6 @@ public class InternetDialog extends SystemUIDialog implements private ImageView mSignalIcon; private TextView mMobileTitleText; private TextView mMobileSummaryText; - private TextView mSecondaryMobileTitleText; - private TextView mSecondaryMobileSummaryText; private TextView mAirplaneModeSummaryText; private Switch mMobileDataToggle; private View mMobileToggleDivider; @@ -139,12 +140,12 @@ public class InternetDialog extends SystemUIDialog implements protected Button mShareWifiButton; private Button mAirplaneModeButton; private Drawable mBackgroundOn; - private KeyguardStateController mKeyguard; + private final KeyguardStateController mKeyguard; @Nullable private Drawable mBackgroundOff = null; - private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private boolean mCanConfigMobileData; - private boolean mCanChangeWifiState; + private int mDefaultDataSubId; + private final boolean mCanConfigMobileData; + private final boolean mCanChangeWifiState; // Wi-Fi entries private int mWifiNetworkHeight; @Nullable @@ -157,26 +158,41 @@ public class InternetDialog extends SystemUIDialog implements // Wi-Fi scanning progress bar protected boolean mIsProgressBarVisible; - - @Inject - public InternetDialog(Context context, InternetDialogFactory internetDialogFactory, - InternetDialogController internetDialogController, boolean canConfigMobileData, - boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger, + private SystemUIDialog mDialog; + + @AssistedFactory + public interface Factory { + InternetDialogDelegate create( + @Assisted(ABOVE_STATUS_BAR) boolean aboveStatusBar, + @Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigMobileData, + @Assisted(CAN_CONFIG_WIFI) boolean canConfigWifi); + } + + @AssistedInject + public InternetDialogDelegate( + Context context, + InternetDialogManager internetDialogManager, + InternetDialogController internetDialogController, + @Assisted(ABOVE_STATUS_BAR) boolean canConfigMobileData, + @Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigWifi, + @Assisted(CAN_CONFIG_WIFI) boolean aboveStatusBar, + UiEventLogger uiEventLogger, DialogTransitionAnimator dialogTransitionAnimator, - @Main Handler handler, @Background Executor executor, - KeyguardStateController keyguardStateController) { - super(context); + @Main Handler handler, + @Background Executor executor, + KeyguardStateController keyguardStateController, + SystemUIDialog.Factory systemUIDialogFactory) { + mAboveStatusBar = aboveStatusBar; + mSystemUIDialogFactory = systemUIDialogFactory; if (DEBUG) { Log.d(TAG, "Init InternetDialog"); } // Save the context that is wrapped with our theme. - mContext = getContext(); mHandler = handler; mBackgroundExecutor = executor; - mInternetDialogFactory = internetDialogFactory; + mInternetDialogManager = internetDialogManager; mInternetDialogController = internetDialogController; - mSubscriptionManager = mInternetDialogController.getSubscriptionManager(); mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId(); mTelephonyManager = mInternetDialogController.getTelephonyManager(); mCanConfigMobileData = canConfigMobileData; @@ -187,31 +203,42 @@ public class InternetDialog extends SystemUIDialog implements mUiEventLogger = uiEventLogger; mDialogTransitionAnimator = dialogTransitionAnimator; mAdapter = new InternetAdapter(mInternetDialogController); - if (!aboveStatusBar) { - getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); + } + + @Override + public SystemUIDialog createDialog() { + SystemUIDialog dialog = mSystemUIDialogFactory.create(this); + if (!mAboveStatusBar) { + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); + } + + if (mDialog != null) { + mDialog.dismiss(); } + mDialog = dialog; + + return dialog; } @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) { if (DEBUG) { Log.d(TAG, "onCreate"); } + Context context = dialog.getContext(); mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW); - mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog, - null); + mDialogView = LayoutInflater.from(context).inflate( + R.layout.internet_connectivity_dialog, null); mDialogView.setAccessibilityPaneTitle( - mContext.getText(R.string.accessibility_desc_quick_settings)); - final Window window = getWindow(); + context.getText(R.string.accessibility_desc_quick_settings)); + final Window window = dialog.getWindow(); window.setContentView(mDialogView); window.setWindowAnimations(R.style.Animation_InternetDialog); - mWifiNetworkHeight = mContext.getResources() + mWifiNetworkHeight = context.getResources() .getDimensionPixelSize(R.dimen.internet_dialog_wifi_network_height); - mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog); mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title); mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle); mDivider = mDialogView.requireViewById(R.id.divider); @@ -239,20 +266,20 @@ public class InternetDialog extends SystemUIDialog implements mMobileToggleDivider = mDialogView.requireViewById(R.id.mobile_toggle_divider); mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle); mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle); - mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on); + mBackgroundOn = context.getDrawable(R.drawable.settingslib_switch_bar_bg_on); mInternetDialogTitle.setText(getDialogTitleText()); mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); - mBackgroundOff = mContext.getDrawable(R.drawable.internet_dialog_selected_effect); - setOnClickListener(); + mBackgroundOff = context.getDrawable(R.drawable.internet_dialog_selected_effect); + setOnClickListener(dialog); mTurnWifiOnLayout.setBackground(null); mAirplaneModeButton.setVisibility( mInternetDialogController.isAirplaneModeEnabled() ? View.VISIBLE : View.GONE); - mWifiRecyclerView.setLayoutManager(new LinearLayoutManager(mContext)); + mWifiRecyclerView.setLayoutManager(new LinearLayoutManager(context)); mWifiRecyclerView.setAdapter(mAdapter); } @Override - public void start() { + public void onStart(SystemUIDialog dialog) { if (DEBUG) { Log.d(TAG, "onStart"); } @@ -273,7 +300,7 @@ public class InternetDialog extends SystemUIDialog implements } @Override - public void stop() { + public void onStop(SystemUIDialog dialog) { if (DEBUG) { Log.d(TAG, "onStop"); } @@ -288,7 +315,7 @@ public class InternetDialog extends SystemUIDialog implements mShareWifiButton.setOnClickListener(null); mAirplaneModeButton.setOnClickListener(null); mInternetDialogController.onStop(); - mInternetDialogFactory.destroyDialog(); + mInternetDialogManager.destroyDialog(); } @Override @@ -296,8 +323,11 @@ public class InternetDialog extends SystemUIDialog implements if (DEBUG) { Log.d(TAG, "dismissDialog"); } - mInternetDialogFactory.destroyDialog(); - dismiss(); + mInternetDialogManager.destroyDialog(); + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } } /** @@ -334,11 +364,11 @@ public class InternetDialog extends SystemUIDialog implements updateWifiScanNotify(isWifiEnabled, isWifiScanEnabled, isDeviceLocked); } - private void setOnClickListener() { + private void setOnClickListener(SystemUIDialog dialog) { mMobileNetworkLayout.setOnClickListener(v -> { int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId(); if (autoSwitchNonDdsSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - showTurnOffAutoDataSwitchDialog(autoSwitchNonDdsSubId); + showTurnOffAutoDataSwitchDialog(dialog, autoSwitchNonDdsSubId); } mInternetDialogController.connectCarrierNetwork(); }); @@ -346,10 +376,10 @@ public class InternetDialog extends SystemUIDialog implements boolean isChecked = mMobileDataToggle.isChecked(); if (!isChecked && shouldShowMobileDialog()) { mMobileDataToggle.setChecked(true); - showTurnOffMobileDialog(); + showTurnOffMobileDialog(dialog); } else if (mInternetDialogController.isMobileDataEnabled() != isChecked) { - mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId, - isChecked, false); + mInternetDialogController.setMobileDataEnabled( + dialog.getContext(), mDefaultDataSubId, isChecked, false); } }); mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi); @@ -359,7 +389,7 @@ public class InternetDialog extends SystemUIDialog implements if (mInternetDialogController.isWifiEnabled() == isChecked) return; mInternetDialogController.setWifiEnabled(isChecked); }); - mDoneButton.setOnClickListener(v -> dismiss()); + mDoneButton.setOnClickListener(v -> dialog.dismiss()); mShareWifiButton.setOnClickListener(v -> { if (mInternetDialogController.mayLaunchShareWifiSettings(mConnectedWifiEntry)) { mUiEventLogger.log(InternetDialogEvent.SHARE_WIFI_QS_BUTTON_CLICKED); @@ -378,6 +408,14 @@ public class InternetDialog extends SystemUIDialog implements private void setMobileDataLayout(boolean activeNetworkIsCellular, boolean isCarrierNetworkActive) { + + if (mDialog != null) { + setMobileDataLayout(mDialog, activeNetworkIsCellular, isCarrierNetworkActive); + } + } + + private void setMobileDataLayout(SystemUIDialog dialog, boolean activeNetworkIsCellular, + boolean isCarrierNetworkActive) { boolean isNetworkConnected = activeNetworkIsCellular || isCarrierNetworkActive; // 1. Mobile network should be gone if airplane mode ON or the list of active // subscriptionId is null. @@ -431,19 +469,19 @@ public class InternetDialog extends SystemUIDialog implements if (stub != null) { stub.inflate(); } - mSecondaryMobileNetworkLayout = findViewById(R.id.secondary_mobile_network_layout); + mSecondaryMobileNetworkLayout = dialog.findViewById( + R.id.secondary_mobile_network_layout); mSecondaryMobileNetworkLayout.setOnClickListener( this::onClickConnectedSecondarySub); mSecondaryMobileNetworkLayout.setBackground(mBackgroundOn); - mSecondaryMobileTitleText = mDialogView.requireViewById( + TextView mSecondaryMobileTitleText = mDialogView.requireViewById( R.id.secondary_mobile_title); mSecondaryMobileTitleText.setText(getMobileNetworkTitle(autoSwitchNonDdsSubId)); mSecondaryMobileTitleText.setTextAppearance( R.style.TextAppearance_InternetDialog_Active); - mSecondaryMobileSummaryText = - mDialogView.requireViewById(R.id.secondary_mobile_summary); + TextView mSecondaryMobileSummaryText = mDialogView.requireViewById(R.id.secondary_mobile_summary); summary = getMobileNetworkSummary(autoSwitchNonDdsSubId); if (!TextUtils.isEmpty(summary)) { mSecondaryMobileSummaryText.setText( @@ -465,7 +503,7 @@ public class InternetDialog extends SystemUIDialog implements ImageView mSecondaryMobileSettingsIcon = mDialogView.requireViewById(R.id.secondary_settings_icon); mSecondaryMobileSettingsIcon.setColorFilter( - mContext.getColor(R.color.connected_network_primary_color)); + dialog.getContext().getColor(R.color.connected_network_primary_color)); // set secondary visual for default data sub mMobileNetworkLayout.setBackground(mBackgroundOff); @@ -473,7 +511,7 @@ public class InternetDialog extends SystemUIDialog implements mMobileSummaryText.setTextAppearance( R.style.TextAppearance_InternetDialog_Secondary); mSignalIcon.setColorFilter( - mContext.getColor(R.color.connected_network_secondary_color)); + dialog.getContext().getColor(R.color.connected_network_secondary_color)); } else { mMobileNetworkLayout.setBackground( isNetworkConnected ? mBackgroundOn : mBackgroundOff); @@ -491,7 +529,8 @@ public class InternetDialog extends SystemUIDialog implements // Set airplane mode to the summary for carrier network if (mInternetDialogController.isAirplaneModeEnabled()) { mAirplaneModeSummaryText.setVisibility(View.VISIBLE); - mAirplaneModeSummaryText.setText(mContext.getText(R.string.airplane_mode)); + mAirplaneModeSummaryText.setText( + dialog.getContext().getText(R.string.airplane_mode)); mAirplaneModeSummaryText.setTextAppearance(secondaryRes); } else { mAirplaneModeSummaryText.setVisibility(View.GONE); @@ -523,7 +562,7 @@ public class InternetDialog extends SystemUIDialog implements @MainThread private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) { - if (!isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) { + if (mDialog == null || !isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) { mConnectedWifListLayout.setVisibility(View.GONE); mShareWifiButton.setVisibility(View.GONE); return; @@ -534,7 +573,7 @@ public class InternetDialog extends SystemUIDialog implements mConnectedWifiIcon.setImageDrawable( mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry)); mWifiSettingsIcon.setColorFilter( - mContext.getColor(R.color.connected_network_primary_color)); + mDialog.getContext().getColor(R.color.connected_network_primary_color)); if (mInternetDialogController.getConfiguratorQrCodeGeneratorIntentOrNull( mConnectedWifiEntry) != null) { mShareWifiButton.setVisibility(View.VISIBLE); @@ -593,7 +632,7 @@ public class InternetDialog extends SystemUIDialog implements @MainThread private void updateWifiScanNotify(boolean isWifiEnabled, boolean isWifiScanEnabled, boolean isDeviceLocked) { - if (isWifiEnabled || !isWifiScanEnabled || isDeviceLocked) { + if (mDialog == null || isWifiEnabled || !isWifiScanEnabled || isDeviceLocked) { mWifiScanNotifyLayout.setVisibility(View.GONE); return; } @@ -602,7 +641,7 @@ public class InternetDialog extends SystemUIDialog implements AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION, mInternetDialogController::launchWifiScanningSetting); mWifiScanNotifyText.setText(AnnotationLinkSpan.linkify( - getContext().getText(R.string.wifi_scan_notify_message), linkInfo)); + mDialog.getContext().getText(R.string.wifi_scan_notify_message), linkInfo)); mWifiScanNotifyText.setMovementMethod(LinkMovementMethod.getInstance()); } mWifiScanNotifyLayout.setVisibility(View.VISIBLE); @@ -657,7 +696,10 @@ public class InternetDialog extends SystemUIDialog implements } private boolean shouldShowMobileDialog() { - boolean flag = Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, + if (mDialog == null) { + return false; + } + boolean flag = Prefs.getBoolean(mDialog.getContext(), QS_HAS_TURNED_OFF_MOBILE_DATA, false); if (mInternetDialogController.isMobileDataEnabled() && !flag) { return true; @@ -665,40 +707,42 @@ public class InternetDialog extends SystemUIDialog implements return false; } - private void showTurnOffMobileDialog() { + private void showTurnOffMobileDialog(SystemUIDialog dialog) { + Context context = dialog.getContext(); CharSequence carrierName = getMobileNetworkTitle(mDefaultDataSubId); boolean isInService = mInternetDialogController.isVoiceStateInService(mDefaultDataSubId); if (TextUtils.isEmpty(carrierName) || !isInService) { - carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier); + carrierName = context.getString(R.string.mobile_data_disable_message_default_carrier); } - mAlertDialog = new Builder(mContext) + mAlertDialog = new AlertDialog.Builder(context) .setTitle(R.string.mobile_data_disable_title) - .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName)) + .setMessage(context.getString(R.string.mobile_data_disable_message, carrierName)) .setNegativeButton(android.R.string.cancel, (d, w) -> { }) .setPositiveButton( com.android.internal.R.string.alert_windows_notification_turn_off_action, (d, w) -> { - mInternetDialogController.setMobileDataEnabled(mContext, + mInternetDialogController.setMobileDataEnabled(context, mDefaultDataSubId, false, false); mMobileDataToggle.setChecked(false); - Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true); + Prefs.putBoolean(context, QS_HAS_TURNED_OFF_MOBILE_DATA, true); }) .create(); mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); SystemUIDialog.setShowForAllUsers(mAlertDialog, true); SystemUIDialog.registerDismissListener(mAlertDialog); SystemUIDialog.setWindowOnTop(mAlertDialog, mKeyguard.isShowing()); - mDialogTransitionAnimator.showFromDialog(mAlertDialog, this, null, false); + mDialogTransitionAnimator.showFromDialog(mAlertDialog, dialog, null, false); } - private void showTurnOffAutoDataSwitchDialog(int subId) { + private void showTurnOffAutoDataSwitchDialog(SystemUIDialog dialog, int subId) { + Context context = dialog.getContext(); CharSequence carrierName = getMobileNetworkTitle(mDefaultDataSubId); if (TextUtils.isEmpty(carrierName)) { - carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier); + carrierName = context.getString(R.string.mobile_data_disable_message_default_carrier); } - mAlertDialog = new Builder(mContext) - .setTitle(mContext.getString(R.string.auto_data_switch_disable_title, carrierName)) + mAlertDialog = new AlertDialog.Builder(context) + .setTitle(context.getString(R.string.auto_data_switch_disable_title, carrierName)) .setMessage(R.string.auto_data_switch_disable_message) .setNegativeButton(R.string.auto_data_switch_dialog_negative_button, (d, w) -> { @@ -716,7 +760,7 @@ public class InternetDialog extends SystemUIDialog implements SystemUIDialog.setShowForAllUsers(mAlertDialog, true); SystemUIDialog.registerDismissListener(mAlertDialog); SystemUIDialog.setWindowOnTop(mAlertDialog, mKeyguard.isShowing()); - mDialogTransitionAnimator.showFromDialog(mAlertDialog, this, null, false); + mDialogTransitionAnimator.showFromDialog(mAlertDialog, dialog, null, false); } @Override @@ -802,11 +846,10 @@ public class InternetDialog extends SystemUIDialog implements } @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); + public void onWindowFocusChanged(SystemUIDialog dialog, boolean hasFocus) { if (mAlertDialog != null && !mAlertDialog.isShowing()) { - if (!hasFocus && isShowing()) { - dismiss(); + if (!hasFocus && dialog.isShowing()) { + dialog.dismiss(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt index c5f89834d5ce..2a177c791a03 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt @@ -15,64 +15,49 @@ */ package com.android.systemui.qs.tiles.dialog -import android.content.Context -import android.os.Handler import android.util.Log import android.view.View import com.android.internal.jank.InteractionJankMonitor -import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.statusbar.policy.KeyguardStateController -import java.util.concurrent.Executor +import com.android.systemui.statusbar.phone.SystemUIDialog import javax.inject.Inject private const val TAG = "InternetDialogFactory" private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) /** - * Factory to create [InternetDialog] objects. + * Factory to create [InternetDialogDelegate] objects. */ @SysUISingleton -class InternetDialogFactory @Inject constructor( - @Main private val handler: Handler, - @Background private val executor: Executor, - private val internetDialogController: InternetDialogController, - private val context: Context, - private val uiEventLogger: UiEventLogger, +class InternetDialogManager @Inject constructor( private val dialogTransitionAnimator: DialogTransitionAnimator, - private val keyguardStateController: KeyguardStateController + private val dialogFactory: InternetDialogDelegate.Factory ) { companion object { private const val INTERACTION_JANK_TAG = "internet" - var internetDialog: InternetDialog? = null + var dialog: SystemUIDialog? = null } - /** Creates a [InternetDialog]. The dialog will be animated from [view] if it is not null. */ + /** Creates a [InternetDialogDelegate]. The dialog will be animated from [view] if it is not null. */ fun create( aboveStatusBar: Boolean, canConfigMobileData: Boolean, canConfigWifi: Boolean, view: View? ) { - if (internetDialog != null) { + if (dialog != null) { if (DEBUG) { Log.d(TAG, "InternetDialog is showing, do not create it twice.") } return } else { - internetDialog = InternetDialog( - context, this, internetDialogController, - canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, - dialogTransitionAnimator, handler, - executor, keyguardStateController - ) + dialog = dialogFactory.create( + aboveStatusBar, canConfigMobileData, canConfigWifi).createDialog() if (view != null) { dialogTransitionAnimator.showFromView( - internetDialog!!, view, + dialog!!, view, animateBackgroundBoundsChange = true, cuj = DialogCuj( InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, @@ -80,7 +65,7 @@ class InternetDialogFactory @Inject constructor( ) ) } else { - internetDialog?.show() + dialog!!.show() } } } @@ -89,6 +74,6 @@ class InternetDialogFactory @Inject constructor( if (DEBUG) { Log.d(TAG, "destroyDialog") } - internetDialog = null + dialog = null } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 457b3d7e6217..29de688fa7bf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -27,7 +27,7 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.lifecycle.lifecycleScope -import com.android.systemui.Flags.centralizedStatusBarDimensRefactor +import com.android.systemui.Flags.centralizedStatusBarHeightFix import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main @@ -183,7 +183,7 @@ class NotificationsQSContainerController @Inject constructor( } private fun calculateLargeShadeHeaderHeight(): Int { - return if (centralizedStatusBarDimensRefactor()) { + return if (centralizedStatusBarHeightFix()) { largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight() } else { resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height) diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index f86c71b9508c..f7b9e4e35ef8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -20,7 +20,7 @@ package com.android.systemui.shade; import static android.view.WindowInsets.Type.ime; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; -import static com.android.systemui.Flags.centralizedStatusBarDimensRefactor; +import static com.android.systemui.Flags.centralizedStatusBarHeightFix; import static com.android.systemui.Flags.migrateClocksToBlueprint; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS; @@ -453,7 +453,7 @@ public class QuickSettingsController implements Dumpable { mUseLargeScreenShadeHeader = LargeScreenUtils.shouldUseLargeScreenShadeHeader(mPanelView.getResources()); mLargeScreenShadeHeaderHeight = - centralizedStatusBarDimensRefactor() + centralizedStatusBarHeightFix() ? mLargeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight() : mResources.getDimensionPixelSize( R.dimen.large_screen_shade_header_height); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index a66bacd237be..df9c57c13732 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -38,7 +38,7 @@ import androidx.core.view.doOnLayout import com.android.app.animation.Interpolators import com.android.settingslib.Utils import com.android.systemui.Dumpable -import com.android.systemui.Flags.centralizedStatusBarDimensRefactor +import com.android.systemui.Flags.centralizedStatusBarHeightFix import com.android.systemui.animation.ShadeInterpolation import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController @@ -433,7 +433,7 @@ constructor( changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints() } - if (centralizedStatusBarDimensRefactor()) { + if (centralizedStatusBarHeightFix()) { view.setPadding(view.paddingLeft, sbInsets.top, view.paddingRight, view.paddingBottom) } view.updateAllConstraints(changes) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index ffb11dd3cf92..ca19f71bd391 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -177,6 +177,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_CONFIRM_IMMERSIVE_PROMPT = 77 << MSG_SHIFT; private static final int MSG_IMMERSIVE_CHANGED = 78 << MSG_SHIFT; private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT; + private static final int MSG_ENTER_DESKTOP = 80 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1; @@ -520,6 +521,11 @@ public class CommandQueue extends IStatusBar.Stub implements * @see IStatusBar#immersiveModeChanged */ default void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {} + + /** + * @see IStatusBar#enterDesktop(int) + */ + default void enterDesktop(int displayId) {} } @VisibleForTesting @@ -1420,6 +1426,13 @@ public class CommandQueue extends IStatusBar.Stub implements mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget(); } + @Override + public void enterDesktop(int displayId) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = displayId; + mHandler.obtainMessage(MSG_ENTER_DESKTOP, args).sendToTarget(); + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -1914,6 +1927,13 @@ public class CommandQueue extends IStatusBar.Stub implements mCallbacks.get(i).immersiveModeChanged(rootDisplayAreaId, isImmersiveMode); } break; + case MSG_ENTER_DESKTOP: + args = (SomeArgs) msg.obj; + int displayId = args.argi1; + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).enterDesktop(displayId); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 0e0f15237185..615534809c97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar; import static com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress; +import static com.android.systemui.util.ColorUtilKt.hexColorString; import android.content.Context; import android.content.res.Configuration; @@ -42,6 +43,7 @@ import com.android.systemui.flags.Flags; import com.android.systemui.flags.RefactorFlag; import com.android.systemui.res.R; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; +import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -187,8 +189,8 @@ public class NotificationShelf extends ActivatableNotificationView { @Override public String toString() { - return "NotificationShelf" - + "(hideBackground=" + mHideBackground + return super.toString() + + " (hideBackground=" + mHideBackground + " notGoneIndex=" + mNotGoneIndex + " hasItemsInStableShelf=" + mHasItemsInStableShelf + " interactive=" + mInteractive @@ -368,6 +370,17 @@ public class NotificationShelf extends ActivatableNotificationView { && isYInView(localY, slop, top, bottom); } + @Override + public void updateBackgroundColors() { + super.updateBackgroundColors(); + ColorUpdateLogger colorUpdateLogger = ColorUpdateLogger.getInstance(); + if (colorUpdateLogger != null) { + colorUpdateLogger.logEvent("Shelf.updateBackgroundColors()", + "normalBgColor=" + hexColorString(getNormalBgColor()) + + " background=" + mBackgroundNormal.toDumpString()); + } + } + /** * Update the shelf appearance based on the other notifications around it. This transforms * the icons from the notification area into the shelf. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index fc84973c46bd..724b19c891aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -61,7 +61,6 @@ import com.android.settingslib.mobile.MobileStatusTracker.SubscriptionDefaults; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.DataUsageController; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -72,7 +71,8 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.log.dagger.StatusBarNetworkControllerLog; -import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; +import com.android.systemui.qs.tiles.dialog.InternetDialogManager; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -83,8 +83,6 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceP import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.CarrierConfigTracker; -import dalvik.annotation.optimization.NeverCompile; - import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -99,6 +97,7 @@ import java.util.stream.Collectors; import javax.inject.Inject; +import dalvik.annotation.optimization.NeverCompile; import kotlin.Unit; /** Platform implementation of the network controller. **/ @@ -201,7 +200,7 @@ public class NetworkControllerImpl extends BroadcastReceiver private boolean mUserSetup; private boolean mSimDetected; private boolean mForceCellularValidated; - private InternetDialogFactory mInternetDialogFactory; + private InternetDialogManager mInternetDialogManager; private Handler mMainHandler; private ConfigurationController.ConfigurationListener mConfigurationListener = @@ -245,7 +244,7 @@ public class NetworkControllerImpl extends BroadcastReceiver WifiStatusTrackerFactory trackerFactory, MobileSignalControllerFactory mobileFactory, @Main Handler handler, - InternetDialogFactory internetDialogFactory, + InternetDialogManager internetDialogManager, DumpManager dumpManager, @StatusBarNetworkControllerLog LogBuffer logBuffer) { this(context, connectivityManager, @@ -272,7 +271,7 @@ public class NetworkControllerImpl extends BroadcastReceiver dumpManager, logBuffer); mReceiverHandler.post(mRegisterListeners); - mInternetDialogFactory = internetDialogFactory; + mInternetDialogManager = internetDialogManager; } @VisibleForTesting @@ -829,7 +828,7 @@ public class NetworkControllerImpl extends BroadcastReceiver mReceiverHandler.post(this::handleConfigurationChanged); break; case Settings.Panel.ACTION_INTERNET_CONNECTIVITY: - mMainHandler.post(() -> mInternetDialogFactory.create(true, + mMainHandler.post(() -> mInternetDialogManager.create(true, mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi(), null /* view */)); break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt new file mode 100644 index 000000000000..c8f996a0272f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.notification + +import android.icu.text.SimpleDateFormat +import android.util.IndentingPrintWriter +import com.android.systemui.Dumpable +import com.android.systemui.Flags.notificationColorUpdateLogger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.util.Compile +import com.android.systemui.util.asIndenting +import com.android.systemui.util.printCollection +import com.android.systemui.util.withIncreasedIndent +import com.google.errorprone.annotations.CompileTimeConstant +import java.io.PrintWriter +import java.util.Locale +import java.util.SortedSet +import java.util.TreeSet +import javax.inject.Inject + +@SysUISingleton +class ColorUpdateLogger +@Inject +constructor( + val featureFlags: FeatureFlagsClassic, + dumpManager: DumpManager, +) : Dumpable { + + inline val isEnabled + get() = Compile.IS_DEBUG && notificationColorUpdateLogger() + private val frames: MutableList<Frame> = mutableListOf() + + init { + dumpManager.registerDumpable(this) + if (isEnabled) { + instance = this + } + } + + @JvmOverloads + fun logTriggerEvent(@CompileTimeConstant type: String, extra: String? = null) { + if (!isEnabled) return + val event = Event(type = type, extraValue = extra) + val didAppend = frames.lastOrNull()?.tryAddTrigger(event) == true + if (!didAppend) { + frames.add(Frame(event)) + if (frames.size > maxFrames) frames.removeFirst() + } + } + + @JvmOverloads + fun logEvent(@CompileTimeConstant type: String, extra: String? = null) { + if (!isEnabled) return + val frame = frames.lastOrNull() ?: return + frame.events.add(Event(type = type, extraValue = extra)) + frame.trim() + } + + @JvmOverloads + fun logNotificationEvent( + @CompileTimeConstant type: String, + key: String, + extra: String? = null + ) { + if (!isEnabled) return + val frame = frames.lastOrNull() ?: return + frame.events.add(Event(type = type, extraValue = extra, notificationKey = key)) + frame.trim() + } + + override fun dump(pwOrig: PrintWriter, args: Array<out String>) { + val pw = pwOrig.asIndenting() + pw.println("enabled: $isEnabled") + pw.printCollection("frames", frames) { it.dump(pw) } + } + + private class Frame(event: Event) { + val startTime: Long = event.time + val events: MutableList<Event> = mutableListOf(event) + val triggers: SortedSet<String> = TreeSet<String>().apply { add(event.type) } + var trimmedEvents: Int = 0 + + fun tryAddTrigger(newEvent: Event): Boolean { + if (newEvent.time < startTime) return false + if (newEvent.time - startTime > triggerStartsNewFrameAge) return false + if (newEvent.type in triggers) return false + triggers.add(newEvent.type) + events.add(newEvent) + trim() + return true + } + + fun trim() { + if (events.size > maxEventsPerFrame) { + events.removeFirst() + trimmedEvents++ + } + } + + fun dump(pw: IndentingPrintWriter) { + pw.println("Frame") + pw.withIncreasedIndent { + pw.println("startTime: ${timeString(startTime)}") + pw.printCollection("triggers", triggers) + pw.println("trimmedEvents: $trimmedEvents") + pw.printCollection("events", events) { it.dump(pw) } + } + } + } + + private class Event( + @CompileTimeConstant val type: String, + val extraValue: String? = null, + val notificationKey: String? = null, + ) { + val time: Long = System.currentTimeMillis() + + fun dump(pw: IndentingPrintWriter) { + pw.append(timeString(time)).append(": ").append(type) + extraValue?.let { pw.append(" ").append(it) } + notificationKey?.let { pw.append(" ---- ").append(logKey(it)) } + pw.println() + } + } + + private companion object { + @JvmStatic + var instance: ColorUpdateLogger? = null + private set + private const val maxFrames = 5 + private const val maxEventsPerFrame = 250 + private const val triggerStartsNewFrameAge = 5000 + + private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) + private fun timeString(time: Long): String = dateFormat.format(time) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt index 17fc5c60f74f..bdd9fd032800 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt @@ -26,11 +26,11 @@ import com.android.systemui.res.R /** Returns accessibility content description for a given notification. */ @MainThread -fun contentDescForNotification(c: Context, n: Notification?): CharSequence { - val appName = n?.loadHeaderAppName(c) ?: "" - val title = n?.extras?.getCharSequence(Notification.EXTRA_TITLE) - val text = n?.extras?.getCharSequence(Notification.EXTRA_TEXT) - val ticker = n?.tickerText +fun contentDescForNotification(c: Context, n: Notification): CharSequence { + val appName = n.loadHeaderAppName(c) ?: "" + val title = n.extras?.getCharSequence(Notification.EXTRA_TITLE) + val text = n.extras?.getCharSequence(Notification.EXTRA_TEXT) + val ticker = n.tickerText // Some apps just put the app name into the title val titleOrText = if (TextUtils.equals(title, appName)) text else title diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java index 0be4bde749f3..16af9d9c82ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java @@ -22,7 +22,10 @@ import android.os.RemoteException; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; +import androidx.annotation.NonNull; + import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.Flags; import com.android.systemui.media.controls.util.MediaFeatureFlag; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -53,32 +56,13 @@ public class MediaCoordinator implements Coordinator { private final NotifFilter mMediaFilter = new NotifFilter(TAG) { @Override - public boolean shouldFilterOut(NotificationEntry entry, long now) { + public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) { if (!mIsMediaFeatureEnabled || !isMediaNotification(entry.getSbn())) { return false; } - switch (mIconsState.getOrDefault(entry, STATE_ICONS_UNINFLATED)) { - case STATE_ICONS_UNINFLATED: - try { - mIconManager.createIcons(entry); - mIconsState.put(entry, STATE_ICONS_INFLATED); - } catch (InflationException e) { - reportInflationError(entry, e); - mIconsState.put(entry, STATE_ICONS_ERROR); - } - break; - case STATE_ICONS_INFLATED: - try { - mIconManager.updateIcons(entry); - } catch (InflationException e) { - reportInflationError(entry, e); - mIconsState.put(entry, STATE_ICONS_ERROR); - } - break; - case STATE_ICONS_ERROR: - // do nothing - break; + if (!Flags.notificationsBackgroundMediaIcons()) { + inflateOrUpdateIcons(entry); } return true; @@ -87,24 +71,67 @@ public class MediaCoordinator implements Coordinator { private final NotifCollectionListener mCollectionListener = new NotifCollectionListener() { @Override - public void onEntryInit(NotificationEntry entry) { - mIconsState.put(entry, STATE_ICONS_UNINFLATED); + public void onEntryInit(@NonNull NotificationEntry entry) { + // We default to STATE_ICONS_UNINFLATED anyway, so there's no need to initialize it. + if (!Flags.notificationsBackgroundMediaIcons()) { + mIconsState.put(entry, STATE_ICONS_UNINFLATED); + } + } + + @Override + public void onEntryAdded(@NonNull NotificationEntry entry) { + if (Flags.notificationsBackgroundMediaIcons()) { + if (isMediaNotification(entry.getSbn())) { + inflateOrUpdateIcons(entry); + } + } } @Override - public void onEntryUpdated(NotificationEntry entry) { + public void onEntryUpdated(@NonNull NotificationEntry entry) { if (mIconsState.getOrDefault(entry, STATE_ICONS_UNINFLATED) == STATE_ICONS_ERROR) { // The update may have fixed the inflation error, so give it another chance. mIconsState.put(entry, STATE_ICONS_UNINFLATED); } + + if (Flags.notificationsBackgroundMediaIcons()) { + if (isMediaNotification(entry.getSbn())) { + inflateOrUpdateIcons(entry); + } + } } @Override - public void onEntryCleanUp(NotificationEntry entry) { + public void onEntryCleanUp(@NonNull NotificationEntry entry) { mIconsState.remove(entry); } }; + private void inflateOrUpdateIcons(NotificationEntry entry) { + switch (mIconsState.getOrDefault(entry, STATE_ICONS_UNINFLATED)) { + case STATE_ICONS_UNINFLATED: + try { + mIconManager.createIcons(entry); + mIconsState.put(entry, STATE_ICONS_INFLATED); + } catch (InflationException e) { + reportInflationError(entry, e); + mIconsState.put(entry, STATE_ICONS_ERROR); + } + break; + case STATE_ICONS_INFLATED: + try { + mIconManager.updateIcons(entry); + } catch (InflationException e) { + reportInflationError(entry, e); + mIconsState.put(entry, STATE_ICONS_ERROR); + } + break; + case STATE_ICONS_ERROR: + // do nothing + break; + } + } + private void reportInflationError(NotificationEntry entry, Exception e) { // This is the same logic as in PreparationCoordinator; it doesn't handle media // notifications when the media feature is enabled since they aren't displayed in the shade, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt index 3809ea0f58da..b8a959440312 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt @@ -23,6 +23,7 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener +import com.android.systemui.statusbar.notification.ColorUpdateLogger import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.row.NotificationGutsManager @@ -41,7 +42,8 @@ class ViewConfigCoordinator @Inject internal constructor( private val mConfigurationController: ConfigurationController, private val mLockscreenUserManager: NotificationLockscreenUserManager, private val mGutsManager: NotificationGutsManager, - private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor + private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor, + private val colorUpdateLogger: ColorUpdateLogger, ) : Coordinator, ConfigurationController.ConfigurationListener { private var mIsSwitchingUser = false @@ -51,11 +53,13 @@ class ViewConfigCoordinator @Inject internal constructor( private val mKeyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() { override fun onUserSwitching(userId: Int) { + colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()") log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" } mIsSwitchingUser = true } override fun onUserSwitchComplete(userId: Int) { + colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitchComplete()") log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" } mIsSwitchingUser = false applyChangesOnUserSwitched() @@ -64,6 +68,7 @@ class ViewConfigCoordinator @Inject internal constructor( private val mUserChangedListener = object : UserChangedListener { override fun onUserChanged(userId: Int) { + colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()") log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" } applyChangesOnUserSwitched() } @@ -77,6 +82,7 @@ class ViewConfigCoordinator @Inject internal constructor( } override fun onDensityOrFontScaleChanged() { + colorUpdateLogger.logTriggerEvent("VCC.onDensityOrFontScaleChanged()") log { val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser "ViewConfigCoordinator.onDensityOrFontScaleChanged()" + @@ -93,6 +99,7 @@ class ViewConfigCoordinator @Inject internal constructor( } override fun onUiModeChanged() { + colorUpdateLogger.logTriggerEvent("VCC.onUiModeChanged()") log { val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser "ViewConfigCoordinator.onUiModeChanged()" + @@ -107,10 +114,12 @@ class ViewConfigCoordinator @Inject internal constructor( } override fun onThemeChanged() { + colorUpdateLogger.logTriggerEvent("VCC.onThemeChanged()") onDensityOrFontScaleChanged() } private fun applyChangesOnUserSwitched() { + colorUpdateLogger.logEvent("VCC.applyChangesOnUserSwitched()") if (mReinflateNotificationsOnUserSwitched) { updateNotificationsOnDensityOrFontScaleChanged() mReinflateNotificationsOnUserSwitched = false @@ -122,6 +131,8 @@ class ViewConfigCoordinator @Inject internal constructor( } private fun updateNotificationsOnUiModeChanged() { + colorUpdateLogger.logEvent("VCC.updateNotificationsOnUiModeChanged()", + "mode=" + mConfigurationController.nightModeName) log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" } traceSection("updateNotifOnUiModeChanged") { mPipeline?.allNotifs?.forEach { entry -> @@ -131,6 +142,7 @@ class ViewConfigCoordinator @Inject internal constructor( } private fun updateNotificationsOnDensityOrFontScaleChanged() { + colorUpdateLogger.logEvent("VCC.updateNotificationsOnDensityOrFontScaleChanged()") mPipeline?.allNotifs?.forEach { entry -> entry.onDensityOrFontScaleChanged() val exposedGuts = entry.areGutsExposed() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index 16f18a3c3fb6..f792898520a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.footer.ui.view; import static android.graphics.PorterDuff.Mode.SRC_ATOP; import static com.android.systemui.Flags.notificationBackgroundTintOptimization; +import static com.android.systemui.util.ColorUtilKt.hexColorString; import android.annotation.ColorInt; import android.annotation.DrawableRes; @@ -40,11 +41,13 @@ import androidx.annotation.NonNull; import com.android.settingslib.Utils; import com.android.systemui.res.R; +import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.row.FooterViewButton; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.ViewState; +import com.android.systemui.util.DrawableDumpKt; import com.android.systemui.util.DumpUtilsKt; import java.io.PrintWriter; @@ -239,6 +242,10 @@ public class FooterView extends StackScrollerDecorView { @Override protected void onFinishInflate() { + ColorUpdateLogger colorUpdateLogger = ColorUpdateLogger.getInstance(); + if (colorUpdateLogger != null) { + colorUpdateLogger.logTriggerEvent("Footer.onFinishInflate()"); + } super.onFinishInflate(); mClearAllButton = (FooterViewButton) findSecondaryView(); mManageOrHistoryButton = findViewById(R.id.manage_text); @@ -348,6 +355,10 @@ public class FooterView extends StackScrollerDecorView { @Override protected void onConfigurationChanged(Configuration newConfig) { + ColorUpdateLogger colorUpdateLogger = ColorUpdateLogger.getInstance(); + if (colorUpdateLogger != null) { + colorUpdateLogger.logTriggerEvent("Footer.onConfigurationChanged()"); + } super.onConfigurationChanged(newConfig); updateColors(); if (!FooterViewRefactor.isEnabled()) { @@ -365,14 +376,17 @@ public class FooterView extends StackScrollerDecorView { com.android.internal.R.attr.materialColorOnSurface); final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background); final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background); + final @ColorInt int scHigh; if (!notificationBackgroundTintOptimization()) { - final @ColorInt int scHigh = Utils.getColorAttrDefaultColor(mContext, + scHigh = Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.materialColorSurfaceContainerHigh); if (scHigh != 0) { final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP); clearAllBg.setColorFilter(bgColorFilter); manageBg.setColorFilter(bgColorFilter); } + } else { + scHigh = 0; } mClearAllButton.setBackground(clearAllBg); mClearAllButton.setTextColor(onSurface); @@ -380,6 +394,13 @@ public class FooterView extends StackScrollerDecorView { mManageOrHistoryButton.setTextColor(onSurface); mSeenNotifsFooterTextView.setTextColor(onSurface); mSeenNotifsFooterTextView.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface)); + ColorUpdateLogger colorUpdateLogger = ColorUpdateLogger.getInstance(); + if (colorUpdateLogger != null) { + colorUpdateLogger.logEvent("Footer.updateColors()", + "textColor(onSurface)=" + hexColorString(onSurface) + + " backgroundTint(surfaceContainerHigh)=" + hexColorString(scHigh) + + " background=" + DrawableDumpKt.dumpToString(manageBg)); + } } private void updateResources() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index fca527f5fc4d..73580343fab7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -88,7 +88,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private boolean mActivated; private Interpolator mCurrentAppearInterpolator; - NotificationBackgroundView mBackgroundNormal; + protected NotificationBackgroundView mBackgroundNormal; private float mAnimationTranslationY; private boolean mDrawingAppearAnimation; private ValueAnimator mAppearAnimator; @@ -142,6 +142,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView updateBackgroundTint(); } + protected int getNormalBgColor() { + return mNormalColor; + } + /** * @param width The actual width to apply to the background view. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index cc91ed394f48..d828ad70f5c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -21,6 +21,7 @@ import static android.service.notification.NotificationListenerService.REASON_CA import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED; import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP; +import static com.android.systemui.util.ColorUtilKt.hexColorString; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -85,6 +86,7 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; +import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.FeedbackIcon; import com.android.systemui.statusbar.notification.LaunchAnimationParameters; import com.android.systemui.statusbar.notification.NotificationFadeAware; @@ -172,6 +174,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private Optional<BubblesManager> mBubblesManagerOptional; private MetricsLogger mMetricsLogger; private NotificationChildrenContainerLogger mChildrenContainerLogger; + private ColorUpdateLogger mColorUpdateLogger; private NotificationDismissibilityProvider mDismissibilityProvider; private FeatureFlags mFeatureFlags; private int mIconTransformContentShift; @@ -445,6 +448,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * Sets animations running in the layouts of this row, including public, private, and children. + * * @param running whether the animations should be started running or stopped. */ public void setAnimationRunning(boolean running) { @@ -611,6 +615,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private void updateBackgroundColorsOfSelf() { super.updateBackgroundColors(); + if (mColorUpdateLogger.isEnabled()) { + mColorUpdateLogger.logNotificationEvent("ENR.updateBackgroundColorsOfSelf()", + mLoggingKey, + "normalBgColor=" + hexColorString(getNormalBgColor()) + + " background=" + mBackgroundNormal.toDumpString()); + } } @Override @@ -1389,7 +1399,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public void setContentBackground(int customBackgroundColor, boolean animate, - NotificationContentView notificationContentView) { + NotificationContentView notificationContentView) { if (getShowingLayout() == notificationContentView) { setTintColor(customBackgroundColor, animate); } @@ -1458,7 +1468,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * @return if this entry should be kept in its parent during removal. + * @return if this entry should be kept in its parent during removal. */ public boolean keepInParentForDismissAnimation() { return mKeepInParentForDismissAnimation; @@ -1769,6 +1779,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView NotificationDismissibilityProvider dismissibilityProvider, MetricsLogger metricsLogger, NotificationChildrenContainerLogger childrenContainerLogger, + ColorUpdateLogger colorUpdateLogger, SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController, FeatureFlags featureFlags, @@ -1807,6 +1818,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mNotificationGutsManager = gutsManager; mMetricsLogger = metricsLogger; mChildrenContainerLogger = childrenContainerLogger; + mColorUpdateLogger = colorUpdateLogger; mDismissibilityProvider = dismissibilityProvider; mFeatureFlags = featureFlags; } @@ -2265,7 +2277,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public Animator getTranslateViewAnimator(final float leftTarget, - AnimatorUpdateListener listener) { + AnimatorUpdateListener listener) { if (mTranslateAnim != null) { mTranslateAnim.cancel(); } @@ -2664,6 +2676,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return getCollapsedHeight(); } } + /** * @return {@code true} if the notification can show it's heads up layout. This is mostly true * except for legacy use cases. @@ -2833,7 +2846,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, - long duration) { + long duration) { if (getVisibility() == GONE) { // If we are GONE, the hideSensitive parameter will not be calculated and always be // false, which is incorrect, let's wait until a real call comes in later. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 5614f3a3fcc5..e59829bf3c99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -41,6 +41,7 @@ import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.SmartReplyController; +import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.FeedbackIcon; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; @@ -86,6 +87,7 @@ public class ExpandableNotificationRowController implements NotifViewController private final SystemClock mClock; private final String mAppName; private final String mNotificationKey; + private final ColorUpdateLogger mColorUpdateLogger; private final KeyguardBypassController mKeyguardBypassController; private final GroupMembershipManager mGroupMembershipManager; private final GroupExpansionManager mGroupExpansionManager; @@ -200,6 +202,7 @@ public class ExpandableNotificationRowController implements NotifViewController ActivatableNotificationViewController activatableNotificationViewController, RemoteInputViewSubcomponent.Factory rivSubcomponentFactory, MetricsLogger metricsLogger, + ColorUpdateLogger colorUpdateLogger, NotificationRowLogger logBufferLogger, NotificationChildrenContainerLogger childrenContainerLogger, NotificationListContainer listContainer, @@ -256,6 +259,7 @@ public class ExpandableNotificationRowController implements NotifViewController mDragController = dragController; mMetricsLogger = metricsLogger; mChildrenContainerLogger = childrenContainerLogger; + mColorUpdateLogger = colorUpdateLogger; mLogBufferLogger = logBufferLogger; mSmartReplyConstants = smartReplyConstants; mSmartReplyController = smartReplyController; @@ -290,6 +294,7 @@ public class ExpandableNotificationRowController implements NotifViewController mDismissibilityProvider, mMetricsLogger, mChildrenContainerLogger, + mColorUpdateLogger, mSmartReplyConstants, mSmartReplyController, mFeatureFlags, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index ec8e5d730c36..ea9df9af8cff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.row; +import static com.android.systemui.Flags.notificationColorUpdateLogger; + import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Configuration; @@ -54,8 +56,8 @@ import java.util.List; public abstract class ExpandableView extends FrameLayout implements Dumpable, Roundable { private static final String TAG = "ExpandableView"; /** whether the dump() for this class should include verbose details */ - protected static final boolean DUMP_VERBOSE = - Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); + protected static final boolean DUMP_VERBOSE = Compile.IS_DEBUG + && (Log.isLoggable(TAG, Log.VERBOSE) || notificationColorUpdateLogger()); private RoundableState mRoundableState = null; protected OnHeightChangedListener mOnHeightChangedListener; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index 7ea9b14353d3..ed3a38d3305b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -36,6 +36,7 @@ import com.android.internal.util.ContrastColorUtil; import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.res.R; +import com.android.systemui.util.DrawableDumpKt; import java.io.PrintWriter; import java.util.Arrays; @@ -333,6 +334,16 @@ public class NotificationBackgroundView extends View implements Dumpable { pw.println("mActualHeight: " + mActualHeight); pw.println("mTintColor: " + hexColorString(mTintColor)); pw.println("mRippleColor: " + hexColorString(mRippleColor)); - pw.println("mBackground: " + mBackground); + pw.println("mBackground: " + DrawableDumpKt.dumpToString(mBackground)); + } + + /** create a concise dump of this view's colors */ + public String toDumpString() { + return "<NotificationBackgroundView" + + " tintColor=" + hexColorString(mTintColor) + + " rippleColor=" + hexColorString(mRippleColor) + + " bgColor=" + DrawableDumpKt.getSolidColor(mBackground) + + ">"; + } } 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 933a78009e29..7925a1ce97ee 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 @@ -99,6 +99,7 @@ import com.android.systemui.shade.TouchLogger; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.LaunchAnimationParameters; import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController; @@ -122,6 +123,7 @@ import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.Assert; +import com.android.systemui.util.ColorUtilKt; import com.android.systemui.util.DumpUtilsKt; import com.google.errorprone.annotations.CompileTimeConstant; @@ -805,8 +807,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateBackgroundDimming(); for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); - if (child instanceof ActivatableNotificationView) { - ((ActivatableNotificationView) child).updateBackgroundColors(); + if (child instanceof ActivatableNotificationView activatableView) { + activatableView.updateBackgroundColors(); } } } @@ -4595,6 +4597,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final @ColorInt int onSurfaceVariant = Utils.getColorAttrDefaultColor( mContext, com.android.internal.R.attr.materialColorOnSurfaceVariant); + ColorUpdateLogger colorUpdateLogger = ColorUpdateLogger.getInstance(); + if (colorUpdateLogger != null) { + colorUpdateLogger.logEvent("NSSL.updateDecorViews()", + "onSurface=" + ColorUtilKt.hexColorString(onSurface) + + " onSurfaceVariant=" + ColorUtilKt.hexColorString(onSurfaceVariant)); + } + mSectionsManager.setHeaderForegroundColors(onSurface, onSurfaceVariant); if (mFooterView != null) { 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 78e6a795604a..d2ff266319d0 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 @@ -96,6 +96,7 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.LaunchAnimationParameters; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -177,6 +178,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final ConfigurationController mConfigurationController; private final ZenModeController mZenModeController; private final MetricsLogger mMetricsLogger; + private final ColorUpdateLogger mColorUpdateLogger; private final DumpManager mDumpManager; private final FalsingCollector mFalsingCollector; @@ -239,6 +241,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { + mColorUpdateLogger.logTriggerEvent("NSSLC.onViewAttachedToWindow()"); mConfigurationController.addCallback(mConfigurationListener); if (!FooterViewRefactor.isEnabled()) { mZenModeController.addCallback(mZenModeControllerCallback); @@ -254,6 +257,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onViewDetachedFromWindow(View v) { + mColorUpdateLogger.logTriggerEvent("NSSLC.onViewDetachedFromWindow()"); mConfigurationController.removeCallback(mConfigurationListener); if (!FooterViewRefactor.isEnabled()) { mZenModeController.removeCallback(mZenModeControllerCallback); @@ -332,12 +336,16 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Override public void onUiModeChanged() { + mColorUpdateLogger.logTriggerEvent("NSSLC.onUiModeChanged()", + "mode=" + mConfigurationController.getNightModeName()); mView.updateBgColor(); mView.updateDecorViews(); } @Override public void onThemeChanged() { + mColorUpdateLogger.logTriggerEvent("NSSLC.onThemeChanged()", + "mode=" + mConfigurationController.getNightModeName()); mView.updateCornerRadius(); mView.updateBgColor(); mView.updateDecorViews(); @@ -719,6 +727,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { ZenModeController zenModeController, NotificationLockscreenUserManager lockscreenUserManager, MetricsLogger metricsLogger, + ColorUpdateLogger colorUpdateLogger, DumpManager dumpManager, FalsingCollector falsingCollector, FalsingManager falsingManager, @@ -773,6 +782,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { mZenModeController = zenModeController; mLockscreenUserManager = lockscreenUserManager; mMetricsLogger = metricsLogger; + mColorUpdateLogger = colorUpdateLogger; mDumpManager = dumpManager; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mFalsingCollector = falsingCollector; 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 4b8fb1e5a0ff..20e8cac6e94b 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,7 +18,7 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import android.content.Context -import com.android.systemui.Flags.centralizedStatusBarDimensRefactor +import com.android.systemui.Flags.centralizedStatusBarHeightFix import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor @@ -76,7 +76,7 @@ constructor( getDimensionPixelSize(R.dimen.notification_panel_margin_bottom), marginTop = getDimensionPixelSize(R.dimen.notification_panel_margin_top), marginTopLargeScreen = - if (centralizedStatusBarDimensRefactor()) { + if (centralizedStatusBarHeightFix()) { largeScreenHeaderHelperLazy.get().getLargeScreenHeaderHeight() } else { getDimensionPixelSize(R.dimen.large_screen_shade_header_height) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt index 6321820b0733..7b502564ecb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -122,32 +122,31 @@ constructor( qsFullScreen, isRemoteInputActive, isShadeClosed -> - // A pair of (visible, canAnimate) when { - !hasNotifications -> Pair(false, true) + !hasNotifications -> VisibilityChange.HIDE_WITH_ANIMATION // Hide the footer until the user setup is complete, to prevent access // to settings (b/193149550). - !isUserSetUp -> Pair(false, true) + !isUserSetUp -> VisibilityChange.HIDE_WITH_ANIMATION // Do not show the footer if the lockscreen is visible (incl. AOD), // except if the shade is opened on top. See also b/219680200. // Do not animate, as that makes the footer appear briefly when // transitioning between the shade and keyguard. - isShowingOnLockscreen -> Pair(false, false) + isShowingOnLockscreen -> VisibilityChange.HIDE_WITHOUT_ANIMATION // Do not show the footer if quick settings are fully expanded (except // for the foldable split shade view). See b/201427195 && b/222699879. - qsExpansion == 1f && qsFullScreen -> Pair(false, true) + qsExpansion == 1f && qsFullScreen -> VisibilityChange.HIDE_WITH_ANIMATION // Hide the footer if remote input is active (i.e. user is replying to a // notification). See b/75984847. - isRemoteInputActive -> Pair(false, true) + isRemoteInputActive -> VisibilityChange.HIDE_WITH_ANIMATION // Never show the footer if the shade is collapsed (e.g. when HUNing). - isShadeClosed -> Pair(false, false) - else -> Pair(true, true) + isShadeClosed -> VisibilityChange.HIDE_WITHOUT_ANIMATION + else -> VisibilityChange.SHOW_WITH_ANIMATION } } .distinctUntilChanged( // Equivalent unless visibility changes - areEquivalent = { a: Pair<Boolean, Boolean>, b: Pair<Boolean, Boolean> -> - a.first == b.first + areEquivalent = { a: VisibilityChange, b: VisibilityChange -> + a.visible == b.visible } ) // Should we animate the visibility change? @@ -160,17 +159,24 @@ constructor( ::Pair ) .onStart { emit(Pair(false, false)) } - ) { (visible, canAnimate), (isShadeFullyExpanded, animationsEnabled) -> + ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) -> // Animate if the shade is interactive, but NOT on the lockscreen. Having // animations enabled while on the lockscreen makes the footer appear briefly // when transitioning between the shade and keyguard. - val shouldAnimate = isShadeFullyExpanded && animationsEnabled && canAnimate - AnimatableEvent(visible, shouldAnimate) + val shouldAnimate = + isShadeFullyExpanded && animationsEnabled && visibilityChange.canAnimate + AnimatableEvent(visibilityChange.visible, shouldAnimate) } .toAnimatedValueFlow() } } + enum class VisibilityChange(val visible: Boolean, val canAnimate: Boolean) { + HIDE_WITHOUT_ANIMATION(visible = false, canAnimate = false), + HIDE_WITH_ANIMATION(visible = false, canAnimate = true), + SHOW_WITH_ANIMATION(visible = true, canAnimate = true) + } + private val isShowingOnLockscreen: Flow<Boolean> by lazy { if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(false) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt index 6e8ad2e50620..dea94162ad0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt @@ -159,6 +159,15 @@ class ConfigurationControllerImpl @Inject constructor( override fun isLayoutRtl(): Boolean { return layoutDirection == LAYOUT_DIRECTION_RTL } + + override fun getNightModeName(): String { + return when (uiMode and Configuration.UI_MODE_NIGHT_MASK) { + Configuration.UI_MODE_NIGHT_YES -> "night" + Configuration.UI_MODE_NIGHT_NO -> "day" + Configuration.UI_MODE_NIGHT_UNDEFINED -> "undefined" + else -> "err" + } + } } // This could be done with a Collection.filter and Collection.forEach, but Collection.filter diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index db55da7b8acb..0adc1b0af66f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.Flags.centralizedStatusBarDimensRefactor; +import static com.android.systemui.Flags.centralizedStatusBarHeightFix; import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInScale; import static com.android.systemui.statusbar.notification.NotificationUtils.interpolate; @@ -169,7 +169,7 @@ public class KeyguardClockPositionAlgorithm { mStatusViewBottomMargin = res.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin); mSplitShadeTopNotificationsMargin = - centralizedStatusBarDimensRefactor() + centralizedStatusBarHeightFix() ? LargeScreenHeaderHelper.getLargeScreenHeaderHeight(context) : res.getDimensionPixelSize(R.dimen.large_screen_shade_header_height); mSplitShadeTargetTopMargin = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 769145923886..302bdcc2ea77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.Flags.centralizedStatusBarDimensRefactor; +import static com.android.systemui.Flags.centralizedStatusBarHeightFix; import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard; @@ -131,7 +131,7 @@ public class KeyguardStatusBarView extends RelativeLayout { mUserSwitcherContainer = findViewById(R.id.user_switcher_container); mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot); loadDimens(); - if (!centralizedStatusBarDimensRefactor()) { + if (!centralizedStatusBarHeightFix()) { setGravity(Gravity.CENTER_VERTICAL); } } @@ -311,7 +311,7 @@ public class KeyguardStatusBarView extends RelativeLayout { final int minRight = (!isLayoutRtl() && mIsPrivacyDotEnabled) ? Math.max(mMinDotWidth, mPadding.right) : mPadding.right; - int top = centralizedStatusBarDimensRefactor() ? waterfallTop + mPadding.top : waterfallTop; + int top = centralizedStatusBarHeightFix() ? waterfallTop + mPadding.top : waterfallTop; setPadding(minLeft, top, minRight, 0); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index 1414150c5511..2c1780d3b304 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -158,7 +158,15 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { @Override public void showNotification(@NonNull NotificationEntry entry) { mLogger.logShowNotification(entry); - addEntry(entry); + + // Add new entry and begin managing it + HeadsUpEntry headsUpEntry = createHeadsUpEntry(); + headsUpEntry.setEntry(entry); + mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry); + onEntryAdded(headsUpEntry); + entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + entry.setIsHeadsUpEntry(true); + updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */); entry.setInterruption(); } @@ -319,19 +327,6 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } /** - * Add a new entry and begin managing it. - * @param entry the entry to add - */ - protected final void addEntry(@NonNull NotificationEntry entry) { - HeadsUpEntry headsUpEntry = createHeadsUpEntry(); - headsUpEntry.setEntry(entry); - mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry); - onEntryAdded(headsUpEntry); - entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - entry.setIsHeadsUpEntry(true); - } - - /** * Manager-specific logic that should occur when an entry is added. * @param headsUpEntry entry added */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java index b2ef818d3282..cec77c12a40b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java @@ -33,6 +33,9 @@ public interface ConfigurationController extends CallbackController<Configuratio /** Query the current configuration's layout direction */ boolean isLayoutRtl(); + /** Logging only; Query the current configuration's night mode name */ + String getNightModeName(); + interface ConfigurationListener { default void onConfigChanged(Configuration newConfig) {} default void onDensityOrFontScaleChanged() {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index df210b073e77..600005b97610 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; +import static com.android.systemui.Flags.registerZenModeContentObserverBackground; + import android.app.AlarmManager; import android.app.Flags; import android.app.NotificationManager; @@ -45,6 +47,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; @@ -104,6 +107,7 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { public ZenModeControllerImpl( Context context, @Main Handler handler, + @Background Handler bgHandler, BroadcastDispatcher broadcastDispatcher, DumpManager dumpManager, GlobalSettings globalSettings, @@ -134,9 +138,18 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { } }; mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver); + if (registerZenModeContentObserverBackground()) { + bgHandler.post(() -> { + globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver); + globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, + configContentObserver); + }); + } else { + globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver); + globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, + configContentObserver); + } updateZenMode(getModeSettingValueFromProvider()); - globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, configContentObserver); updateZenModeConfig(); updateConsolidatedNotificationPolicy(); mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 585ab72dd963..44c684c34587 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -815,7 +815,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { ? () -> {} : () -> { Log.d(TAG, "ThemeHomeDelay: ThemeOverlayController ready"); - mActivityManager.setThemeOverlayReady(true); + mActivityManager.setThemeOverlayReady(currentUser); }; if (colorSchemeIsApplied(managedProfiles)) { diff --git a/packages/SystemUI/src/com/android/systemui/util/DrawableDump.kt b/packages/SystemUI/src/com/android/systemui/util/DrawableDump.kt new file mode 100644 index 000000000000..0c079a309962 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/DrawableDump.kt @@ -0,0 +1,201 @@ +/* + * 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.util + +import android.content.res.ColorStateList +import android.graphics.BlendMode +import android.graphics.BlendModeColorFilter +import android.graphics.ColorFilter +import android.graphics.LightingColorFilter +import android.graphics.PorterDuffColorFilter +import android.graphics.drawable.Drawable +import android.graphics.drawable.DrawableWrapper +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.LayerDrawable +import android.graphics.drawable.RippleDrawable +import android.util.Log + +fun dumpToString(drawable: Drawable?): String = + if (Compile.IS_DEBUG) StringBuilder().appendDrawable(drawable).toString() + else drawable.toString() + +fun getSolidColor(drawable: Drawable?): String = + if (Compile.IS_DEBUG) hexColorString(getSolidColors(drawable)?.defaultColor) + else if (drawable == null) "null" else "?" + +private fun getSolidColors(drawable: Drawable?): ColorStateList? { + return when (drawable) { + is GradientDrawable -> { + return drawable.getStateField<ColorStateList>("mSolidColors") + } + is LayerDrawable -> { + for (iLayer in 0 until drawable.numberOfLayers) { + getSolidColors(drawable.getDrawable(iLayer))?.let { + return it + } + } + null + } + is DrawableWrapper -> { + return getSolidColors(drawable.drawable) + } + else -> null + } +} + +private fun StringBuilder.appendDrawable(drawable: Drawable?): StringBuilder { + if (drawable == null) { + append("null") + return this + } + append("<") + append(drawable.javaClass.simpleName) + + drawable.getStateField<ColorStateList>("mTint", fieldRequired = false)?.let { + append(" tint=") + appendColors(it) + append(" blendMode=") + append(drawable.getStateField<BlendMode>("mBlendMode")) + } + drawable.colorFilter + ?.takeUnless { drawable is DrawableWrapper } + ?.let { + append(" colorFilter=") + appendColorFilter(it) + } + when (drawable) { + is DrawableWrapper -> { + append(" wrapped=") + appendDrawable(drawable.drawable) + } + is LayerDrawable -> { + if (drawable is RippleDrawable) { + drawable.getStateField<ColorStateList>("mColor")?.let { + append(" color=") + appendColors(it) + } + drawable.effectColor?.let { + append(" effectColor=") + appendColors(it) + } + } + append(" layers=[") + for (iLayer in 0 until drawable.numberOfLayers) { + if (iLayer != 0) append(", ") + appendDrawable(drawable.getDrawable(iLayer)) + } + append("]") + } + is GradientDrawable -> { + drawable + .getStateField<Int>("mShape") + ?.takeIf { it != 0 } + ?.let { + append(" shape=") + append(it) + } + drawable.getStateField<ColorStateList>("mSolidColors")?.let { + append(" solidColors=") + appendColors(it) + } + drawable.getStateField<ColorStateList>("mStrokeColors")?.let { + append(" strokeColors=") + appendColors(it) + } + drawable.colors?.let { + append(" gradientColors=[") + it.forEachIndexed { iColor, color -> + if (iColor != 0) append(", ") + append(hexColorString(color)) + } + append("]") + } + } + } + append(">") + return this +} + +private inline fun <reified T> Drawable.getStateField( + name: String, + fieldRequired: Boolean = true +): T? { + val state = this.constantState ?: return null + val clazz = state.javaClass + return try { + val field = clazz.getDeclaredField(name) + field.isAccessible = true + field.get(state) as T? + } catch (ex: Exception) { + if (fieldRequired) { + Log.w(TAG, "Missing ${clazz.simpleName}.$name: ${T::class.simpleName}", ex) + } else if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Missing ${clazz.simpleName}.$name: ${T::class.simpleName} ($ex)") + } + null + } +} + +private fun Appendable.appendColors(colorStateList: ColorStateList?) { + if (colorStateList == null) { + append("null") + return + } + val colors = colorStateList.colors + if (colors.size == 1) { + append(hexColorString(colors[0])) + return + } + append("<ColorStateList size=") + append(colors.size.toString()) + append(" default=") + append(hexColorString(colorStateList.defaultColor)) + append(">") +} + +private fun Appendable.appendColorFilter(colorFilter: ColorFilter?) { + if (colorFilter == null) { + append("null") + return + } + append("<") + append(colorFilter.javaClass.simpleName) + when (colorFilter) { + is PorterDuffColorFilter -> { + append(" color=") + append(hexColorString(colorFilter.color)) + append(" mode=") + append(colorFilter.mode.toString()) + } + is BlendModeColorFilter -> { + append(" color=") + append(hexColorString(colorFilter.color)) + append(" mode=") + append(colorFilter.mode.toString()) + } + is LightingColorFilter -> { + append(" multiply=") + append(hexColorString(colorFilter.colorMultiply)) + append(" add=") + append(hexColorString(colorFilter.colorAdd)) + } + else -> append(" unhandled") + } + append(">") +} + +private const val TAG = "DrawableDump" diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt new file mode 100644 index 000000000000..e17274c435aa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.kotlin + +import android.util.IndentingPrintWriter +import com.android.systemui.Dumpable +import com.android.systemui.dump.DumpManager +import com.android.systemui.util.asIndenting +import com.android.systemui.util.printCollection +import java.io.PrintWriter +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicBoolean +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flow + +/** + * An interface which gives the implementing type flow extension functions which will register a + * given flow as a field in the Dumpable. + */ +interface FlowDumper : Dumpable { + /** + * Include the last emitted value of this Flow whenever it is being collected. Remove its value + * when collection ends. + * + * @param dumpName the name to use for this field in the dump output + */ + fun <T> Flow<T>.dumpWhileCollecting(dumpName: String): Flow<T> + + /** + * Include the [SharedFlow.replayCache] for this Flow in the dump. + * + * @param dumpName the name to use for this field in the dump output + */ + fun <T, F : SharedFlow<T>> F.dumpReplayCache(dumpName: String): F + + /** + * Include the [StateFlow.value] for this Flow in the dump. + * + * @param dumpName the name to use for this field in the dump output + */ + fun <T, F : StateFlow<T>> F.dumpValue(dumpName: String): F + + /** The default [Dumpable.dump] implementation which just calls [dumpFlows] */ + override fun dump(pw: PrintWriter, args: Array<out String>) = dumpFlows(pw.asIndenting()) + + /** Dump all the values from any registered / active Flows. */ + fun dumpFlows(pw: IndentingPrintWriter) +} + +/** + * An implementation of [FlowDumper]. This be extended directly, or can be used to implement + * [FlowDumper] by delegation. + * + * @param dumpManager if provided, this will be used by the [FlowDumperImpl] to register and + * unregister itself when there is something to dump. + * @param tag a static name by which this [FlowDumperImpl] is registered. If not provided, this + * class's name will be used. If you're implementing by delegation, you probably want to provide + * this tag to get a meaningful dumpable name. + */ +open class FlowDumperImpl(private val dumpManager: DumpManager?, tag: String? = null) : FlowDumper { + private val stateFlowMap = ConcurrentHashMap<String, StateFlow<*>>() + private val sharedFlowMap = ConcurrentHashMap<String, SharedFlow<*>>() + private val flowCollectionMap = ConcurrentHashMap<Pair<String, String>, Any>() + override fun dumpFlows(pw: IndentingPrintWriter) { + pw.printCollection("StateFlow (value)", stateFlowMap.toSortedMap().entries) { (key, flow) -> + append(key).append('=').println(flow.value) + } + pw.printCollection("SharedFlow (replayCache)", sharedFlowMap.toSortedMap().entries) { + (key, flow) -> + append(key).append('=').println(flow.replayCache) + } + val comparator = compareBy<Pair<String, String>> { it.first }.thenBy { it.second } + pw.printCollection("Flow (latest)", flowCollectionMap.toSortedMap(comparator).entries) { + (pair, value) -> + append(pair.first).append('=').println(value) + } + } + + private val Any.idString: String + get() = Integer.toHexString(System.identityHashCode(this)) + + override fun <T> Flow<T>.dumpWhileCollecting(dumpName: String): Flow<T> = flow { + val mapKey = dumpName to idString + try { + collect { + flowCollectionMap[mapKey] = it ?: "null" + updateRegistration(required = true) + emit(it) + } + } finally { + flowCollectionMap.remove(mapKey) + updateRegistration(required = false) + } + } + + override fun <T, F : StateFlow<T>> F.dumpValue(dumpName: String): F { + stateFlowMap[dumpName] = this + return this + } + + override fun <T, F : SharedFlow<T>> F.dumpReplayCache(dumpName: String): F { + sharedFlowMap[dumpName] = this + return this + } + + private val dumpManagerName = tag ?: "[$idString] ${javaClass.simpleName}" + private var registered = AtomicBoolean(false) + private fun updateRegistration(required: Boolean) { + if (dumpManager == null) return + if (required && registered.get()) return + synchronized(registered) { + val shouldRegister = + stateFlowMap.isNotEmpty() || + sharedFlowMap.isNotEmpty() || + flowCollectionMap.isNotEmpty() + val wasRegistered = registered.getAndSet(shouldRegister) + if (wasRegistered != shouldRegister) { + if (shouldRegister) { + dumpManager.registerCriticalDumpable(dumpManagerName, this) + } else { + dumpManager.unregisterDumpable(dumpManagerName) + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index e8325065c219..15e0965c16fe 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -356,6 +356,12 @@ public final class WMShell implements // TODO(b/278084491): update sysui state for changes on other displays } }, mSysUiMainExecutor); + mCommandQueue.addCallback(new CommandQueue.Callbacks() { + @Override + public void enterDesktop(int displayId) { + desktopMode.enterDesktop(displayId); + } + }); } @VisibleForTesting diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt index 5eda2b28175d..569e0642a160 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt @@ -89,6 +89,16 @@ class CredentialInteractorImplTest : SysuiTestCase() { } } + @Test + fun testParentProfile() { + for (value in listOf(12, 8, 4)) { + whenever(userManager.getProfileParent(eq(USER_ID))) + .thenReturn(UserInfo(value, "test", 0)) + + assertThat(interactor.getParentProfileIdOrSelfId(USER_ID)).isEqualTo(value) + } + } + @Test fun pinCredentialWhenGood() = pinCredential(goodCredential()) @Test fun pinCredentialWhenBad() = pinCredential(badCredential()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 1a6da7608849..915522de62d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -53,11 +53,9 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker -import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.testKosmos import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -103,8 +101,6 @@ class CustomizationProviderTest : SysuiTestCase() { private lateinit var underTest: CustomizationProvider private lateinit var testScope: TestScope - private val kosmos = testKosmos() - @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -189,7 +185,6 @@ class CustomizationProviderTest : SysuiTestCase() { }, ) .keyguardInteractor, - shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 45b2a4266ff6..d2a8444e40ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -46,9 +46,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker -import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.testKosmos import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -244,8 +242,6 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository private lateinit var userTracker: UserTracker - private val kosmos = testKosmos() - @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -315,7 +311,6 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { featureFlags = featureFlags, ) .keyguardInteractor, - shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 729086388a4f..2ec2fe3d0eb7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -54,7 +54,6 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker -import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.statusbar.policy.KeyguardStateController @@ -219,7 +218,6 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { quickAffordanceInteractor = KeyguardQuickAffordanceInteractor( keyguardInteractor = keyguardInteractor, - shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index eded4dcb9bd6..18a34ba964cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -264,14 +264,12 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha) .thenReturn(emptyFlow()) whenever(shadeInteractor.qsExpansion).thenReturn(intendedShadeAlphaMutableStateFlow) - whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f)) underTest = KeyguardQuickAffordancesCombinedViewModel( quickAffordanceInteractor = KeyguardQuickAffordanceInteractor( keyguardInteractor = keyguardInteractor, - shadeInteractor = shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt index 83f8f180ae9d..06833213671d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt @@ -30,7 +30,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger -import com.android.systemui.qs.tiles.dialog.InternetDialogFactory +import com.android.systemui.qs.tiles.dialog.InternetDialogManager import com.android.systemui.statusbar.connectivity.AccessPointController import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor @@ -83,7 +83,7 @@ class InternetTileNewImplTest : SysuiTestCase() { @Mock private lateinit var sbStateController: StatusBarStateController @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var logger: QSLogger - @Mock private lateinit var dialogFactory: InternetDialogFactory + @Mock private lateinit var dialogManager: InternetDialogManager @Mock private lateinit var accessPointController: AccessPointController @Before @@ -118,7 +118,7 @@ class InternetTileNewImplTest : SysuiTestCase() { activityStarter, logger, viewModel, - dialogFactory, + dialogManager, accessPointController ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java index c1f19645e4a7..288faccd2f92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java @@ -17,7 +17,6 @@ package com.android.systemui.qs.tiles; import static com.google.common.truth.Truth.assertThat; - import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -38,7 +37,7 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; -import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; +import com.android.systemui.qs.tiles.dialog.InternetDialogManager; import com.android.systemui.statusbar.connectivity.AccessPointController; import com.android.systemui.statusbar.connectivity.IconState; import com.android.systemui.statusbar.connectivity.NetworkController; @@ -63,7 +62,7 @@ public class InternetTileTest extends SysuiTestCase { @Mock private AccessPointController mAccessPointController; @Mock - private InternetDialogFactory mInternetDialogFactory; + private InternetDialogManager mInternetDialogManager; @Mock private QsEventLogger mUiEventLogger; @@ -89,7 +88,7 @@ public class InternetTileTest extends SysuiTestCase { mock(QSLogger.class), mNetworkController, mAccessPointController, - mInternetDialogFactory + mInternetDialogManager ); mTile.initialize(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java index 077ec4b7102c..74f50dff99ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java @@ -5,7 +5,6 @@ import static android.provider.Settings.Global.AIRPLANE_MODE_ON; import static android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS; import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT; import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR; - import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource; import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT; @@ -13,9 +12,7 @@ import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAS import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX; import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN; import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE; - import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -101,7 +98,7 @@ import java.util.Map; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class InternetDialogControllerTest extends SysuiTestCase { +public class InternetDialogDelegateControllerTest extends SysuiTestCase { private static final int SUB_ID = 1; private static final int SUB_ID2 = 2; diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java index c9e6274e5660..db9f5cfc9ac6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java @@ -1,9 +1,7 @@ package com.android.systemui.qs.tiles.dialog; import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT; - import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -34,6 +32,7 @@ import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.res.R; +import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -55,7 +54,7 @@ import java.util.List; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -public class InternetDialogTest extends SysuiTestCase { +public class InternetDialogDelegateTest extends SysuiTestCase { private static final String MOBILE_NETWORK_TITLE = "Mobile Title"; private static final String MOBILE_NETWORK_SUMMARY = "Mobile Summary"; @@ -78,9 +77,13 @@ public class InternetDialogTest extends SysuiTestCase { private KeyguardStateController mKeyguard; @Mock private DialogTransitionAnimator mDialogTransitionAnimator; + @Mock + private SystemUIDialog.Factory mSystemUIDialogFactory; + @Mock + private SystemUIDialog mSystemUIDialog; private FakeExecutor mBgExecutor = new FakeExecutor(new FakeSystemClock()); - private InternetDialog mInternetDialog; + private InternetDialogDelegate mInternetDialogDelegate; private View mDialogView; private View mSubTitle; private LinearLayout mEthernet; @@ -117,21 +120,30 @@ public class InternetDialogTest extends SysuiTestCase { .spyStatic(WifiEnterpriseRestrictionUtils.class) .startMocking(); when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(true); - + when(mSystemUIDialogFactory.create(any(SystemUIDialog.Delegate.class))) + .thenReturn(mSystemUIDialog); createInternetDialog(); } private void createInternetDialog() { - mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class), - mInternetDialogController, true, true, true, mock(UiEventLogger.class), - mDialogTransitionAnimator, mHandler, - mBgExecutor, mKeyguard); - mInternetDialog.mAdapter = mInternetAdapter; - mInternetDialog.mConnectedWifiEntry = mInternetWifiEntry; - mInternetDialog.mWifiEntriesCount = mWifiEntries.size(); - mInternetDialog.show(); - - mDialogView = mInternetDialog.mDialogView; + mInternetDialogDelegate = new InternetDialogDelegate( + mContext, + mock(InternetDialogManager.class), + mInternetDialogController, + true, + true, + true, + mock(UiEventLogger.class), + mDialogTransitionAnimator, + mHandler, + mBgExecutor, + mKeyguard, + mSystemUIDialogFactory); + mInternetDialogDelegate.mAdapter = mInternetAdapter; + mInternetDialogDelegate.mConnectedWifiEntry = mInternetWifiEntry; + mInternetDialogDelegate.mWifiEntriesCount = mWifiEntries.size(); + + mDialogView = mInternetDialogDelegate.mDialogView; mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle); mEthernet = mDialogView.requireViewById(R.id.ethernet_layout); mMobileDataLayout = mDialogView.requireViewById(R.id.mobile_network_layout); @@ -148,7 +160,7 @@ public class InternetDialogTest extends SysuiTestCase { @After public void tearDown() { - mInternetDialog.dismissDialog(); + mInternetDialogDelegate.dismissDialog(); mMockitoSession.finishMocking(); } @@ -160,9 +172,9 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void hideWifiViews_WifiViewsGone() { - mInternetDialog.hideWifiViews(); + mInternetDialogDelegate.hideWifiViews(); - assertThat(mInternetDialog.mIsProgressBarVisible).isFalse(); + assertThat(mInternetDialogDelegate.mIsProgressBarVisible).isFalse(); assertThat(mWifiToggle.getVisibility()).isEqualTo(View.GONE); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE); assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE); @@ -173,7 +185,7 @@ public class InternetDialogTest extends SysuiTestCase { public void updateDialog_withApmOn_internetDialogSubTitleGone() { when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE); } @@ -182,7 +194,7 @@ public class InternetDialogTest extends SysuiTestCase { public void updateDialog_withApmOff_internetDialogSubTitleVisible() { when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE); } @@ -192,7 +204,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); when(mInternetDialogController.hasEthernet()).thenReturn(true); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE); } @@ -202,7 +214,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); when(mInternetDialogController.hasEthernet()).thenReturn(false); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE); } @@ -212,7 +224,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); when(mInternetDialogController.hasEthernet()).thenReturn(true); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE); } @@ -222,7 +234,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); when(mInternetDialogController.hasEthernet()).thenReturn(false); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE); } @@ -234,7 +246,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); when(mInternetDialogController.hasActiveSubId()).thenReturn(false); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE); } @@ -246,7 +258,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); when(mInternetDialogController.isWifiEnabled()).thenReturn(false); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE); @@ -255,7 +267,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); when(mInternetDialogController.isWifiEnabled()).thenReturn(true); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE); } @@ -265,7 +277,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false); when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE); } @@ -274,10 +286,10 @@ public class InternetDialogTest extends SysuiTestCase { public void updateDialog_apmOnAndWifiOnHasCarrierNetwork_showAirplaneSummary() { when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); - mInternetDialog.mConnectedWifiEntry = null; + mInternetDialogDelegate.mConnectedWifiEntry = null; doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE); @@ -287,10 +299,10 @@ public class InternetDialogTest extends SysuiTestCase { public void updateDialog_apmOffAndWifiOnHasCarrierNetwork_notShowApmSummary() { when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); - mInternetDialog.mConnectedWifiEntry = null; + mInternetDialogDelegate.mConnectedWifiEntry = null; doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE); } @@ -300,7 +312,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE); } @@ -310,7 +322,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false); when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE); } @@ -322,7 +334,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true); mMobileToggleSwitch.setChecked(false); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mMobileToggleSwitch.isChecked()).isTrue(); } @@ -334,20 +346,20 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false); mMobileToggleSwitch.setChecked(false); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mMobileToggleSwitch.isChecked()).isFalse(); } @Test public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() { - mInternetDialog.dismissDialog(); + mInternetDialogDelegate.dismissDialog(); doReturn(true).when(mInternetDialogController).hasActiveSubId(); createInternetDialog(); // The preconditions WiFi ON and Internet WiFi are already in setUp() doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE); LinearLayout secondaryLayout = mDialogView.requireViewById( @@ -358,10 +370,10 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndNoConnectedWifi_hideConnectedWifi() { // The precondition WiFi ON is already in setUp() - mInternetDialog.mConnectedWifiEntry = null; + mInternetDialogDelegate.mConnectedWifiEntry = null; doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE); } @@ -369,10 +381,10 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndNoWifiEntry_showWifiListAndSeeAllArea() { // The precondition WiFi ON is already in setUp() - mInternetDialog.mConnectedWifiEntry = null; - mInternetDialog.mWifiEntriesCount = 0; + mInternetDialogDelegate.mConnectedWifiEntry = null; + mInternetDialogDelegate.mWifiEntriesCount = 0; - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE); // Show a blank block to fix the dialog height even if there is no WiFi list @@ -384,10 +396,10 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndOneWifiEntry_showWifiListAndSeeAllArea() { // The precondition WiFi ON is already in setUp() - mInternetDialog.mConnectedWifiEntry = null; - mInternetDialog.mWifiEntriesCount = 1; + mInternetDialogDelegate.mConnectedWifiEntry = null; + mInternetDialogDelegate.mWifiEntriesCount = 1; - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE); // Show a blank block to fix the dialog height even if there is no WiFi list @@ -399,9 +411,9 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() { // The preconditions WiFi ON and WiFi entries are already in setUp() - mInternetDialog.mWifiEntriesCount = 0; + mInternetDialogDelegate.mWifiEntriesCount = 0; - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE); // Show a blank block to fix the dialog height even if there is no WiFi list @@ -413,11 +425,11 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndHasMaxWifiList_showWifiListAndSeeAll() { // The preconditions WiFi ON and WiFi entries are already in setUp() - mInternetDialog.mConnectedWifiEntry = null; - mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT; - mInternetDialog.mHasMoreWifiEntries = true; + mInternetDialogDelegate.mConnectedWifiEntry = null; + mInternetDialogDelegate.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT; + mInternetDialogDelegate.mHasMoreWifiEntries = true; - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE); assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); @@ -428,10 +440,10 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndHasBothWifiEntry_showBothWifiEntryAndSeeAll() { // The preconditions WiFi ON and WiFi entries are already in setUp() - mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT - 1; - mInternetDialog.mHasMoreWifiEntries = true; + mInternetDialogDelegate.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT - 1; + mInternetDialogDelegate.mHasMoreWifiEntries = true; - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); @@ -443,9 +455,9 @@ public class InternetDialogTest extends SysuiTestCase { public void updateDialog_deviceLockedAndNoConnectedWifi_showWifiToggle() { // The preconditions WiFi entries are already in setUp() when(mInternetDialogController.isDeviceLocked()).thenReturn(true); - mInternetDialog.mConnectedWifiEntry = null; + mInternetDialogDelegate.mConnectedWifiEntry = null; - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); // Show WiFi Toggle without background assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE); @@ -461,7 +473,7 @@ public class InternetDialogTest extends SysuiTestCase { // The preconditions WiFi ON and WiFi entries are already in setUp() when(mInternetDialogController.isDeviceLocked()).thenReturn(true); - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); // Show WiFi Toggle with highlight background assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE); @@ -474,11 +486,11 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_disallowChangeWifiState_disableWifiSwitch() { - mInternetDialog.dismissDialog(); + mInternetDialogDelegate.dismissDialog(); when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(false); createInternetDialog(); - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); // Disable Wi-Fi switch and show restriction message in summary. assertThat(mWifiToggleSwitch.isEnabled()).isFalse(); @@ -488,11 +500,11 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_allowChangeWifiState_enableWifiSwitch() { - mInternetDialog.dismissDialog(); + mInternetDialogDelegate.dismissDialog(); when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(true); createInternetDialog(); - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); // Enable Wi-Fi switch and hide restriction message in summary. assertThat(mWifiToggleSwitch.isEnabled()).isTrue(); @@ -501,14 +513,14 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_showSecondaryDataSub() { - mInternetDialog.dismissDialog(); + mInternetDialogDelegate.dismissDialog(); doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId(); doReturn(true).when(mInternetDialogController).hasActiveSubId(); doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled(); createInternetDialog(); clearInvocations(mInternetDialogController); - mInternetDialog.updateDialog(true); + mInternetDialogDelegate.updateDialog(true); LinearLayout primaryLayout = mDialogView.requireViewById( R.id.mobile_network_layout); @@ -523,7 +535,7 @@ public class InternetDialogTest extends SysuiTestCase { ArgumentCaptor<AlertDialog> dialogArgumentCaptor = ArgumentCaptor.forClass(AlertDialog.class); verify(mDialogTransitionAnimator).showFromDialog(dialogArgumentCaptor.capture(), - eq(mInternetDialog), eq(null), eq(false)); + eq(mSystemUIDialog), eq(null), eq(false)); AlertDialog dialog = dialogArgumentCaptor.getValue(); dialog.show(); dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); @@ -541,7 +553,7 @@ public class InternetDialogTest extends SysuiTestCase { public void updateDialog_wifiOn_hideWifiScanNotify() { // The preconditions WiFi ON and WiFi entries are already in setUp() - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE); } @@ -551,7 +563,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isWifiEnabled()).thenReturn(false); when(mInternetDialogController.isWifiScanEnabled()).thenReturn(false); - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE); } @@ -562,7 +574,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isWifiScanEnabled()).thenReturn(true); when(mInternetDialogController.isDeviceLocked()).thenReturn(true); - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE); } @@ -573,7 +585,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isWifiScanEnabled()).thenReturn(true); when(mInternetDialogController.isDeviceLocked()).thenReturn(false); - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.VISIBLE); TextView wifiScanNotifyText = mDialogView.requireViewById(R.id.wifi_scan_notify_text); @@ -586,7 +598,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isWifiEnabled()).thenReturn(false); mWifiToggleSwitch.setChecked(true); - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mWifiToggleSwitch.isChecked()).isFalse(); } @@ -596,7 +608,7 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.isWifiEnabled()).thenReturn(true); mWifiToggleSwitch.setChecked(false); - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); assertThat(mWifiToggleSwitch.isChecked()).isTrue(); } @@ -611,20 +623,20 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void onWifiScan_isScanTrue_setProgressBarVisibleTrue() { - mInternetDialog.mIsProgressBarVisible = false; + mInternetDialogDelegate.mIsProgressBarVisible = false; - mInternetDialog.onWifiScan(true); + mInternetDialogDelegate.onWifiScan(true); - assertThat(mInternetDialog.mIsProgressBarVisible).isTrue(); + assertThat(mInternetDialogDelegate.mIsProgressBarVisible).isTrue(); } @Test public void onWifiScan_isScanFalse_setProgressBarVisibleFalse() { - mInternetDialog.mIsProgressBarVisible = true; + mInternetDialogDelegate.mIsProgressBarVisible = true; - mInternetDialog.onWifiScan(false); + mInternetDialogDelegate.onWifiScan(false); - assertThat(mInternetDialog.mIsProgressBarVisible).isFalse(); + assertThat(mInternetDialogDelegate.mIsProgressBarVisible).isFalse(); } @Test @@ -633,42 +645,47 @@ public class InternetDialogTest extends SysuiTestCase { // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT. setNetworkVisible(false, false, false); - assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT); + assertThat(mInternetDialogDelegate.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT); // If the Connected Wi-Fi is displayed then reduce one of the Wi-Fi list max count. setNetworkVisible(false, false, true); - assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); + assertThat(mInternetDialogDelegate.getWifiListMaxCount()) + .isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); // Only one of Ethernet, MobileData is displayed. // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT. setNetworkVisible(true, false, false); - assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT); + assertThat(mInternetDialogDelegate.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT); setNetworkVisible(false, true, false); - assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT); + assertThat(mInternetDialogDelegate.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT); // If the Connected Wi-Fi is displayed then reduce one of the Wi-Fi list max count. setNetworkVisible(true, false, true); - assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); + assertThat(mInternetDialogDelegate.getWifiListMaxCount()) + .isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); setNetworkVisible(false, true, true); - assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); + assertThat(mInternetDialogDelegate.getWifiListMaxCount()) + .isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); // Both of Ethernet, MobileData, ConnectedWiFi is displayed. // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT - 1. setNetworkVisible(true, true, false); - assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); + assertThat(mInternetDialogDelegate.getWifiListMaxCount()) + .isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); // If the Connected Wi-Fi is displayed then reduce one of the Wi-Fi list max count. setNetworkVisible(true, true, true); - assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2); + assertThat(mInternetDialogDelegate.getWifiListMaxCount()) + .isEqualTo(MAX_WIFI_ENTRY_COUNT - 2); } @Test @@ -676,9 +693,9 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.getConfiguratorQrCodeGeneratorIntentOrNull(any())) .thenReturn(null); - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); - assertThat(mInternetDialog.mShareWifiButton.getVisibility()).isEqualTo(View.GONE); + assertThat(mInternetDialogDelegate.mShareWifiButton.getVisibility()).isEqualTo(View.GONE); } @Test @@ -686,9 +703,10 @@ public class InternetDialogTest extends SysuiTestCase { when(mInternetDialogController.getConfiguratorQrCodeGeneratorIntentOrNull(any())) .thenReturn(new Intent()); - mInternetDialog.updateDialog(false); + mInternetDialogDelegate.updateDialog(false); - assertThat(mInternetDialog.mShareWifiButton.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mInternetDialogDelegate.mShareWifiButton.getVisibility()) + .isEqualTo(View.VISIBLE); } private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible, 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 992658a99df4..db455cb31684 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -96,6 +96,7 @@ import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewConfigurator; +import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepository; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; @@ -354,6 +355,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; protected KeyguardClockInteractor mKeyguardClockInteractor; protected FakeKeyguardRepository mFakeKeyguardRepository; + protected FakeKeyguardClockRepository mFakeKeyguardClockRepository; protected KeyguardInteractor mKeyguardInteractor; protected ShadeAnimationInteractor mShadeAnimationInteractor; protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); @@ -399,6 +401,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { KeyguardInteractorFactory.create(); mFakeKeyguardRepository = keyguardInteractorDeps.getRepository(); mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository); + mFakeKeyguardClockRepository = new FakeKeyguardClockRepository(); + mKeyguardClockInteractor = new KeyguardClockInteractor(mFakeKeyguardClockRepository); mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor(); mShadeRepository = new FakeShadeRepository(); mShadeAnimationInteractor = new ShadeAnimationInteractorLegacyImpl( 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 ab5e51c1b1a1..59fe813cf18e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -207,7 +207,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Test fun testDragDownHelperCalledWhenDraggingDown() = testScope.runTest { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) whenever(dragDownHelper.isDraggingDown).thenReturn(true) val now = SystemClock.elapsedRealtime() val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */) 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 4cc123407d83..81d0e06df1fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt @@ -27,7 +27,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.Flags as AConfigFlags -import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR +import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.fragments.FragmentService @@ -167,7 +167,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() { val headerResourceHeight = 20 val headerHelperHeight = 30 - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) .thenReturn(headerHelperHeight) overrideResource(R.bool.config_use_large_screen_shade_header, true) @@ -190,7 +190,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() { val headerResourceHeight = 20 val headerHelperHeight = 30 - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) .thenReturn(headerHelperHeight) overrideResource(R.bool.config_use_large_screen_shade_header, true) @@ -401,7 +401,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testSplitShadeLayout_isAlignedToGuideline() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) enableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline) @@ -411,7 +411,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testSinglePaneLayout_childrenHaveEqualMargins() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) disableSplitShade() underTest.updateResources() val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin @@ -428,7 +428,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) enableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0) @@ -446,8 +446,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderResourceHeight = 100 val largeScreenHeaderHelperHeight = 200 @@ -469,8 +469,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderResourceHeight = 100 val largeScreenHeaderHelperHeight = 200 @@ -492,7 +492,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) setSmallScreen() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0) @@ -513,7 +513,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { @Test fun testSinglePaneShadeLayout_isAlignedToParent() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) disableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd) 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 d7eada82b9a6..4ae751b4e7eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt @@ -26,7 +26,7 @@ import androidx.annotation.IdRes import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR +import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.fragments.FragmentService @@ -162,7 +162,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { @Test fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val helperHeight = 30 val resourceHeight = 20 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight) @@ -183,7 +183,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { @Test fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val helperHeight = 30 val resourceHeight = 20 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight) @@ -425,7 +425,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { @Test fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderHelperHeight = 200 val largeScreenHeaderResourceHeight = 100 @@ -445,7 +445,7 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { @Test fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderHelperHeight = 200 val largeScreenHeaderResourceHeight = 100 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java index f178046b665a..b6ee46ddafeb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java @@ -52,8 +52,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.ContrastColorUtil; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.NotificationContentDescription; import org.junit.Before; @@ -153,7 +153,7 @@ public class StatusBarIconViewTest extends SysuiTestCase { Icon icon = Icon.createWithBitmap(bitmap); StatusBarIcon largeIcon = new StatusBarIcon(UserHandle.ALL, "mockPackage", icon, 0, 0, ""); - mIconView.setNotification(mock(StatusBarNotification.class)); + mIconView.setNotification(getMockSbn()); mIconView.getIcon(largeIcon); // no crash? good @@ -196,7 +196,7 @@ public class StatusBarIconViewTest extends SysuiTestCase { // the icon view layout size would be 60x150 // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT) setUpIconView(dpIconSize, dpDrawingSize, dpIconSize); - mIconView.setNotification(mock(StatusBarNotification.class)); + mIconView.setNotification(getMockSbn()); // the raw drawable size is 50x50. When put the drawable into iconView whose // layout size is 60x150, the drawable size would not be constrained and thus keep 50x50 setIconDrawableWithSize(/* width= */ 50, /* height= */ 50); @@ -215,7 +215,7 @@ public class StatusBarIconViewTest extends SysuiTestCase { // the icon view layout size would be 60x150 // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT) setUpIconView(dpIconSize, dpDrawingSize, dpIconSize); - mIconView.setNotification(mock(StatusBarNotification.class)); + mIconView.setNotification(getMockSbn()); // the raw drawable size is 50x100. When put the drawable into iconView whose // layout size is 60x150, the drawable size would not be constrained and thus keep 50x100 setIconDrawableWithSize(/* width= */ 50, /* height= */ 100); @@ -235,7 +235,7 @@ public class StatusBarIconViewTest extends SysuiTestCase { // the icon view layout size would be 60x150 // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT) setUpIconView(dpIconSize, dpDrawingSize, dpIconSize); - mIconView.setNotification(mock(StatusBarNotification.class)); + mIconView.setNotification(getMockSbn()); // the raw drawable size is 100x50. When put the drawable into iconView whose // layout size is 60x150, the drawable size would be constrained to 60x30 setIconDrawableWithSize(/* width= */ 100, /* height= */ 50); @@ -257,7 +257,7 @@ public class StatusBarIconViewTest extends SysuiTestCase { // the icon view layout size would be 40x150 // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT) setUpIconView(dpIconSize, dpDrawingSize, spIconSize); - mIconView.setNotification(mock(StatusBarNotification.class)); + mIconView.setNotification(getMockSbn()); // the raw drawable size is 50x50. When put the drawable into iconView whose // layout size is 40x150, the drawable size would be constrained to 40x40 setIconDrawableWithSize(/* width= */ 50, /* height= */ 50); @@ -283,7 +283,7 @@ public class StatusBarIconViewTest extends SysuiTestCase { // the icon view layout size would be 40x150 // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT) setUpIconView(dpIconSize, dpDrawingSize, spIconSize); - mIconView.setNotification(mock(StatusBarNotification.class)); + mIconView.setNotification(getMockSbn()); // the raw drawable size is 70x70. When put the drawable into iconView whose // layout size is 40x150, the drawable size would be constrained to 40x40 setIconDrawableWithSize(/* width= */ 70, /* height= */ 70); @@ -310,7 +310,7 @@ public class StatusBarIconViewTest extends SysuiTestCase { // the icon view layout size would be 40x150 // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT) setUpIconView(dpIconSize, dpDrawingSize, spIconSize); - mIconView.setNotification(mock(StatusBarNotification.class)); + mIconView.setNotification(getMockSbn()); // the raw drawable size is 50x100. When put the drawable into iconView whose // layout size is 40x150, the drawable size would be constrained to 40x80 setIconDrawableWithSize(/* width= */ 50, /* height= */ 100); @@ -334,7 +334,7 @@ public class StatusBarIconViewTest extends SysuiTestCase { // the icon view layout size would be 80x150 // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT) setUpIconView(dpIconSize, dpDrawingSize, spIconSize); - mIconView.setNotification(mock(StatusBarNotification.class)); + mIconView.setNotification(getMockSbn()); // the raw drawable size is 50x50. When put the drawable into iconView whose // layout size is 80x150, the drawable size would not be constrained and thus keep 50x50 setIconDrawableWithSize(/* width= */ 50, /* height= */ 50); @@ -357,7 +357,7 @@ public class StatusBarIconViewTest extends SysuiTestCase { // the icon view layout size would be 80x150 // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT) setUpIconView(dpIconSize, dpDrawingSize, spIconSize); - mIconView.setNotification(mock(StatusBarNotification.class)); + mIconView.setNotification(getMockSbn()); // the raw drawable size is 50x100. When put the drawable into iconView whose // layout size is 80x150, the drawable size would not be constrained and thus keep 50x100 setIconDrawableWithSize(/* width= */ 50, /* height= */ 100); @@ -381,7 +381,7 @@ public class StatusBarIconViewTest extends SysuiTestCase { // the icon view layout size would be 80x150 // (the height is always 150 due to TEST_STATUS_BAR_HEIGHT) setUpIconView(dpIconSize, dpDrawingSize, spIconSize); - mIconView.setNotification(mock(StatusBarNotification.class)); + mIconView.setNotification(getMockSbn()); // the raw drawable size is 100x50. When put the drawable into iconView whose // layout size is 80x150, the drawable size would not be constrained and thus keep 80x40 setIconDrawableWithSize(/* width= */ 100, /* height= */ 50); @@ -397,6 +397,12 @@ public class StatusBarIconViewTest extends SysuiTestCase { mIconView.getIconScale(), 0.01f); } + private static StatusBarNotification getMockSbn() { + StatusBarNotification sbn = mock(StatusBarNotification.class); + when(sbn.getNotification()).thenReturn(mock(Notification.class)); + return sbn; + } + /** * Setup iconView dimens for testing. The result icon view layout width would * be spIconSize and height would be 150. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java index 590c902ba687..b548117684ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; @@ -28,12 +29,15 @@ import static org.mockito.Mockito.when; import android.app.Notification.MediaStyle; import android.media.session.MediaSession; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.service.notification.NotificationListenerService; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.media.controls.util.MediaFeatureFlag; import com.android.systemui.statusbar.notification.InflationException; @@ -153,7 +157,8 @@ public final class MediaCoordinatorTest extends SysuiTestCase { } @Test - public void inflateMediaNotificationIconsMediaEnabled() throws InflationException { + @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS) + public void inflateMediaNotificationIconsMediaEnabled_old() throws InflationException { finishSetupWithMediaFeatureFlagEnabled(true); mListener.onEntryInit(mMediaEntry); @@ -181,7 +186,37 @@ public final class MediaCoordinatorTest extends SysuiTestCase { } @Test - public void inflationException() throws InflationException { + @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS) + public void inflateMediaNotificationIconsMediaEnabled_new() throws InflationException { + finishSetupWithMediaFeatureFlagEnabled(true); + + mListener.onEntryInit(mMediaEntry); + mListener.onEntryAdded(mMediaEntry); + verify(mIconManager).createIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + clearInvocations(mIconManager); + + mFilter.shouldFilterOut(mMediaEntry, 0); + verify(mIconManager, never()).createIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + + mListener.onEntryUpdated(mMediaEntry); + verify(mIconManager, never()).createIcons(eq(mMediaEntry)); + verify(mIconManager).updateIcons(eq(mMediaEntry)); + + mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL); + mListener.onEntryCleanUp(mMediaEntry); + clearInvocations(mIconManager); + + mListener.onEntryInit(mMediaEntry); + mListener.onEntryAdded(mMediaEntry); + verify(mIconManager).createIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + } + + @Test + @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS) + public void inflationException_old() throws InflationException { finishSetupWithMediaFeatureFlagEnabled(true); mListener.onEntryInit(mMediaEntry); @@ -208,6 +243,31 @@ public final class MediaCoordinatorTest extends SysuiTestCase { verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); } + @Test + @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS) + public void inflationException_new() throws InflationException { + finishSetupWithMediaFeatureFlagEnabled(true); + + doThrow(InflationException.class).when(mIconManager).createIcons(eq(mMediaEntry)); + + mListener.onEntryInit(mMediaEntry); + mListener.onEntryAdded(mMediaEntry); + verify(mIconManager).createIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + clearInvocations(mIconManager); + + mListener.onEntryUpdated(mMediaEntry); + verify(mIconManager).createIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + clearInvocations(mIconManager); + + doNothing().when(mIconManager).createIcons(eq(mMediaEntry)); + + mListener.onEntryUpdated(mMediaEntry); + verify(mIconManager).createIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + } + private void finishSetupWithMediaFeatureFlagEnabled(boolean mediaFeatureFlagEnabled) { when(mMediaFeatureFlag.getEnabled()).thenReturn(mediaFeatureFlagEnabled); mCoordinator = new MediaCoordinator(mMediaFeatureFlag, mStatusBarService, mIconManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt index 0830191fe035..b1d2ea21f7fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt @@ -23,6 +23,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener +import com.android.systemui.statusbar.notification.ColorUpdateLogger import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow @@ -57,6 +58,7 @@ class ViewConfigCoordinatorTest : SysuiTestCase() { private val lockscreenUserManager: NotificationLockscreenUserManager = mock() private val gutsManager: NotificationGutsManager = mock() private val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock() + private val colorUpdateLogger: ColorUpdateLogger = mock() @Before fun setUp() { @@ -66,7 +68,9 @@ class ViewConfigCoordinatorTest : SysuiTestCase() { configurationController, lockscreenUserManager, gutsManager, - keyguardUpdateMonitor) + keyguardUpdateMonitor, + colorUpdateLogger, + ) coordinator.attach(pipeline) userChangedListener = withArgCaptor { verify(lockscreenUserManager).addUserChangedListener(capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index 8ac2a334818c..210b1a7f22f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.plugins.PluginManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.statusbar.SmartReplyController +import com.android.systemui.statusbar.notification.ColorUpdateLogger import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider @@ -82,6 +83,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { private val rivSubComponentFactory: RemoteInputViewSubcomponent.Factory = mock() private val metricsLogger: MetricsLogger = mock() private val logBufferLogger = NotificationRowLogger(logcatLogBuffer(), logcatLogBuffer()) + private val colorUpdateLogger: ColorUpdateLogger = mock() private val listContainer: NotificationListContainer = mock() private val childrenContainer: NotificationChildrenContainer = mock() private val smartReplyConstants: SmartReplyConstants = mock() @@ -117,6 +119,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { activableNotificationViewController, rivSubComponentFactory, metricsLogger, + colorUpdateLogger, logBufferLogger, NotificationChildrenContainerLogger(logcatLogBuffer()), listContainer, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index c71799170482..e78081fc34bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -65,6 +65,7 @@ import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SmartReplyController; +import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.ConversationNotificationProcessor; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -607,6 +608,7 @@ public class NotificationTestHelper { mDismissibilityProvider, mock(MetricsLogger.class), new NotificationChildrenContainerLogger(logcatLogBuffer()), + mock(ColorUpdateLogger.class), mock(SmartReplyConstants.class), mock(SmartReplyController.class), mFeatureFlags, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 354f3f679ffb..f2ef4e17900d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -78,6 +78,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChan import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -148,6 +149,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager; @Mock private MetricsLogger mMetricsLogger; + @Mock private ColorUpdateLogger mColorUpdateLogger; @Mock private DumpManager mDumpManager; @Mock(answer = Answers.RETURNS_SELF) private NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder; @@ -1007,6 +1009,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mZenModeController, mNotificationLockscreenUserManager, mMetricsLogger, + mColorUpdateLogger, mDumpManager, new FalsingCollectorFake(), new FalsingManagerFake(), 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 2da88e9f0ef9..b9b872254b06 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 @@ -21,7 +21,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR +import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository @@ -134,7 +134,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validatePaddingTopInSplitShade_refactorFlagOff_usesLargeHeaderResource() = testScope.runTest { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) .thenReturn(true) @@ -153,7 +153,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validatePaddingTopInSplitShade_refactorFlagOn_usesLargeHeaderHelper() = testScope.runTest { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) .thenReturn(true) @@ -210,7 +210,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_usesResource() = testScope.runTest { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val headerResourceHeight = 50 val headerHelperHeight = 100 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) @@ -229,7 +229,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() = testScope.runTest { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val headerResourceHeight = 50 val headerHelperHeight = 100 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) @@ -449,7 +449,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() = testScope.runTest { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val bounds by collectLastValue(underTest.bounds) // When in split shade @@ -478,7 +478,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun boundsOnLockscreenInSplitShade_refactorFlagOn_usesLargeHeaderHelper() = testScope.runTest { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) + mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val bounds by collectLastValue(underTest.bounds) // When in split shade diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index 38698f86c659..fd295b57379c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -298,7 +298,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { @Test public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() { - mSetFlagsRule.disableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR); + mSetFlagsRule.disableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX); int keyguardSplitShadeTopMargin = 100; int largeScreenHeaderHeightResource = 70; when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) @@ -317,7 +317,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { @Test public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() { - mSetFlagsRule.enableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR); + mSetFlagsRule.enableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX); int keyguardSplitShadeTopMargin = 100; int largeScreenHeaderHeightHelper = 50; int largeScreenHeaderHeightResource = 70; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java index 41b959e98221..9d53b9c66b33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java @@ -119,7 +119,7 @@ public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase @Test public void testAppearResetsTranslation() { - mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL); + mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); mController.setupAodIcons(mAodIcons); when(mDozeParameters.shouldControlScreenOff()).thenReturn(false); mController.appearAodIcons(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java index f1a2c281595d..ddd29c3f2803 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java @@ -79,6 +79,7 @@ public class ZenModeControllerImplTest extends SysuiTestCase { mController = new ZenModeControllerImpl( mContext, Handler.createAsync(TestableLooper.get(this).getLooper()), + Handler.createAsync(TestableLooper.get(this).getLooper()), mBroadcastDispatcher, mDumpManager, mGlobalSettings, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt index fbe291ebaf5d..a4c8a0b2c228 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt @@ -14,10 +14,15 @@ class FakeCredentialInteractor : CredentialInteractor { /** Sets return value for [getCredentialOwnerOrSelfId]. */ var credentialOwnerId: Int? = null + /** Sets return value for [getParentProfileIdOrSelfId]. */ + var userIdForPasswordEntry: Int? = null + override fun isStealthModeActive(userId: Int): Boolean = stealthMode override fun getCredentialOwnerOrSelfId(userId: Int): Int = credentialOwnerId ?: userId + override fun getParentProfileIdOrSelfId(userId: Int): Int = userIdForPasswordEntry ?: userId + override fun verifyCredential( request: BiometricPromptRequest.Credential, credential: LockscreenCredential, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index f7e9a117aa77..566fc2529954 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -21,12 +21,16 @@ import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.communal.widgets.EditWidgetsActivityStarter +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.smartspace.data.repository.smartspaceRepository +import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.mock val Kosmos.communalInteractor by Fixture { @@ -47,3 +51,14 @@ val Kosmos.communalInteractor by Fixture { } val Kosmos.editWidgetsActivityStarter by Fixture<EditWidgetsActivityStarter> { mock() } + +suspend fun Kosmos.setCommunalAvailable(available: Boolean) { + fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, available) + if (available) { + fakeUserRepository.asMainUser() + with(fakeKeyguardRepository) { + setIsEncryptedOrLockdown(false) + setKeyguardShowing(true) + } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt index c51de334c8ca..46a10532ea52 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt @@ -43,6 +43,7 @@ class FakeConfigurationController @Inject constructor() : ConfigurationControlle } override fun isLayoutRtl(): Boolean = isRtl + override fun getNightModeName(): String = "undefined" } @Module diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 1124425e5d49..931a59d30d7b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -39,13 +39,19 @@ class FakeUserRepository @Inject constructor() : UserRepository { // User id to represent a non system (human) user id. We presume this is the main user. private const val MAIN_USER_ID = 10 - private val DEFAULT_SELECTED_USER = 0 + private const val DEFAULT_SELECTED_USER = 0 private val DEFAULT_SELECTED_USER_INFO = UserInfo( /* id= */ DEFAULT_SELECTED_USER, /* name= */ "default selected user", /* flags= */ 0, ) + private val MAIN_USER = + UserInfo( + /* id= */ MAIN_USER_ID, + /* name= */ "main user", + /* flags= */ UserInfo.FLAG_MAIN, + ) } private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel()) @@ -113,6 +119,13 @@ class FakeUserRepository @Inject constructor() : UserRepository { yield() } + /** Makes the current user [MAIN_USER]. */ + suspend fun asMainUser(): UserInfo { + setUserInfos(listOf(MAIN_USER)) + setSelectedUserInfo(MAIN_USER) + return MAIN_USER + } + suspend fun setSettings(settings: UserSwitcherSettingsModel) { _userSwitcherSettings.value = settings yield() diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java index 516eb6e6dffd..111c40d49efc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java @@ -38,4 +38,9 @@ public class FakeConfigurationController public boolean isLayoutRtl() { return false; } + + @Override + public String getNightModeName() { + return "undefined"; + } } diff --git a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java index 1f18e15bb646..9b27dd347caf 100644 --- a/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/BrailleDisplayConnection.java @@ -42,7 +42,6 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -112,7 +111,7 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { BrailleDisplayConnection(@NonNull Object lock, @NonNull AccessibilityServiceConnection serviceConnection) { this.mLock = Objects.requireNonNull(lock); - this.mScanner = getDefaultNativeScanner(getDefaultNativeInterface()); + this.mScanner = getDefaultNativeScanner(new DefaultNativeInterface()); this.mServiceConnection = Objects.requireNonNull(serviceConnection); } @@ -128,7 +127,7 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { */ @VisibleForTesting interface BrailleDisplayScanner { - Collection<Path> getHidrawNodePaths(); + Collection<Path> getHidrawNodePaths(@NonNull Path directory); byte[] getDeviceReportDescriptor(@NonNull Path path); @@ -158,8 +157,9 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { Objects.requireNonNull(expectedUniqueId); this.mController = Objects.requireNonNull(controller); + final Path devicePath = Path.of("/dev"); final List<Pair<File, byte[]>> result = new ArrayList<>(); - final Collection<Path> hidrawNodePaths = mScanner.getHidrawNodePaths(); + final Collection<Path> hidrawNodePaths = mScanner.getHidrawNodePaths(devicePath); if (hidrawNodePaths == null) { Slog.w(LOG_TAG, "Unable to access the HIDRAW node directory"); sendConnectionErrorLocked(FLAG_ERROR_CANNOT_ACCESS); @@ -285,6 +285,9 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { if (buffer.length > IBinder.getSuggestedMaxIpcSizeBytes()) { Slog.e(LOG_TAG, "Requested write of size " + buffer.length + " which is larger than maximum " + IBinder.getSuggestedMaxIpcSizeBytes()); + // The caller only got here by bypassing the AccessibilityService-side check with + // reflection, so disconnect this connection to prevent further attempts. + disconnect(); return; } synchronized (mLock) { @@ -292,7 +295,7 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { if (mOutputThread == null) { try { mOutputStream = new FileOutputStream(mHidrawNode); - } catch (FileNotFoundException e) { + } catch (Exception e) { Slog.e(LOG_TAG, "Unable to create write stream", e); disconnect(); return; @@ -387,14 +390,13 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { BrailleDisplayScanner getDefaultNativeScanner(@NonNull NativeInterface nativeInterface) { Objects.requireNonNull(nativeInterface); return new BrailleDisplayScanner() { - private static final Path DEVICE_DIR = Path.of("/dev"); private static final String HIDRAW_DEVICE_GLOB = "hidraw*"; @Override - public Collection<Path> getHidrawNodePaths() { + public Collection<Path> getHidrawNodePaths(@NonNull Path directory) { final List<Path> result = new ArrayList<>(); try (DirectoryStream<Path> hidrawNodePaths = Files.newDirectoryStream( - DEVICE_DIR, HIDRAW_DEVICE_GLOB)) { + directory, HIDRAW_DEVICE_GLOB)) { for (Path path : hidrawNodePaths) { result.add(path); } @@ -458,8 +460,8 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { synchronized (mLock) { mScanner = new BrailleDisplayScanner() { @Override - public Collection<Path> getHidrawNodePaths() { - return brailleDisplayMap.keySet(); + public Collection<Path> getHidrawNodePaths(@NonNull Path directory) { + return brailleDisplayMap.isEmpty() ? null : brailleDisplayMap.keySet(); } @Override @@ -490,38 +492,56 @@ class BrailleDisplayConnection extends IBrailleDisplayConnection.Stub { */ @VisibleForTesting interface NativeInterface { + /** + * Returns the HIDRAW descriptor size for the file descriptor. + * + * @return the result of ioctl(HIDIOCGRDESCSIZE), or -1 if the ioctl fails. + */ int getHidrawDescSize(int fd); + /** + * Returns the HIDRAW descriptor for the file descriptor. + * + * @return the result of ioctl(HIDIOCGRDESC), or null if the ioctl fails. + */ byte[] getHidrawDesc(int fd, int descSize); + /** + * Returns the HIDRAW unique identifier for the file descriptor. + * + * @return the result of ioctl(HIDIOCGRAWUNIQ), or null if the ioctl fails. + */ String getHidrawUniq(int fd); + /** + * Returns the HIDRAW bus type for the file descriptor. + * + * @return the result of ioctl(HIDIOCGRAWINFO).bustype, or -1 if the ioctl fails. + */ int getHidrawBusType(int fd); } /** Native interface that actually calls native HIDRAW ioctls. */ - private NativeInterface getDefaultNativeInterface() { - return new NativeInterface() { - @Override - public int getHidrawDescSize(int fd) { - return nativeGetHidrawDescSize(fd); - } + private class DefaultNativeInterface implements NativeInterface { + @Override + public int getHidrawDescSize(int fd) { + return nativeGetHidrawDescSize(fd); + } - @Override - public byte[] getHidrawDesc(int fd, int descSize) { - return nativeGetHidrawDesc(fd, descSize); - } + @Override + public byte[] getHidrawDesc(int fd, int descSize) { + return nativeGetHidrawDesc(fd, descSize); + } - @Override - public String getHidrawUniq(int fd) { - return nativeGetHidrawUniq(fd); - } + @Override + public String getHidrawUniq(int fd) { + return nativeGetHidrawUniq(fd); + } - @Override - public int getHidrawBusType(int fd) { - return nativeGetHidrawBusType(fd); - } - }; + @Override + public int getHidrawBusType(int fd) { + return nativeGetHidrawBusType(fd); + } } private native int nativeGetHidrawDescSize(int fd); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 18e11bab3c54..eb661ce8113e 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -2413,6 +2413,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } AppWidgetProviderInfo info = createPartialProviderInfo(providerId, ri, existing); + + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()) { + // Do not add widget providers for profiles with items restricted on home screen. + if (mUserManager + .getUserProperties(info.getProfile()).areItemsRestrictedOnHomeScreen()) { + return false; + } + } + if (info != null) { if (existing != null) { if (existing.zombie && !mSafeMode) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index bfdcb95b099e..bbd3ad0bf5ce 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -930,7 +930,7 @@ public class ActivityManagerService extends IActivityManager.Stub * Tracks all users with computed color resources by ThemeOverlaycvontroller */ @GuardedBy("this") - private final Set<Integer> mThemeOverlayReadiness = new HashSet<>(); + private final Set<Integer> mThemeOverlayReadyUsers = new HashSet<>(); /** * Tracks association information for a particular package along with debuggability. @@ -2351,7 +2351,7 @@ public class ActivityManagerService extends IActivityManager.Stub mService.startBroadcastObservers(); } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { mService.mPackageWatchdog.onPackagesReady(); - mService.setHomeTimeout(); + mService.scheduleHomeTimeout(); } } @@ -5327,40 +5327,43 @@ public class ActivityManagerService extends IActivityManager.Stub /** * Starts Home if there is no completion signal from ThemeOverlayController */ - private void setHomeTimeout() { + private void scheduleHomeTimeout() { if (enableHomeDelay() && mHasHomeDelay.compareAndSet(false, true)) { + int userId = mUserController.getCurrentUserId(); mHandler.postDelayed(() -> { - if (!getThemeOverlayReadiness()) { + if (!isThemeOverlayReady(userId)) { Slog.d(TAG, "ThemeHomeDelay: ThemeOverlayController not responding, launching " + "Home after " + HOME_LAUNCH_TIMEOUT_MS + "ms"); - setThemeOverlayReady(true); + setThemeOverlayReady(userId); } }, HOME_LAUNCH_TIMEOUT_MS); } } /** - * Used by ThemeOverlayController to notify all listeners for - * color palette readiness. + * Used by ThemeOverlayController to notify when color + * palette is ready. + * + * @param userId The ID of the user where ThemeOverlayController is ready. + * + * @throws RemoteException + * * @hide */ @Override - public void setThemeOverlayReady(boolean readiness) { + public void setThemeOverlayReady(@UserIdInt int userId) { enforceCallingPermission(Manifest.permission.SET_THEME_OVERLAY_CONTROLLER_READY, "setThemeOverlayReady"); - int currentUserId = mUserController.getCurrentUserId(); - - boolean updateReadiness; - synchronized (mThemeOverlayReadiness) { - updateReadiness = readiness ? mThemeOverlayReadiness.add(currentUserId) - : mThemeOverlayReadiness.remove(currentUserId); + boolean updateUser; + synchronized (mThemeOverlayReadyUsers) { + updateUser = mThemeOverlayReadyUsers.add(userId); } - if (updateReadiness && readiness && enableHomeDelay()) { - mAtmInternal.startHomeOnAllDisplays(currentUserId, "setThemeOverlayReady"); + if (updateUser && enableHomeDelay()) { + mAtmInternal.startHomeOnAllDisplays(userId, "setThemeOverlayReady"); } } @@ -5370,10 +5373,9 @@ public class ActivityManagerService extends IActivityManager.Stub * * @hide */ - public boolean getThemeOverlayReadiness() { - int uid = mUserController.getCurrentUserId(); - synchronized (mThemeOverlayReadiness) { - return mThemeOverlayReadiness.contains(uid); + public boolean isThemeOverlayReady(int userId) { + synchronized (mThemeOverlayReadyUsers) { + return mThemeOverlayReadyUsers.contains(userId); } } @@ -18114,8 +18116,8 @@ public class ActivityManagerService extends IActivityManager.Stub // Clean up various services by removing the user mBatteryStatsService.onUserRemoved(userId); - synchronized (mThemeOverlayReadiness) { - mThemeOverlayReadiness.remove(userId); + synchronized (mThemeOverlayReadyUsers) { + mThemeOverlayReadyUsers.remove(userId); } } @@ -19477,8 +19479,8 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public boolean getThemeOverlayReadiness() { - return ActivityManagerService.this.getThemeOverlayReadiness(); + public boolean isThemeOverlayReady(int userId) { + return ActivityManagerService.this.isThemeOverlayReady(userId); } } diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index e4956b348dc9..0ce1407004f5 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -2305,6 +2305,8 @@ public class AppProfiler { } ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); } + EventLogTags.writeAmCpu(st.pid, st.uid, st.baseName, + st.rel_uptime, st.rel_utime, st.rel_stime); } } diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index 0f75ad481662..80387322b038 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -127,6 +127,9 @@ option java_package com.android.server.am 30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1) 30103 am_foreground_service_timed_out (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1) +# Report collection of cpu used by a process +30104 am_cpu (Pid|2|5),(UID|2|5),(Base Name|3),(Uptime|2|3),(Stime|2|3),(Utime|2|3) + # Intent Sender redirect for UserHandle.USER_CURRENT 30110 am_intent_sender_redirect_user (userId|1|5) diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index fca119917fd0..0cc64941cfe6 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -1241,15 +1241,15 @@ public class AppOpsService extends IAppOpsService.Stub { Map<String, PackageState> packageStates) { synchronized (this) { // Remove what may have been added during persistence parsing - for (int i = mUidStates.size() - 1; i >= 0; i--) { - int uid = mUidStates.keyAt(i); + for (int uidIdx = mUidStates.size() - 1; uidIdx >= 0; uidIdx--) { + int uid = mUidStates.keyAt(uidIdx); if (knownUids.get(uid, false)) { if (uid >= Process.FIRST_APPLICATION_UID) { - ArrayMap<String, Ops> pkgOps = mUidStates.valueAt(i).pkgOps; - for (int j = 0; j < pkgOps.size(); j++) { - String pkgName = pkgOps.keyAt(j); + ArrayMap<String, Ops> pkgOps = mUidStates.valueAt(uidIdx).pkgOps; + for (int pkgIdx = pkgOps.size() - 1; pkgIdx >= 0; pkgIdx--) { + String pkgName = pkgOps.keyAt(pkgIdx); if (!packageStates.containsKey(pkgName)) { - pkgOps.removeAt(j); + pkgOps.removeAt(pkgIdx); continue; } AndroidPackage pkg = packageStates.get(pkgName).getAndroidPackage(); @@ -1258,11 +1258,11 @@ public class AppOpsService extends IAppOpsService.Stub { } } if (pkgOps.isEmpty()) { - mUidStates.remove(i); + mUidStates.removeAt(uidIdx); } } } else { - mUidStates.removeAt(i); + mUidStates.removeAt(uidIdx); } } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 559a1d647ea4..ef934004ab2f 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -964,6 +964,8 @@ public class AudioService extends IAudioService.Stub private final HardeningEnforcer mHardeningEnforcer; + private final AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; + private final Object mSupportedSystemUsagesLock = new Object(); @GuardedBy("mSupportedSystemUsagesLock") private @AttributeSystemUsage int[] mSupportedSystemUsages = @@ -974,6 +976,13 @@ public class AudioService extends IAudioService.Stub return "card=" + card + ";device=" + device; } + private static class AudioVolumeGroupHelper extends AudioVolumeGroupHelperBase { + @Override + public List<AudioVolumeGroup> getAudioVolumeGroups() { + return AudioVolumeGroup.getAudioVolumeGroups(); + } + } + public static final class Lifecycle extends SystemService { private AudioService mService; @@ -983,6 +992,7 @@ public class AudioService extends IAudioService.Stub AudioSystemAdapter.getDefaultAdapter(), SystemServerAdapter.getDefaultAdapter(context), SettingsAdapter.getDefaultAdapter(), + new AudioVolumeGroupHelper(), new DefaultAudioPolicyFacade(), null); @@ -1062,16 +1072,19 @@ public class AudioService extends IAudioService.Stub /** * @param context * @param audioSystem Adapter for {@link AudioSystem} - * @param systemServer Adapter for privilieged functionality for system server components + * @param systemServer Adapter for privileged functionality for system server components * @param settings Adapter for {@link Settings} + * @param audioVolumeGroupHelper Adapter for {@link AudioVolumeGroup} + * @param audioPolicy Interface of a facade to IAudioPolicyManager * @param looper Looper to use for the service's message handler. If this is null, an * {@link AudioSystemThread} is created as the messaging thread instead. */ public AudioService(Context context, AudioSystemAdapter audioSystem, SystemServerAdapter systemServer, SettingsAdapter settings, - AudioPolicyFacade audioPolicy, @Nullable Looper looper) { - this (context, audioSystem, systemServer, settings, audioPolicy, looper, - context.getSystemService(AppOpsManager.class), + AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy, + @Nullable Looper looper) { + this (context, audioSystem, systemServer, settings, audioVolumeGroupHelper, + audioPolicy, looper, context.getSystemService(AppOpsManager.class), PermissionEnforcer.fromContext(context)); } @@ -1080,14 +1093,18 @@ public class AudioService extends IAudioService.Stub * @param audioSystem Adapter for {@link AudioSystem} * @param systemServer Adapter for privilieged functionality for system server components * @param settings Adapter for {@link Settings} + * @param audioVolumeGroupHelper Adapter for {@link AudioVolumeGroup} + * @param audioPolicy Interface of a facade to IAudioPolicyManager * @param looper Looper to use for the service's message handler. If this is null, an * {@link AudioSystemThread} is created as the messaging thread instead. + * @param appOps {@link AppOpsManager} system service + * @param enforcer Used for permission enforcing */ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public AudioService(Context context, AudioSystemAdapter audioSystem, SystemServerAdapter systemServer, SettingsAdapter settings, - AudioPolicyFacade audioPolicy, @Nullable Looper looper, AppOpsManager appOps, - @NonNull PermissionEnforcer enforcer) { + AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy, + @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer) { super(enforcer); sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()")); mContext = context; @@ -1096,6 +1113,7 @@ public class AudioService extends IAudioService.Stub mAudioSystem = audioSystem; mSystemServer = systemServer; + mAudioVolumeGroupHelper = audioVolumeGroupHelper; mSettings = settings; mAudioPolicy = audioPolicy; mPlatformType = AudioSystem.getPlatformType(context); @@ -2104,7 +2122,7 @@ public class AudioService extends IAudioService.Stub // verify permissions super.getAudioVolumeGroups_enforcePermission(); - return AudioVolumeGroup.getAudioVolumeGroups(); + return mAudioVolumeGroupHelper.getAudioVolumeGroups(); } private void checkAllAliasStreamVolumes() { @@ -3803,7 +3821,7 @@ public class AudioService extends IAudioService.Stub } /** - * Loops on aliasted stream, update the mute cache attribute of each + * Loops on aliased stream, update the mute cache attribute of each * {@see AudioService#VolumeStreamState}, and then apply the change. * It prevents to unnecessary {@see AudioSystem#setStreamVolume} done for each stream * and aliases before mute change changed and after. @@ -4040,18 +4058,6 @@ public class AudioService extends IAudioService.Stub } } - @Nullable - private AudioVolumeGroup getAudioVolumeGroupById(int volumeGroupId) { - for (AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) { - if (avg.getId() == volumeGroupId) { - return avg; - } - } - - Log.e(TAG, ": invalid volume group id: " + volumeGroupId + " requested"); - return null; - } - @Override @android.annotation.EnforcePermission(anyOf = { MODIFY_AUDIO_SETTINGS_PRIVILEGED, @@ -8252,7 +8258,7 @@ public class AudioService extends IAudioService.Stub index = 1; } // Set the volume index - AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device); + mAudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device); } @GuardedBy("AudioService.VolumeStreamState.class") diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 49ab19a816dc..7202fa286453 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -551,6 +551,11 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, return AudioSystem.setStreamVolumeIndexAS(stream, index, device); } + /** Same as {@link AudioSystem#setVolumeIndexForAttributes(AudioAttributes, int, int)} */ + public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, int device) { + return AudioSystem.setVolumeIndexForAttributes(attributes, index, device); + } + /** * Same as {@link AudioSystem#setPhoneState(int, int)} * @param state diff --git a/services/core/java/com/android/server/audio/AudioVolumeGroupHelperBase.java b/services/core/java/com/android/server/audio/AudioVolumeGroupHelperBase.java new file mode 100644 index 000000000000..6f4de5bc5f81 --- /dev/null +++ b/services/core/java/com/android/server/audio/AudioVolumeGroupHelperBase.java @@ -0,0 +1,34 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.audio; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import android.media.audiopolicy.AudioVolumeGroup; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; + +/** Abstract class for {@link AudioVolumeGroup} related helper methods. */ +@VisibleForTesting(visibility = PACKAGE) +public class AudioVolumeGroupHelperBase { + public List<AudioVolumeGroup> getAudioVolumeGroups() { + return new ArrayList<>(); + } +} diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java index 6a6e6ab23687..c2c82edee33d 100644 --- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java +++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java @@ -16,6 +16,7 @@ package com.android.server.grammaticalinflection; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Configuration; @@ -55,11 +56,11 @@ public abstract class GrammaticalInflectionManagerInternal { * */ public abstract @Configuration.GrammaticalGender int retrieveSystemGrammaticalGender( - Configuration configuration); + @NonNull Configuration configuration); /** * Whether the package can get the system grammatical gender or not. */ - public abstract boolean canGetSystemGrammaticalGender(int uid, String packageName); + public abstract boolean canGetSystemGrammaticalGender(int uid, @Nullable String packageName); } diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java index d01f54f09679..0bcb26d7d0ab 100644 --- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java +++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java @@ -329,8 +329,9 @@ public class GrammaticalInflectionService extends SystemService { private void checkCallerIsSystem() { int callingUid = Binder.getCallingUid(); - if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID) { - throw new SecurityException("Caller is not system and shell."); + if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID + && callingUid != Process.ROOT_UID) { + throw new SecurityException("Caller is not system, shell and root."); } } @@ -354,12 +355,11 @@ public class GrammaticalInflectionService extends SystemService { final File file = getGrammaticalGenderFile(userId); synchronized (mLock) { if (!file.exists()) { - Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file."); + Log.d(TAG, "User " + userId + " doesn't have the grammatical gender file."); return; } if (mGrammaticalGenderCache.indexOfKey(userId) < 0) { - try { - InputStream in = new FileInputStream(file); + try (FileInputStream in = new FileInputStream(file)) { final TypedXmlPullParser parser = Xml.resolvePullParser(in); mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser)); } catch (IOException | XmlPullParserException e) { diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java index 6eec0dee9152..3ffd2e1dec71 100644 --- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java +++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java @@ -34,6 +34,7 @@ import android.util.Slog; import android.util.TypedValue; import android.view.Gravity; import android.view.InputDevice; +import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.RoundedCorner; @@ -335,7 +336,15 @@ public class FocusEventDebugView extends RelativeLayout { final int unicodeChar = event.getUnicodeChar(); if (unicodeChar != 0) { - return new String(Character.toChars(unicodeChar)); + if ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) != 0) { + // Show combining character + final int combiningChar = KeyCharacterMap.getCombiningChar( + unicodeChar & KeyCharacterMap.COMBINING_ACCENT_MASK); + // Return the Unicode dotted circle as part of the label as it is used is used to + // illustrate the effect of a combining marks + return "\u25cc" + String.valueOf((char) combiningChar); + } + return String.valueOf((char) unicodeChar); } final var label = KeyEvent.keyCodeToString(event.getKeyCode()); diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index f97f6d2350bc..3e8af27d2584 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -105,7 +105,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; /** @@ -154,7 +153,7 @@ public class MediaSessionService extends SystemService implements Monitor { private UsageStatsManagerInternal mUsageStatsManagerInternal; /* Maps uid with all user engaging session tokens associated to it */ - private final Map<Integer, Set<MediaSession.Token>> mUserEngagingSessions = new HashMap<>(); + private final SparseArray<Set<MediaSession.Token>> mUserEngagingSessions = new SparseArray<>(); // The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile) // It's always not null after the MediaSessionService is started. @@ -625,29 +624,27 @@ public class MediaSessionService extends SystemService implements Monitor { String packageName = record.getPackageName(); int sessionUid = record.getUid(); - String actionToLog = null; MediaSession.Token token = ((MediaSessionRecord) record).getSessionToken(); if (userEngaged) { - if (!mUserEngagingSessions.containsKey(sessionUid)) { + if (!mUserEngagingSessions.contains(sessionUid)) { mUserEngagingSessions.put(sessionUid, new HashSet<>()); - actionToLog = "start"; + reportUserInteractionEvent(/* action= */ "start", record.getUserId(), packageName); } mUserEngagingSessions.get(sessionUid).add(token); - } else if (mUserEngagingSessions.containsKey(sessionUid)) { + } else if (mUserEngagingSessions.contains(sessionUid)) { mUserEngagingSessions.get(sessionUid).remove(token); if (mUserEngagingSessions.get(sessionUid).isEmpty()) { - actionToLog = "stop"; + reportUserInteractionEvent(/* action= */ "stop", record.getUserId(), packageName); mUserEngagingSessions.remove(sessionUid); } } + } - if (actionToLog != null) { - PersistableBundle extras = new PersistableBundle(); - extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, "android.media"); - extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, actionToLog); - mUsageStatsManagerInternal.reportUserInteractionEvent( - packageName, record.getUserId(), extras); - } + private void reportUserInteractionEvent(String action, int userId, String packageName) { + PersistableBundle extras = new PersistableBundle(); + extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, "android.media"); + extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, action); + mUsageStatsManagerInternal.reportUserInteractionEvent(packageName, userId, extras); } void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage, diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java index 133fc8faf14d..59d3d1746754 100644 --- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java @@ -143,7 +143,8 @@ public class PersistentDataBlockService extends SystemService { // Magic number to mark block device as adhering to the format consumed by this service private static final int PARTITION_TYPE_MARKER = 0x19901873; /** Size of the block reserved for FRP credential, including 4 bytes for the size header. */ - private static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000; + @VisibleForTesting + static final int FRP_CREDENTIAL_RESERVED_SIZE = 1000; /** Maximum size of the FRP credential handle that can be stored. */ @VisibleForTesting static final int MAX_FRP_CREDENTIAL_HANDLE_SIZE = FRP_CREDENTIAL_RESERVED_SIZE - 4; @@ -158,7 +159,8 @@ public class PersistentDataBlockService extends SystemService { /** * Size of the block reserved for Test Harness Mode data, including 4 bytes for the size header. */ - private static final int TEST_MODE_RESERVED_SIZE = 10000; + @VisibleForTesting + static final int TEST_MODE_RESERVED_SIZE = 10000; /** Maximum size of the Test Harness Mode data that can be stored. */ @VisibleForTesting static final int MAX_TEST_MODE_DATA_SIZE = TEST_MODE_RESERVED_SIZE - 4; @@ -393,7 +395,8 @@ public class PersistentDataBlockService extends SystemService { return totalDataSize; } - private long getBlockDeviceSize() { + @VisibleForTesting + long getBlockDeviceSize() { synchronized (mLock) { if (mBlockDeviceSize == -1) { if (mIsFileBacked) { @@ -553,26 +556,33 @@ public class PersistentDataBlockService extends SystemService { channel.write(buf); channel.force(true); - // 3. skip the test mode data and leave it unformatted. + // 3. Write the default FRP secret (all zeros). + if (mFrpEnforced) { + Slog.i(TAG, "Writing FRP secret magic"); + channel.write(ByteBuffer.wrap(FRP_SECRET_MAGIC)); + + Slog.i(TAG, "Writing default FRP secret"); + channel.write(ByteBuffer.allocate(FRP_SECRET_SIZE)); + channel.force(true); + + mFrpActive = false; + } + + // 4. skip the test mode data and leave it unformatted. // This is for a feature that enables testing. channel.position(channel.position() + TEST_MODE_RESERVED_SIZE); - // 4. wipe the FRP_CREDENTIAL explicitly + // 5. wipe the FRP_CREDENTIAL explicitly buf = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE); channel.write(buf); channel.force(true); - // 5. set unlock = 0 because it's a formatPartitionLocked + // 6. set unlock = 0 because it's a formatPartitionLocked buf = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE); buf.put((byte)0); buf.flip(); channel.write(buf); channel.force(true); - - // 6. Write the default FRP secret (all zeros). - if (mFrpEnforced) { - writeFrpMagicAndDefaultSecret(); - } } catch (IOException e) { Slog.e(TAG, "failed to format block", e); return; @@ -616,7 +626,7 @@ public class PersistentDataBlockService extends SystemService { // version. If so, we deactivate FRP and set the secret to the default value. if (isUpgradingFromPreVRelease()) { Slog.w(TAG, "Upgrading from Android 14 or lower, defaulting FRP secret"); - writeFrpMagicAndDefaultSecret(); + writeFrpMagicAndDefaultSecretLocked(); mFrpActive = false; return true; } @@ -630,7 +640,7 @@ public class PersistentDataBlockService extends SystemService { try { return deactivateFrp(Files.readAllBytes(Paths.get(frpSecretFile))); } catch (IOException e) { - Slog.w(TAG, "Failed to read FRP secret file: " + frpSecretFile + " " + Slog.i(TAG, "Failed to read FRP secret file: " + frpSecretFile + " " + e.getClass().getSimpleName()); return false; } @@ -653,7 +663,8 @@ public class PersistentDataBlockService extends SystemService { } /** - * Write the provided secret to the FRP secret file in /data and to the /persist partition. + * Write the provided secret to the FRP secret file in /data and to the persistent data block + * partition. * * Writing is a three-step process, to ensure that we can recover from a crash at any point. */ @@ -713,7 +724,7 @@ public class PersistentDataBlockService extends SystemService { synchronized (mLock) { if (!hasFrpSecretMagic()) { Slog.i(TAG, "No FRP secret magic, system must have been upgraded."); - writeFrpMagicAndDefaultSecret(); + writeFrpMagicAndDefaultSecretLocked(); } } @@ -735,11 +746,9 @@ public class PersistentDataBlockService extends SystemService { } } - private void writeFrpMagicAndDefaultSecret() { + private void writeFrpMagicAndDefaultSecretLocked() { try (FileChannel channel = getBlockOutputChannelIgnoringFrp()) { synchronized (mLock) { - // Write secret first in case we crash between the writes, causing the first write - // to be synced but the second to be lost. Slog.i(TAG, "Writing default FRP secret"); channel.position(getFrpSecretDataOffset()); channel.write(ByteBuffer.allocate(FRP_SECRET_SIZE)); @@ -755,6 +764,7 @@ public class PersistentDataBlockService extends SystemService { } catch (IOException e) { Slog.e(TAG, "Failed to write FRP magic and default secret", e); } + computeAndWriteDigestLocked(); } @VisibleForTesting @@ -879,7 +889,7 @@ public class PersistentDataBlockService extends SystemService { if (printSecret) { try { pw.println("FRP secret in " + frpSecretFile + ": " + HexFormat.of() - .formatHex(Files.readAllBytes(Paths.get(mFrpSecretFile)))); + .formatHex(Files.readAllBytes(Paths.get(frpSecretFile)))); } catch (IOException e) { Slog.e(TAG, "Failed to read " + frpSecretFile, e); } @@ -1230,6 +1240,7 @@ public class PersistentDataBlockService extends SystemService { @Override public boolean setFactoryResetProtectionSecret(byte[] secret) { + enforceConfigureFrpPermission(); enforceUid(Binder.getCallingUid()); if (secret == null || secret.length != FRP_SECRET_SIZE) { throw new IllegalArgumentException( diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index e984e9c255da..23d48e871d62 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -45,6 +45,8 @@ import android.content.Intent; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.content.pm.UserProperties; +import android.multiuser.Flags; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -361,14 +363,13 @@ public final class BroadcastHelper { final UserInfo parent = ums.getProfileParent(userId); final int launcherUserId = (parent != null) ? parent.id : userId; final ComponentName launcherComponent = snapshot.getDefaultHomeActivity(launcherUserId); - if (launcherComponent != null) { + if (launcherComponent != null && canLauncherAccessProfile(launcherComponent, userId)) { Intent launcherIntent = new Intent(PackageInstaller.ACTION_SESSION_COMMITTED) .putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo) .putExtra(Intent.EXTRA_USER, UserHandle.of(userId)) .setPackage(launcherComponent.getPackageName()); mContext.sendBroadcastAsUser(launcherIntent, UserHandle.of(launcherUserId)); } - // TODO(b/122900055) Change/Remove this and replace with new permission role. if (appPredictionServicePackage != null) { Intent predictorIntent = new Intent(PackageInstaller.ACTION_SESSION_COMMITTED) .putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo) @@ -378,6 +379,36 @@ public final class BroadcastHelper { } } + /** + * A Profile is accessible to launcher in question if: + * - It's not hidden for API visibility. + * - Hidden, but launcher application has either + * {@link Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} or + * {@link Manifest.permission.ACCESS_HIDDEN_PROFILES} + * granted. + */ + boolean canLauncherAccessProfile(ComponentName launcherComponent, int userId) { + if (android.os.Flags.allowPrivateProfile() + && Flags.enablePermissionToAccessHiddenProfiles()) { + if (mUmInternal.getUserProperties(userId).getProfileApiVisibility() + != UserProperties.PROFILE_API_VISIBILITY_HIDDEN) { + return true; + } + if (mContext.getPackageManager().checkPermission( + Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL, + launcherComponent.getPackageName()) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + // TODO(b/122900055) Change/Remove this and replace with new permission role. + return mContext.getPackageManager().checkPermission( + Manifest.permission.ACCESS_HIDDEN_PROFILES, + launcherComponent.getPackageName()) + == PackageManager.PERMISSION_GRANTED; + } + return true; + } + void sendPreferredActivityChangedBroadcast(int userId) { mHandler.post(() -> { final IActivityManager am = ActivityManager.getService(); diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index f9d81127fd86..76d87ff65500 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -798,6 +798,10 @@ public class LauncherAppsService extends SystemService { public ParceledListSlice getShortcutConfigActivities( String callingPackage, String packageName, UserHandle user) throws RemoteException { + // Not supported for user-profiles with items restricted on home screen. + if (!mShortcutServiceInternal.areShortcutsSupportedOnHomeScreen(user.getIdentifier())) { + return null; + } return queryActivitiesForUser(callingPackage, new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName), user); } @@ -1256,6 +1260,14 @@ public class LauncherAppsService extends SystemService { @Override public void pinShortcuts(String callingPackage, String packageName, List<String> ids, UserHandle targetUser) { + if (!mShortcutServiceInternal + .areShortcutsSupportedOnHomeScreen(targetUser.getIdentifier())) { + // Requires strict ACCESS_SHORTCUTS permission for user-profiles with items + // restricted on home screen. + ensureStrictAccessShortcutsPermission(callingPackage); + } else { + ensureShortcutPermission(callingPackage); + } ensureShortcutPermission(callingPackage); if (!canAccessProfile(targetUser.getIdentifier(), "Cannot pin shortcuts")) { return; diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index dc97e5fa92af..05e8f9aa24ec 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -122,6 +122,7 @@ import java.util.concurrent.CompletableFuture; public class PackageArchiver { private static final String TAG = "PackageArchiverService"; + private static final boolean DEBUG = true; public static final String EXTRA_UNARCHIVE_INTENT_SENDER = "android.content.pm.extra.UNARCHIVE_INTENT_SENDER"; @@ -203,6 +204,9 @@ public class PackageArchiver { Objects.requireNonNull(intentSender); Objects.requireNonNull(userHandle); + Slog.i(TAG, + TextUtils.formatSimple("Requested archival of package %s for user %s.", packageName, + userHandle.getIdentifier())); Computer snapshot = mPm.snapshotComputer(); int binderUserId = userHandle.getIdentifier(); int binderUid = Binder.getCallingUid(); @@ -227,7 +231,7 @@ public class PackageArchiver { archiveStateStored[i] = createAndStoreArchiveState(packageName, users[i]); } } catch (PackageManager.NameNotFoundException e) { - Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", + Slog.e(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", packageName, e.getMessage())); throw new ParcelableException(e); } @@ -247,7 +251,7 @@ public class PackageArchiver { binderPid) ).exceptionally( e -> { - Slog.d(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", + Slog.e(TAG, TextUtils.formatSimple("Failed to archive %s with message %s", packageName, e.getMessage())); sendFailureStatus(intentSender, packageName, e.getMessage()); return null; @@ -359,6 +363,10 @@ public class PackageArchiver { // TODO(b/319238030) Move this into installd. if (!FileUtils.deleteContentsAndDir(iconsDir)) { Slog.e(TAG, "Failed to clean up archive files for " + packageName); + } else { + if (DEBUG) { + Slog.e(TAG, "Deleted icons at " + iconsDir.getAbsolutePath()); + } } }); } @@ -521,6 +529,9 @@ public class PackageArchiver { } out.flush(); } + if (DEBUG && iconFile.exists()) { + Slog.i(TAG, "Stored icon at " + iconFile.getAbsolutePath()); + } return iconFile.toPath(); } @@ -1191,6 +1202,9 @@ public class PackageArchiver { if (!iconsDir.isDirectory()) { throw new IOException("Unable to create directory " + iconsDir); } + if (DEBUG) { + Slog.i(TAG, "Created icons directory at " + iconsDir.getAbsolutePath()); + } } SELinux.restorecon(iconsDir); return iconsDir; diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 6e4f19925b59..29320aeefde9 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1702,6 +1702,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements Objects.requireNonNull(installerPackageName); Objects.requireNonNull(userHandle); + Slog.i(TAG, + TextUtils.formatSimple("Requested archived install of package %s for user %s.", + archivedPackageParcel.packageName, + userHandle.getIdentifier())); final int callingUid = Binder.getCallingUid(); final int userId = userHandle.getIdentifier(); final Computer snapshot = mPm.snapshotComputer(); @@ -1737,6 +1741,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements session.addFile(LOCATION_DATA_APP, "base", 0 /*lengthBytes*/, metadata.toByteArray(), null /*signature*/); session.commit(statusReceiver, false /*forTransfer*/); + Slog.i(TAG, TextUtils.formatSimple("Installed archived app %s.", + archivedPackageParcel.packageName)); } catch (IOException e) { throw ExceptionUtils.wrap(e); } finally { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 29242aa9f5e1..fe65010b7281 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -5358,9 +5358,16 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile pw.println(sdf.format(date)); if (pus.getArchiveState() != null) { + final ArchiveState archiveState = pus.getArchiveState(); pw.print(" archiveTime="); - date.setTime(pus.getArchiveState().getArchiveTimeMillis()); + date.setTime(archiveState.getArchiveTimeMillis()); pw.println(sdf.format(date)); + pw.print(" unarchiveInstallerTitle="); + pw.println(archiveState.getInstallerTitle()); + for (ArchiveState.ArchiveActivityInfo activity : archiveState.getActivityInfos()) { + pw.print(" archiveActivityInfo="); + pw.println(activity.toString()); + } } pw.print(" uninstallReason="); @@ -5475,10 +5482,6 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } } } - ArchiveState archiveState = userState.getArchiveState(); - if (archiveState != null) { - pw.print(archiveState.toString()); - } } } diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java index 1e1f17894605..47a140a97c96 100644 --- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java +++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java @@ -416,6 +416,10 @@ class ShortcutRequestPinProcessor { @VisibleForTesting Pair<ComponentName, Integer> getRequestPinConfirmationActivity( int callingUserId, int requestType) { + // Pinning is not supported for user-profiles with items restricted on home screen. + if (!mService.areShortcutsSupportedOnHomeScreen(callingUserId)) { + return null; + } // Find the default launcher. final int launcherUserId = mService.getParentOrSelfUserId(callingUserId); final String defaultLauncher = mService.getDefaultLauncher(launcherUserId); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index c23d2abf0853..a600eeabf62b 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -70,6 +70,7 @@ import android.graphics.Canvas; import android.graphics.RectF; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.Icon; +import android.multiuser.Flags; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -2830,6 +2831,26 @@ public class ShortcutService extends IShortcutService.Stub { } } + @VisibleForTesting + boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) { + if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()) { + return true; + } + final long start = getStatStartTime(); + final long token = injectClearCallingIdentity(); + boolean isSupported; + try { + synchronized (mLock) { + isSupported = !mUserManagerInternal.getUserProperties(userId) + .areItemsRestrictedOnHomeScreen(); + } + } finally { + injectRestoreCallingIdentity(token); + logDurationStat(Stats.GET_DEFAULT_LAUNCHER, start); + } + return isSupported; + } + @Nullable String getDefaultLauncher(@UserIdInt int userId) { final long start = getStatStartTime(); @@ -3660,6 +3681,10 @@ public class ShortcutService extends IShortcutService.Stub { callingPid, callingUid); } + public boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) { + return ShortcutService.this.areShortcutsSupportedOnHomeScreen(userId); + } + @Override public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName, int userId) { diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index a4c6959f8ad5..3c6baa873eca 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -255,4 +255,9 @@ public interface StatusBarManagerInternal { * @param tile the ComponentName of the {@link android.service.quicksettings.TileService} */ void removeQsTile(ComponentName tile); + + /** + * Called when requested to enter desktop from an app. + */ + void enterDesktop(int displayId); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index fd316eaf9b96..14c38bde6621 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -830,6 +830,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override + public void enterDesktop(int displayId) { + IStatusBar bar = mBar; + if (bar != null) { + try { + bar.enterDesktop(displayId); + } catch (RemoteException ex) { } + } + } + @Override public void showMediaOutputSwitcher(String packageName) { IStatusBar bar = mBar; if (bar != null) { diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java index ea8a801ff697..3b2e69a97889 100644 --- a/services/core/java/com/android/server/webkit/SystemImpl.java +++ b/services/core/java/com/android/server/webkit/SystemImpl.java @@ -21,6 +21,7 @@ import static android.webkit.Flags.updateServiceV2; import android.app.ActivityManager; import android.app.AppGlobals; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -39,11 +40,14 @@ import android.webkit.WebViewProviderInfo; import android.webkit.WebViewZygote; import com.android.internal.util.XmlUtils; +import com.android.server.LocalServices; +import com.android.server.PinnerService; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -60,6 +64,7 @@ public class SystemImpl implements SystemInterface { private static final String TAG_AVAILABILITY = "availableByDefault"; private static final String TAG_SIGNATURE = "signature"; private static final String TAG_FALLBACK = "isFallback"; + private static final String PIN_GROUP = "webview"; private final WebViewProviderInfo[] mWebViewProviderPackages; // Initialization-on-demand holder idiom for getting the WebView provider packages once and @@ -277,6 +282,36 @@ public class SystemImpl implements SystemInterface { return true; } + @Override + public void pinWebviewIfRequired(ApplicationInfo appInfo) { + PinnerService pinnerService = LocalServices.getService(PinnerService.class); + int webviewPinQuota = pinnerService.getWebviewPinQuota(); + if (webviewPinQuota <= 0) { + return; + } + + pinnerService.unpinGroup(PIN_GROUP); + + ArrayList<String> apksToPin = new ArrayList<>(); + boolean pinSharedFirst = appInfo.metaData.getBoolean("PIN_SHARED_LIBS_FIRST", true); + for (String sharedLib : appInfo.sharedLibraryFiles) { + apksToPin.add(sharedLib); + } + apksToPin.add(appInfo.sourceDir); + if (!pinSharedFirst) { + // We want to prioritize pinning of the native library that is most likely used by apps + // which in some build flavors live in the main apk and as a shared library for others. + Collections.reverse(apksToPin); + } + for (String apk : apksToPin) { + if (webviewPinQuota <= 0) { + break; + } + int bytesPinned = pinnerService.pinFile(apk, webviewPinQuota, appInfo, PIN_GROUP); + webviewPinQuota -= bytesPinned; + } + } + // flags declaring we want extra info from the package manager for webview providers private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA | PackageManager.GET_SIGNATURES | PackageManager.GET_SHARED_LIBRARY_FILES diff --git a/services/core/java/com/android/server/webkit/SystemInterface.java b/services/core/java/com/android/server/webkit/SystemInterface.java index 09c23a7229ad..5ed2cfe4252d 100644 --- a/services/core/java/com/android/server/webkit/SystemInterface.java +++ b/services/core/java/com/android/server/webkit/SystemInterface.java @@ -17,6 +17,7 @@ package com.android.server.webkit; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.webkit.UserPackage; @@ -61,4 +62,6 @@ public interface SystemInterface { /** Start the zygote if it's not already running. */ public void ensureZygoteStarted(); public boolean isMultiProcessDefaultEnabled(); + + public void pinWebviewIfRequired(ApplicationInfo appInfo); } diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index d95b431752ec..1d6ad6d3a6d9 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -17,7 +17,6 @@ package com.android.server.webkit; import android.annotation.Nullable; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; @@ -30,12 +29,8 @@ import android.webkit.WebViewFactory; import android.webkit.WebViewProviderInfo; import android.webkit.WebViewProviderResponse; -import com.android.server.LocalServices; -import com.android.server.PinnerService; - import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -93,8 +88,6 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE; private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE; - private static final String PIN_GROUP = "webview"; - private final SystemInterface mSystemInterface; private final Context mContext; @@ -346,38 +339,6 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { return newPackage; } - private void pinWebviewIfRequired(ApplicationInfo appInfo) { - PinnerService pinnerService = LocalServices.getService(PinnerService.class); - if (pinnerService == null) { - // This happens in unit tests which do not have services. - return; - } - int webviewPinQuota = pinnerService.getWebviewPinQuota(); - if (webviewPinQuota <= 0) { - return; - } - - pinnerService.unpinGroup(PIN_GROUP); - - ArrayList<String> apksToPin = new ArrayList<>(); - boolean pinSharedFirst = appInfo.metaData.getBoolean("PIN_SHARED_LIBS_FIRST", true); - for (String sharedLib : appInfo.sharedLibraryFiles) { - apksToPin.add(sharedLib); - } - apksToPin.add(appInfo.sourceDir); - if (!pinSharedFirst) { - // We want to prioritize pinning of the native library that is most likely used by apps - // which in some build flavors live in the main apk and as a shared library for others. - Collections.reverse(apksToPin); - } - for (String apk : apksToPin) { - if (webviewPinQuota <= 0) { - break; - } - int bytesPinned = pinnerService.pinFile(apk, webviewPinQuota, appInfo, PIN_GROUP); - webviewPinQuota -= bytesPinned; - } - } /** * This is called when we change WebView provider, either when the current provider is * updated or a new provider is chosen / takes precedence. @@ -386,7 +347,7 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { synchronized (mLock) { mAnyWebViewInstalled = true; if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) { - pinWebviewIfRequired(newPackage.applicationInfo); + mSystemInterface.pinWebviewIfRequired(newPackage.applicationInfo); mCurrentWebViewPackage = newPackage; // The relro creations might 'finish' (not start at all) before diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java index 1bc635bd9ca3..b3c8b0b3a47a 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java @@ -17,7 +17,6 @@ package com.android.server.webkit; import android.annotation.Nullable; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; @@ -32,12 +31,8 @@ import android.webkit.WebViewFactory; import android.webkit.WebViewProviderInfo; import android.webkit.WebViewProviderResponse; -import com.android.server.LocalServices; -import com.android.server.PinnerService; - import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -88,10 +83,9 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { private static final int VALIDITY_INCORRECT_SIGNATURE = 3; private static final int VALIDITY_NO_LIBRARY_FLAG = 4; - private static final String PIN_GROUP = "webview"; - private final SystemInterface mSystemInterface; private final Context mContext; + private final WebViewProviderInfo mDefaultProvider; private long mMinimumVersionCode = -1; @@ -112,6 +106,16 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { WebViewUpdateServiceImpl2(Context context, SystemInterface systemInterface) { mContext = context; mSystemInterface = systemInterface; + WebViewProviderInfo[] webviewProviders = getWebViewPackages(); + for (WebViewProviderInfo provider : webviewProviders) { + if (provider.availableByDefault) { + mDefaultProvider = provider; + break; + } + } + // This should be unreachable because the config parser enforces that there is at least one + // availableByDefault provider. + throw new AndroidRuntimeException("No available by default WebView Provider."); } @Override @@ -170,11 +174,10 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { if (mCurrentWebViewPackage == null) { return true; } - WebViewProviderInfo defaultProvider = getDefaultWebViewPackage(); - if (mCurrentWebViewPackage.packageName.equals(defaultProvider.packageName)) { + if (mCurrentWebViewPackage.packageName.equals(mDefaultProvider.packageName)) { List<UserPackage> userPackages = mSystemInterface.getPackageInfoForProviderAllUsers( - mContext, defaultProvider); + mContext, mDefaultProvider); return !isInstalledAndEnabledForAllUsers(userPackages); } else { return false; @@ -207,13 +210,12 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { // default package for all users in case it was disabled, even if we already did the // one-time migration before. If this actually changes the state, we will see the // PackageManager broadcast shortly and try again. - WebViewProviderInfo defaultProvider = getDefaultWebViewPackage(); Slog.w( TAG, "No provider available for all users, trying to enable " - + defaultProvider.packageName); + + mDefaultProvider.packageName); mSystemInterface.enablePackageForAllUsers( - mContext, defaultProvider.packageName, true); + mContext, mDefaultProvider.packageName, true); } } catch (Throwable t) { @@ -356,39 +358,6 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { return newPackage; } - private void pinWebviewIfRequired(ApplicationInfo appInfo) { - PinnerService pinnerService = LocalServices.getService(PinnerService.class); - if (pinnerService == null) { - // This happens in unit tests which do not have services. - return; - } - int webviewPinQuota = pinnerService.getWebviewPinQuota(); - if (webviewPinQuota <= 0) { - return; - } - - pinnerService.unpinGroup(PIN_GROUP); - - ArrayList<String> apksToPin = new ArrayList<>(); - boolean pinSharedFirst = appInfo.metaData.getBoolean("PIN_SHARED_LIBS_FIRST", true); - for (String sharedLib : appInfo.sharedLibraryFiles) { - apksToPin.add(sharedLib); - } - apksToPin.add(appInfo.sourceDir); - if (!pinSharedFirst) { - // We want to prioritize pinning of the native library that is most likely used by apps - // which in some build flavors live in the main apk and as a shared library for others. - Collections.reverse(apksToPin); - } - for (String apk : apksToPin) { - if (webviewPinQuota <= 0) { - break; - } - int bytesPinned = pinnerService.pinFile(apk, webviewPinQuota, appInfo, PIN_GROUP); - webviewPinQuota -= bytesPinned; - } - } - /** * This is called when we change WebView provider, either when the current provider is * updated or a new provider is chosen / takes precedence. @@ -397,7 +366,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { synchronized (mLock) { mAnyWebViewInstalled = true; if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) { - pinWebviewIfRequired(newPackage.applicationInfo); + mSystemInterface.pinWebviewIfRequired(newPackage.applicationInfo); mCurrentWebViewPackage = newPackage; // The relro creations might 'finish' (not start at all) before @@ -438,15 +407,7 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { */ @Override public WebViewProviderInfo getDefaultWebViewPackage() { - WebViewProviderInfo[] webviewProviders = getWebViewPackages(); - for (WebViewProviderInfo provider : webviewProviders) { - if (provider.availableByDefault) { - return provider; - } - } - // This should be unreachable because the config parser enforces that there is at least one - // availableByDefault provider. - throw new AndroidRuntimeException("No available by default WebView Provider."); + return mDefaultProvider; } private static class ProviderAndPackageInfo { @@ -507,14 +468,13 @@ class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface { // User did not choose, or the choice failed; return the default provider even if it is not // installed or enabled for all users. - WebViewProviderInfo defaultProvider = getDefaultWebViewPackage(); try { - PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(defaultProvider); - if (validityResult(defaultProvider, packageInfo) == VALIDITY_OK) { + PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(mDefaultProvider); + if (validityResult(mDefaultProvider, packageInfo) == VALIDITY_OK) { return packageInfo; } } catch (NameNotFoundException e) { - Slog.w(TAG, "Default WebView package (" + defaultProvider.packageName + ") not found"); + Slog.w(TAG, "Default WebView package (" + mDefaultProvider.packageName + ") not found"); } // This should never happen during normal operation (only with modified system images). diff --git a/services/core/java/com/android/server/wm/ActivityCallerState.java b/services/core/java/com/android/server/wm/ActivityCallerState.java index e7972904adaa..fa0b17631b0d 100644 --- a/services/core/java/com/android/server/wm/ActivityCallerState.java +++ b/services/core/java/com/android/server/wm/ActivityCallerState.java @@ -30,6 +30,7 @@ import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Intent; import android.net.Uri; +import android.os.BadParcelableException; import android.os.IBinder; import android.os.UserHandle; import android.util.ArraySet; @@ -43,6 +44,7 @@ import com.android.server.uri.GrantUri; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.ArrayList; import java.util.WeakHashMap; /** @@ -143,8 +145,8 @@ final class ActivityCallerState { final boolean writeMet = callerInfo.mWritableContentUris.contains(grantUri); if (!readMet && !writeMet) { - throw new IllegalArgumentException("The supplied URI wasn't passed at launch: " - + grantUri.uri.toSafeString()); + throw new IllegalArgumentException("The supplied URI wasn't passed at launch in" + + " #getData, #EXTRA_STREAM, nor #getClipData: " + grantUri.uri.toSafeString()); } final boolean checkRead = (modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0; @@ -184,6 +186,18 @@ final class ActivityCallerState { // getData addUriIfContentUri(intent.getData(), uris); + // EXTRA_STREAM + if (intent.hasExtra(Intent.EXTRA_STREAM)) { + final ArrayList<Uri> streams = tryToUnparcelArrayListExtraStreamsUri(intent); + if (streams == null) { + addUriIfContentUri(tryToUnparcelExtraStreamUri(intent), uris); + } else { + for (int i = streams.size() - 1; i >= 0; i--) { + addUriIfContentUri(streams.get(i), uris); + } + } + } + final ClipData clipData = intent.getClipData(); if (clipData == null) return uris; @@ -199,6 +213,33 @@ final class ActivityCallerState { return uris; } + private static Uri tryToUnparcelExtraStreamUri(Intent intent) { + try { + return intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri.class); + } catch (BadParcelableException e) { + // Even though the system "defuses" all the parsed Bundles, i.e. suppresses and logs + // instances of {@link BadParcelableException}, we still want to be on the safer side + // and catch the exception to ensure no breakages happen. If the unparcel fails, the + // item is still preserved with the underlying parcel. + Slog.w(TAG, "Failed to unparcel an URI in EXTRA_STREAM, returning null: " + e); + return null; + } + } + + private static ArrayList<Uri> tryToUnparcelArrayListExtraStreamsUri(Intent intent) { + try { + return intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, Uri.class); + } catch (BadParcelableException e) { + // Even though the system "defuses" all the parsed Bundles, i.e. suppresses and logs + // instances of {@link BadParcelableException}, we still want to be on the safer side + // and catch the exception to ensure no breakages happen. If the unparcel fails, the + // item is still preserved with the underlying parcel. + Slog.w(TAG, "Failed to unparcel an ArrayList of URIs in EXTRA_STREAM, returning null: " + + e); + return null; + } + } + private static void addUriIfContentUri(Uri uri, ArraySet<Uri> uris) { if (uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { uris.add(uri); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c36df8da77ae..f15003966d32 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1846,20 +1846,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLetterboxUiController.onMovedToDisplay(mDisplayContent.getDisplayId()); } - void layoutLetterbox(WindowState winHint) { - mLetterboxUiController.layoutLetterbox(winHint); + void layoutLetterboxIfNeeded(WindowState winHint) { + mLetterboxUiController.layoutLetterboxIfNeeded(winHint); } boolean hasWallpaperBackgroundForLetterbox() { return mLetterboxUiController.hasWallpaperBackgroundForLetterbox(); } - void updateLetterboxSurface(WindowState winHint, Transaction t) { - mLetterboxUiController.updateLetterboxSurface(winHint, t); + void updateLetterboxSurfaceIfNeeded(WindowState winHint, Transaction t) { + mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint, t); } - void updateLetterboxSurface(WindowState winHint) { - mLetterboxUiController.updateLetterboxSurface(winHint); + void updateLetterboxSurfaceIfNeeded(WindowState winHint) { + mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint); } /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ @@ -4546,7 +4546,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } super.removeChild(child); checkKeyguardFlagsChanged(); - updateLetterboxSurface(child); + updateLetterboxSurfaceIfNeeded(child); } void setAppLayoutChanges(int changes, String reason) { @@ -6036,7 +6036,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (destroyedSomething) { final DisplayContent dc = getDisplayContent(); dc.assignWindowLayers(true /*setLayoutNeeded*/); - updateLetterboxSurface(null); + updateLetterboxSurfaceIfNeeded(null); } } @@ -7688,7 +7688,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (mNeedsLetterboxedAnimation) { - updateLetterboxSurface(findMainWindow(), t); + updateLetterboxSurfaceIfNeeded(findMainWindow(), t); mNeedsAnimationBoundsLayer = true; } @@ -7856,7 +7856,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mNeedsAnimationBoundsLayer = false; if (mNeedsLetterboxedAnimation) { mNeedsLetterboxedAnimation = false; - updateLetterboxSurface(findMainWindow(), t); + updateLetterboxSurfaceIfNeeded(findMainWindow(), t); } if (mAnimatingActivityRegistry != null) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e7431723789d..d3acd716aed3 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1127,7 +1127,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final ActivityRecord activity = w.mActivityRecord; if (activity != null && activity.isVisibleRequested()) { - activity.updateLetterboxSurface(w); + activity.updateLetterboxSurfaceIfNeeded(w); final boolean updateAllDrawn = activity.updateDrawnWindowStates(w); if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(activity)) { mTmpUpdateAllDrawn.add(activity); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index edf9da1e0bf5..52435274865a 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -924,21 +924,21 @@ final class LetterboxUiController { return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect); } - void updateLetterboxSurface(WindowState winHint) { - updateLetterboxSurface(winHint, mActivityRecord.getSyncTransaction()); + void updateLetterboxSurfaceIfNeeded(WindowState winHint) { + updateLetterboxSurfaceIfNeeded(winHint, mActivityRecord.getSyncTransaction()); } - void updateLetterboxSurface(WindowState winHint, Transaction t) { + void updateLetterboxSurfaceIfNeeded(WindowState winHint, Transaction t) { if (shouldNotLayoutLetterbox(winHint)) { return; } - layoutLetterbox(winHint); + layoutLetterboxIfNeeded(winHint); if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) { mLetterbox.applySurfaceChanges(t); } } - void layoutLetterbox(WindowState w) { + void layoutLetterboxIfNeeded(WindowState w) { if (shouldNotLayoutLetterbox(w)) { return; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index bf45804192a9..19a9b3fbb368 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1452,7 +1452,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return false; } - if (enableHomeDelay() && !mService.mAmInternal.getThemeOverlayReadiness()) { + if (enableHomeDelay() && !mService.mAmInternal.isThemeOverlayReady(userId)) { Slog.d(TAG, "ThemeHomeDelay: Home launch was deferred."); return false; } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 6acf1f3f84af..ee16a37d6baf 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -28,7 +28,6 @@ import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.am.ProcessList.INVALID_ADJ; -import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission; import static com.android.server.wm.ActivityRecord.State.DESTROYED; import static com.android.server.wm.ActivityRecord.State.DESTROYING; import static com.android.server.wm.ActivityRecord.State.PAUSED; @@ -299,7 +298,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio */ private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER; - private boolean mCanUseSystemGrammaticalGender; + private final boolean mCanUseSystemGrammaticalGender; public WindowProcessController(@NonNull ActivityTaskManagerService atm, @NonNull ApplicationInfo info, String name, int uid, int userId, Object owner, diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6e993b340352..b8f1d156ec1d 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1332,7 +1332,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP updateSourceFrame(windowFrames.mFrame); if (mActivityRecord != null && !mIsChildWindow) { - mActivityRecord.layoutLetterbox(this); + mActivityRecord.layoutLetterboxIfNeeded(this); } mSurfacePlacementNeeded = true; mHaveFrame = true; diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS index b363f545760e..f80156021408 100644 --- a/services/tests/mockingservicestests/src/com/android/server/OWNERS +++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS @@ -2,3 +2,4 @@ per-file *Alarm* = file:/apex/jobscheduler/OWNERS per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS per-file SensitiveContentProtectionManagerServiceTest.java = file:/core/java/android/permission/OWNERS +per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 2d065e263a6f..211a83d8588e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -33,6 +33,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import android.content.ContentResolver; @@ -45,7 +46,6 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; -import android.sysprop.CrashRecoveryProperties; import android.util.ArraySet; import com.android.dx.mockito.inline.extended.ExtendedMockito; @@ -64,6 +64,7 @@ import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; +import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -101,6 +102,7 @@ public class RescuePartyTest { private MockitoSession mSession; private HashMap<String, String> mSystemSettingsMap; + private HashMap<String, String> mCrashRecoveryPropertiesMap; //Records the namespaces wiped by setProperties(). private HashSet<String> mNamespacesWiped; @@ -113,6 +115,9 @@ public class RescuePartyTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private PackageManager mPackageManager; + // Mock only sysprop apis + private PackageWatchdog.BootThreshold mSpyBootThreshold; + @Captor private ArgumentCaptor<DeviceConfig.MonitorCallback> mMonitorCallbackCaptor; @Captor @@ -208,11 +213,12 @@ public class RescuePartyTest { // Mock PackageWatchdog doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog) .when(() -> PackageWatchdog.getInstance(mMockContext)); + mockCrashRecoveryProperties(mMockPackageWatchdog); doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime()); - CrashRecoveryProperties.rescueBootCount(0); - CrashRecoveryProperties.enableRescueParty(true); + setCrashRecoveryPropRescueBootCount(0); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false)); } @@ -255,7 +261,7 @@ public class RescuePartyTest { noteBoot(4); assertTrue(RescueParty.isRebootPropertySet()); - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); noteBoot(5); assertTrue(RescueParty.isFactoryResetPropertySet()); } @@ -280,7 +286,7 @@ public class RescuePartyTest { noteAppCrash(4, true); assertTrue(RescueParty.isRebootPropertySet()); - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); noteAppCrash(5, true); assertTrue(RescueParty.isFactoryResetPropertySet()); } @@ -438,7 +444,7 @@ public class RescuePartyTest { noteBoot(i + 1); } assertFalse(RescueParty.isFactoryResetPropertySet()); - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); noteBoot(LEVEL_FACTORY_RESET + 1); assertTrue(RescueParty.isAttemptingFactoryReset()); assertTrue(RescueParty.isFactoryResetPropertySet()); @@ -456,7 +462,7 @@ public class RescuePartyTest { noteBoot(mitigationCount++); assertFalse(RescueParty.isFactoryResetPropertySet()); noteBoot(mitigationCount++); - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); noteBoot(mitigationCount + 1); assertTrue(RescueParty.isAttemptingFactoryReset()); assertTrue(RescueParty.isFactoryResetPropertySet()); @@ -464,10 +470,10 @@ public class RescuePartyTest { @Test public void testThrottlingOnBootFailures() { - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); long now = System.currentTimeMillis(); long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1); - CrashRecoveryProperties.lastFactoryResetTimeMs(beforeTimeout); + setCrashRecoveryPropLastFactoryReset(beforeTimeout); for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) { noteBoot(i); } @@ -476,10 +482,10 @@ public class RescuePartyTest { @Test public void testThrottlingOnAppCrash() { - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); long now = System.currentTimeMillis(); long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1); - CrashRecoveryProperties.lastFactoryResetTimeMs(beforeTimeout); + setCrashRecoveryPropLastFactoryReset(beforeTimeout); for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) { noteAppCrash(i + 1, true); } @@ -488,10 +494,10 @@ public class RescuePartyTest { @Test public void testNotThrottlingAfterTimeoutOnBootFailures() { - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); long now = System.currentTimeMillis(); long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1); - CrashRecoveryProperties.lastFactoryResetTimeMs(afterTimeout); + setCrashRecoveryPropLastFactoryReset(afterTimeout); for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) { noteBoot(i); } @@ -499,10 +505,10 @@ public class RescuePartyTest { } @Test public void testNotThrottlingAfterTimeoutOnAppCrash() { - CrashRecoveryProperties.attemptingReboot(false); + setCrashRecoveryPropAttemptingReboot(false); long now = System.currentTimeMillis(); long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1); - CrashRecoveryProperties.lastFactoryResetTimeMs(afterTimeout); + setCrashRecoveryPropLastFactoryReset(afterTimeout); for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) { noteAppCrash(i + 1, true); } @@ -525,26 +531,26 @@ public class RescuePartyTest { @Test public void testExplicitlyEnablingAndDisablingRescue() { - CrashRecoveryProperties.enableRescueParty(false); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true)); assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false); - CrashRecoveryProperties.enableRescueParty(true); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1)); } @Test public void testDisablingRescueByDeviceConfigFlag() { - CrashRecoveryProperties.enableRescueParty(false); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true)); assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false); // Restore the property value initialized in SetUp() - CrashRecoveryProperties.enableRescueParty(true); + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false)); } @@ -753,4 +759,138 @@ public class RescuePartyTest { RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage( packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount); } + + // Mock CrashRecoveryProperties as they cannot be accessed due to SEPolicy restrictions + private void mockCrashRecoveryProperties(PackageWatchdog watchdog) { + // mock properties in RescueParty + try { + + doAnswer((Answer<Boolean>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.attempting_factory_reset", "false"); + return Boolean.parseBoolean(storedValue); + }).when(() -> RescueParty.isFactoryResetPropertySet()); + doAnswer((Answer<Void>) invocationOnMock -> { + boolean value = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_factory_reset", + Boolean.toString(value)); + return null; + }).when(() -> RescueParty.setFactoryResetProperty(anyBoolean())); + + doAnswer((Answer<Boolean>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.attempting_reboot", "false"); + return Boolean.parseBoolean(storedValue); + }).when(() -> RescueParty.isRebootPropertySet()); + doAnswer((Answer<Void>) invocationOnMock -> { + boolean value = invocationOnMock.getArgument(0); + setCrashRecoveryPropAttemptingReboot(value); + return null; + }).when(() -> RescueParty.setRebootProperty(anyBoolean())); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("persist.crashrecovery.last_factory_reset", "0"); + return Long.parseLong(storedValue); + }).when(() -> RescueParty.getLastFactoryResetTimeMs()); + doAnswer((Answer<Void>) invocationOnMock -> { + long value = invocationOnMock.getArgument(0); + setCrashRecoveryPropLastFactoryReset(value); + return null; + }).when(() -> RescueParty.setLastFactoryResetTimeMs(anyLong())); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.max_rescue_level_attempted", "0"); + return Integer.parseInt(storedValue); + }).when(() -> RescueParty.getMaxRescueLevelAttempted()); + doAnswer((Answer<Void>) invocationOnMock -> { + int value = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.max_rescue_level_attempted", + Integer.toString(value)); + return null; + }).when(() -> RescueParty.setMaxRescueLevelAttempted(anyInt())); + + } catch (Exception e) { + // tests will fail, just printing the error + System.out.println("Error while mocking crashrecovery properties " + e.getMessage()); + } + + // mock properties in BootThreshold + try { + mSpyBootThreshold = spy(watchdog.new BootThreshold( + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); + mCrashRecoveryPropertiesMap = new HashMap<>(); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.rescue_boot_count", "0"); + return Integer.parseInt(storedValue); + }).when(mSpyBootThreshold).getCount(); + doAnswer((Answer<Void>) invocationOnMock -> { + int count = invocationOnMock.getArgument(0); + setCrashRecoveryPropRescueBootCount(count); + return null; + }).when(mSpyBootThreshold).setCount(anyInt()); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.boot_mitigation_count", "0"); + return Integer.parseInt(storedValue); + }).when(mSpyBootThreshold).getMitigationCount(); + doAnswer((Answer<Void>) invocationOnMock -> { + int count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_count", + Integer.toString(count)); + return null; + }).when(mSpyBootThreshold).setMitigationCount(anyInt()); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.rescue_boot_start", "0"); + return Long.parseLong(storedValue); + }).when(mSpyBootThreshold).getStart(); + doAnswer((Answer<Void>) invocationOnMock -> { + long count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_start", + Long.toString(count)); + return null; + }).when(mSpyBootThreshold).setStart(anyLong()); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.boot_mitigation_start", "0"); + return Long.parseLong(storedValue); + }).when(mSpyBootThreshold).getMitigationStart(); + doAnswer((Answer<Void>) invocationOnMock -> { + long count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_start", + Long.toString(count)); + return null; + }).when(mSpyBootThreshold).setMitigationStart(anyLong()); + + Field mBootThresholdField = watchdog.getClass().getDeclaredField("mBootThreshold"); + mBootThresholdField.setAccessible(true); + mBootThresholdField.set(watchdog, mSpyBootThreshold); + } catch (Exception e) { + // tests will fail, just printing the error + System.out.println("Error while spying BootThreshold " + e.getMessage()); + } + } + + private void setCrashRecoveryPropRescueBootCount(int count) { + mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_count", + Integer.toString(count)); + } + + private void setCrashRecoveryPropAttemptingReboot(boolean value) { + mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_reboot", + Boolean.toString(value)); + } + + private void setCrashRecoveryPropLastFactoryReset(long value) { + mCrashRecoveryPropertiesMap.put("persist.crashrecovery.last_factory_reset", + Long.toString(value)); + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java index 7c278cedbcc5..b322dd709c2d 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/BrailleDisplayConnectionTest.java @@ -18,22 +18,31 @@ package com.android.server.accessibility; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.accessibilityservice.BrailleDisplayController; +import android.content.Context; import android.os.Bundle; +import android.os.IBinder; import android.testing.DexmakerShareClassLoaderRule; +import androidx.test.platform.app.InstrumentationRegistry; + import com.google.common.truth.Expect; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.io.File; import java.nio.file.Path; import java.util.List; @@ -54,6 +63,8 @@ public class BrailleDisplayConnectionTest { @Rule public final Expect expect = Expect.create(); + private Context mContext; + // To mock package-private class @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = @@ -62,7 +73,34 @@ public class BrailleDisplayConnectionTest { @Before public void setup() { MockitoAnnotations.initMocks(this); - mBrailleDisplayConnection = new BrailleDisplayConnection(new Object(), mServiceConnection); + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + when(mServiceConnection.isConnectedLocked()).thenReturn(true); + mBrailleDisplayConnection = + spy(new BrailleDisplayConnection(new Object(), mServiceConnection)); + } + + @Test + public void defaultNativeScanner_getHidrawNodePaths_returnsHidrawPaths() throws Exception { + File testDir = mContext.getFilesDir(); + Path hidrawNode0 = Path.of(testDir.getPath(), "hidraw0"); + Path hidrawNode1 = Path.of(testDir.getPath(), "hidraw1"); + Path otherDevice = Path.of(testDir.getPath(), "otherDevice"); + Path[] nodePaths = {hidrawNode0, hidrawNode1, otherDevice}; + try { + for (Path node : nodePaths) { + assertThat(node.toFile().createNewFile()).isTrue(); + } + + BrailleDisplayConnection.BrailleDisplayScanner scanner = + mBrailleDisplayConnection.getDefaultNativeScanner(mNativeInterface); + + assertThat(scanner.getHidrawNodePaths(testDir.toPath())) + .containsExactly(hidrawNode0, hidrawNode1); + } finally { + for (Path node : nodePaths) { + node.toFile().delete(); + } + } } @Test @@ -123,9 +161,38 @@ public class BrailleDisplayConnectionTest { .isEqualTo(BrailleDisplayConnection.BUS_BLUETOOTH); } + @Test + public void write_bypassesServiceSideCheckWithLargeBuffer_disconnects() { + Mockito.doNothing().when(mBrailleDisplayConnection).disconnect(); + mBrailleDisplayConnection.write( + new byte[IBinder.getSuggestedMaxIpcSizeBytes() * 2]); + + verify(mBrailleDisplayConnection).disconnect(); + } + + @Test + public void write_notConnected_throwsIllegalStateException() { + when(mServiceConnection.isConnectedLocked()).thenReturn(false); + + assertThrows(IllegalStateException.class, + () -> mBrailleDisplayConnection.write(new byte[1])); + } + + @Test + public void write_unableToCreateWriteStream_disconnects() { + Mockito.doNothing().when(mBrailleDisplayConnection).disconnect(); + // mBrailleDisplayConnection#connectLocked was never called so the + // connection's mHidrawNode is still null. This will throw an exception + // when attempting to create FileOutputStream on the node. + mBrailleDisplayConnection.write(new byte[1]); + + verify(mBrailleDisplayConnection).disconnect(); + } + // BrailleDisplayConnection#setTestData() is used to enable CTS testing with // test Braille display data, but its own implementation should also be tested // so that issues in this helper don't cause confusing failures in CTS. + @Test public void setTestData_scannerReturnsTestData() { Bundle bd1 = new Bundle(), bd2 = new Bundle(); @@ -148,7 +215,7 @@ public class BrailleDisplayConnectionTest { BrailleDisplayConnection.BrailleDisplayScanner scanner = mBrailleDisplayConnection.setTestData(List.of(bd1, bd2)); - expect.that(scanner.getHidrawNodePaths()).containsExactly(path1, path2); + expect.that(scanner.getHidrawNodePaths(Path.of("/dev"))).containsExactly(path1, path2); expect.that(scanner.getDeviceReportDescriptor(path1)).isEqualTo(desc1); expect.that(scanner.getDeviceReportDescriptor(path2)).isEqualTo(desc2); expect.that(scanner.getUniqueId(path1)).isEqualTo(uniq1); @@ -156,4 +223,12 @@ public class BrailleDisplayConnectionTest { expect.that(scanner.getDeviceBusType(path1)).isEqualTo(bus1); expect.that(scanner.getDeviceBusType(path2)).isEqualTo(bus2); } + + @Test + public void setTestData_emptyTestData_returnsNullNodePaths() { + BrailleDisplayConnection.BrailleDisplayScanner scanner = + mBrailleDisplayConnection.setTestData(List.of()); + + expect.that(scanner.getHidrawNodePaths(Path.of("/dev"))).isNull(); + } } diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java index fc5819de861f..e756082bc912 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java @@ -69,6 +69,7 @@ public class AbsoluteVolumeBehaviorTest { private AudioSystemAdapter mSpyAudioSystem; private SystemServerAdapter mSystemServer; private SettingsAdapter mSettingsAdapter; + private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; private TestLooper mTestLooper; private AudioService mAudioService; @@ -93,9 +94,11 @@ public class AbsoluteVolumeBehaviorTest { mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); mSystemServer = new NoOpSystemServerAdapter(); mSettingsAdapter = new NoOpSettingsAdapter(); + mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase(); mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer, - mSettingsAdapter, mMockAudioPolicy, mTestLooper.getLooper()) { + mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, + mTestLooper.getLooper()) { @Override public int getDeviceForStream(int stream) { return AudioSystem.DEVICE_OUT_SPEAKER; diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java index d4d312894053..3623012b348f 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java @@ -56,6 +56,7 @@ public class AudioDeviceVolumeManagerTest { private AudioSystemAdapter mSpyAudioSystem; private SystemServerAdapter mSystemServer; private SettingsAdapter mSettingsAdapter; + private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; private TestLooper mTestLooper; private AudioPolicyFacade mAudioPolicyMock = mock(AudioPolicyFacade.class); @@ -71,8 +72,10 @@ public class AudioDeviceVolumeManagerTest { mSystemServer = new NoOpSystemServerAdapter(); mSettingsAdapter = new NoOpSettingsAdapter(); + mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase(); mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer, - mSettingsAdapter, mAudioPolicyMock, mTestLooper.getLooper()) { + mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock, + mTestLooper.getLooper()) { @Override public int getDeviceForStream(int stream) { return AudioSystem.DEVICE_OUT_SPEAKER; @@ -82,8 +85,9 @@ public class AudioDeviceVolumeManagerTest { mTestLooper.dispatchAll(); } + // ------------ AudioDeviceVolumeManager related tests ------------ @Test - public void testSetDeviceVolume() { + public void setDeviceVolume_checkIndex() { AudioManager am = mContext.getSystemService(AudioManager.class); final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC); final int maxIndex = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); @@ -110,7 +114,7 @@ public class AudioDeviceVolumeManagerTest { @Test @RequiresFlagsDisabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) - public void testConfigurablePreScaleAbsoluteVolume() throws Exception { + public void configurablePreScaleAbsoluteVolume_checkIndex() throws Exception { AudioManager am = mContext.getSystemService(AudioManager.class); final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC); final int maxIndex = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); @@ -159,7 +163,7 @@ public class AudioDeviceVolumeManagerTest { @Test @RequiresFlagsEnabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) - public void testDisablePreScaleAbsoluteVolume() throws Exception { + public void disablePreScaleAbsoluteVolume_checkIndex() throws Exception { AudioManager am = mContext.getSystemService(AudioManager.class); final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC); final int maxIndex = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java index e565faa1c00b..634877eb2539 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java @@ -60,6 +60,7 @@ public class AudioServiceTest { private Context mContext; private AudioSystemAdapter mSpyAudioSystem; private SettingsAdapter mSettingsAdapter; + private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; @Spy private NoOpSystemServerAdapter mSpySystemServer; @Mock private AppOpsManager mMockAppOpsManager; @@ -80,11 +81,12 @@ public class AudioServiceTest { mContext = InstrumentationRegistry.getTargetContext(); mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); mSettingsAdapter = new NoOpSettingsAdapter(); + mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase(); when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), anyString(), anyString())) .thenReturn(AppOpsManager.MODE_ALLOWED); mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer, - mSettingsAdapter, mMockAudioPolicy, null, mMockAppOpsManager, - mMockPermissionEnforcer); + mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null, + mMockAppOpsManager, mMockPermissionEnforcer); } /** diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java index f5862acb2811..8dfcc1843fed 100644 --- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java @@ -50,6 +50,7 @@ public class DeviceVolumeBehaviorTest { private AudioSystemAdapter mAudioSystem; private SystemServerAdapter mSystemServer; private SettingsAdapter mSettingsAdapter; + private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; private TestLooper mTestLooper; private AudioPolicyFacade mAudioPolicyMock = mock(AudioPolicyFacade.class); @@ -71,8 +72,10 @@ public class DeviceVolumeBehaviorTest { mAudioSystem = new NoOpAudioSystemAdapter(); mSystemServer = new NoOpSystemServerAdapter(); mSettingsAdapter = new NoOpSettingsAdapter(); + mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase(); mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer, - mSettingsAdapter, mAudioPolicyMock, mTestLooper.getLooper()); + mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock, + mTestLooper.getLooper()); mTestLooper.dispatchAll(); } diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java index 0eac718c2f14..96ac5d251ffd 100644 --- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java +++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java @@ -137,6 +137,11 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter { } @Override + public int setVolumeIndexForAttributes(AudioAttributes attributes, int index, int device) { + return AudioSystem.AUDIO_STATUS_OK; + } + + @Override @NonNull public ArrayList<AudioDeviceAttributes> getDevicesForAttributes( @NonNull AudioAttributes attributes, boolean forVolume) { diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java new file mode 100644 index 000000000000..83bbd0e3f014 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java @@ -0,0 +1,726 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import static android.media.AudioManager.ADJUST_LOWER; +import static android.media.AudioManager.ADJUST_MUTE; +import static android.media.AudioManager.ADJUST_RAISE; +import static android.media.AudioManager.DEVICE_OUT_BLE_SPEAKER; +import static android.media.AudioManager.DEVICE_OUT_BLUETOOTH_SCO; +import static android.media.AudioManager.DEVICE_OUT_SPEAKER; +import static android.media.AudioManager.DEVICE_OUT_USB_DEVICE; +import static android.media.AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET; +import static android.media.AudioManager.FLAG_ALLOW_RINGER_MODES; +import static android.media.AudioManager.FLAG_BLUETOOTH_ABS_VOLUME; +import static android.media.AudioManager.RINGER_MODE_NORMAL; +import static android.media.AudioManager.RINGER_MODE_VIBRATE; +import static android.media.AudioManager.STREAM_ACCESSIBILITY; +import static android.media.AudioManager.STREAM_ALARM; +import static android.media.AudioManager.STREAM_BLUETOOTH_SCO; +import static android.media.AudioManager.STREAM_MUSIC; +import static android.media.AudioManager.STREAM_NOTIFICATION; +import static android.media.AudioManager.STREAM_RING; +import static android.media.AudioManager.STREAM_SYSTEM; +import static android.media.AudioManager.STREAM_VOICE_CALL; +import static android.view.KeyEvent.ACTION_DOWN; +import static android.view.KeyEvent.KEYCODE_VOLUME_UP; + +import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.content.Context; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.media.IDeviceVolumeBehaviorDispatcher; +import android.media.VolumeInfo; +import android.media.audiopolicy.AudioVolumeGroup; +import android.os.Looper; +import android.os.PermissionEnforcer; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.util.SparseIntArray; +import android.view.KeyEvent; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@Presubmit +public class VolumeHelperTest { + private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, ""); + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + + private MyAudioService mAudioService; + + private AudioManager mAm; + + private Context mContext; + + private AudioSystemAdapter mSpyAudioSystem; + private SettingsAdapter mSettingsAdapter; + @Spy + private NoOpSystemServerAdapter mSpySystemServer; + @Mock + private AppOpsManager mMockAppOpsManager; + @Mock + private PermissionEnforcer mMockPermissionEnforcer; + @Mock + private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper; + + private final AudioPolicyFacade mFakeAudioPolicy = lookbackAudio -> false; + + private AudioVolumeGroup mAudioMusicVolumeGroup; + + private TestLooper mTestLooper; + + public static final int[] BASIC_VOLUME_BEHAVIORS = { + AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED + }; + + private static class MyAudioService extends AudioService { + private final SparseIntArray mStreamDevice = new SparseIntArray(); + + MyAudioService(Context context, AudioSystemAdapter audioSystem, + SystemServerAdapter systemServer, SettingsAdapter settings, + AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy, + @Nullable Looper looper, AppOpsManager appOps, + @NonNull PermissionEnforcer enforcer) { + super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper, + audioPolicy, looper, appOps, enforcer); + } + + public void setDeviceForStream(int stream, int device) { + mStreamDevice.put(stream, device); + } + + @Override + public int getDeviceForStream(int stream) { + if (mStreamDevice.indexOfKey(stream) < 0) { + return DEVICE_OUT_SPEAKER; + } + return mStreamDevice.get(stream); + } + } + + private static class TestDeviceVolumeBehaviorDispatcherStub + extends IDeviceVolumeBehaviorDispatcher.Stub { + + private AudioDeviceAttributes mDevice; + private int mVolumeBehavior; + private int mTimesCalled; + + @Override + public void dispatchDeviceVolumeBehaviorChanged(@NonNull AudioDeviceAttributes device, + @AudioManager.DeviceVolumeBehavior int volumeBehavior) { + mDevice = device; + mVolumeBehavior = volumeBehavior; + mTimesCalled++; + } + + public void reset() { + mTimesCalled = 0; + mVolumeBehavior = DEVICE_VOLUME_BEHAVIOR_UNSET; + } + } + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mTestLooper = new TestLooper(); + + mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); + mSettingsAdapter = new NoOpSettingsAdapter(); + + mAudioMusicVolumeGroup = getStreamTypeVolumeGroup(STREAM_MUSIC); + if (mAudioMusicVolumeGroup != null) { + when(mAudioVolumeGroupHelper.getAudioVolumeGroups()).thenReturn( + List.of(mAudioMusicVolumeGroup)); + } + + mAm = mContext.getSystemService(AudioManager.class); + + mAudioService = new MyAudioService(mContext, mSpyAudioSystem, mSpySystemServer, + mSettingsAdapter, mAudioVolumeGroupHelper, mFakeAudioPolicy, + mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer); + + mTestLooper.dispatchAll(); + prepareAudioServiceState(); + mTestLooper.dispatchAll(); + + reset(mSpyAudioSystem); + + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .adoptShellPermissionIdentity(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED, + Manifest.permission.MODIFY_AUDIO_ROUTING, + Manifest.permission.MODIFY_PHONE_STATE, + android.Manifest.permission.STATUS_BAR_SERVICE); + } + + private void prepareAudioServiceState() throws Exception { + int[] usedStreamTypes = + {STREAM_MUSIC, STREAM_NOTIFICATION, STREAM_RING, STREAM_ALARM, STREAM_SYSTEM, + STREAM_VOICE_CALL, STREAM_ACCESSIBILITY}; + for (int streamType : usedStreamTypes) { + final int streamVolume = (mAm.getStreamMinVolume(streamType) + mAm.getStreamMaxVolume( + streamType)) / 2; + + mAudioService.setStreamVolume(streamType, streamVolume, /*flags=*/0, + mContext.getOpPackageName()); + } + + mAudioService.setRingerModeInternal(RINGER_MODE_NORMAL, mContext.getOpPackageName()); + mAudioService.setRingerModeExternal(RINGER_MODE_NORMAL, mContext.getOpPackageName()); + } + + private AudioVolumeGroup getStreamTypeVolumeGroup(int streamType) { + // get the volume group from the AudioManager to pass permission checks + // when requesting from real service + final List<AudioVolumeGroup> audioVolumeGroups = AudioManager.getAudioVolumeGroups(); + for (AudioVolumeGroup vg : audioVolumeGroups) { + for (int stream : vg.getLegacyStreamTypes()) { + if (stream == streamType) { + return vg; + } + } + } + + return null; + } + + @After + public void tearDown() { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .dropShellPermissionIdentity(); + } + + // --------------- Volume Stream APIs --------------- + @Test + public void setStreamVolume_callsASSetStreamVolumeIndex() throws Exception { + int newIndex = circularNoMinMaxIncrementVolume(STREAM_MUSIC); + + mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); + mAudioService.setStreamVolume(STREAM_MUSIC, newIndex, /*flags=*/0, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem).setStreamVolumeIndexAS( + eq(STREAM_MUSIC), eq(newIndex), eq(DEVICE_OUT_USB_DEVICE)); + } + + @Test + public void setStreamRingVolume0_setsRingerModeVibrate() throws Exception { + mAudioService.setStreamVolume(STREAM_RING, 0, /*flags=*/0, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + assertEquals(RINGER_MODE_VIBRATE, mAudioService.getRingerModeExternal()); + } + + @Test + public void adjustStreamVolume_callsASSetStreamVolumeIndex() throws Exception { + mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); + mAudioService.adjustStreamVolume(STREAM_MUSIC, ADJUST_LOWER, /*flags=*/0, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + eq(STREAM_MUSIC), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + } + + @Test + public void handleVolumeKey_callsASSetStreamVolumeIndex() throws Exception { + final KeyEvent keyEvent = new KeyEvent(ACTION_DOWN, KEYCODE_VOLUME_UP); + + mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); + mAudioService.handleVolumeKey(keyEvent, /*isOnTv=*/false, mContext.getOpPackageName(), + "adjustSuggestedStreamVolume_callsAudioSystemSetStreamVolumeIndex"); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem).setStreamVolumeIndexAS( + eq(STREAM_MUSIC), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + } + + // --------------- Volume Group APIs --------------- + + @Test + public void setVolumeGroupVolumeIndex_callsASSetVolumeIndexForAttributes() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); + mAudioService.setVolumeGroupVolumeIndex(mAudioMusicVolumeGroup.getId(), + circularNoMinMaxIncrementVolume(STREAM_MUSIC), /*flags=*/0, + mContext.getOpPackageName(), /*attributionTag*/null); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem).setVolumeIndexForAttributes( + any(), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + } + + @Test + public void adjustVolumeGroupVolume_callsASSetVolumeIndexForAttributes() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + mAudioService.setDeviceForStream(STREAM_MUSIC, DEVICE_OUT_USB_DEVICE); + mAudioService.adjustVolumeGroupVolume(mAudioMusicVolumeGroup.getId(), + ADJUST_LOWER, /*flags=*/0, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem).setVolumeIndexForAttributes( + any(), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + } + + @Test + public void check_getVolumeGroupVolumeIndex() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + int newIndex = circularNoMinMaxIncrementVolume(STREAM_MUSIC); + + mAudioService.setVolumeGroupVolumeIndex(mAudioMusicVolumeGroup.getId(), + newIndex, /*flags=*/0, mContext.getOpPackageName(), /*attributionTag*/null); + mTestLooper.dispatchAll(); + + assertEquals(mAudioService.getVolumeGroupVolumeIndex(mAudioMusicVolumeGroup.getId()), + newIndex); + assertEquals(mAudioService.getStreamVolume(STREAM_MUSIC), + newIndex); + } + + @Test + public void check_getVolumeGroupMaxVolumeIndex() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + assertEquals(mAudioService.getVolumeGroupMaxVolumeIndex(mAudioMusicVolumeGroup.getId()), + mAudioService.getStreamMaxVolume(STREAM_MUSIC)); + } + + @Test + public void check_getVolumeGroupMinVolumeIndex() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + assertEquals(mAudioService.getVolumeGroupMinVolumeIndex(mAudioMusicVolumeGroup.getId()), + mAudioService.getStreamMinVolume(STREAM_MUSIC)); + } + + @Test + public void check_getLastAudibleVolumeForVolumeGroup() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + assertEquals( + mAudioService.getLastAudibleVolumeForVolumeGroup(mAudioMusicVolumeGroup.getId()), + mAudioService.getLastAudibleStreamVolume(STREAM_MUSIC)); + } + + @Test + public void check_isVolumeGroupMuted() throws Exception { + assumeNotNull(mAudioMusicVolumeGroup); + + assertEquals(mAudioService.isVolumeGroupMuted(mAudioMusicVolumeGroup.getId()), + mAudioService.isStreamMute(STREAM_MUSIC)); + } + + // ------------------------- Mute Tests ------------------------ + + @Test + public void check_setMasterMute() { + mAudioService.setMasterMute(true, /*flags=*/0, mContext.getOpPackageName(), + mContext.getUserId(), /*attributionTag*/""); + + assertTrue(mAudioService.isMasterMute()); + } + + @Test + public void check_isStreamAffectedByMute() { + assertFalse(mAudioService.isStreamAffectedByMute(STREAM_VOICE_CALL)); + } + + // --------------------- Volume Flag Check -------------------- + + @Test + public void flagAbsVolume_onBtDevice_changesVolume() throws Exception { + mAudioService.setDeviceForStream(STREAM_NOTIFICATION, DEVICE_OUT_BLE_SPEAKER); + + int newIndex = circularNoMinMaxIncrementVolume(STREAM_NOTIFICATION); + mAudioService.setStreamVolume(STREAM_NOTIFICATION, newIndex, FLAG_BLUETOOTH_ABS_VOLUME, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + verify(mSpyAudioSystem).setStreamVolumeIndexAS( + eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER)); + + reset(mSpyAudioSystem); + mAudioService.adjustStreamVolume(STREAM_NOTIFICATION, ADJUST_LOWER, + FLAG_BLUETOOTH_ABS_VOLUME, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + verify(mSpyAudioSystem).setStreamVolumeIndexAS( + eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER)); + } + + @Test + public void flagAbsVolume_onNonBtDevice_noVolumeChange() throws Exception { + mAudioService.setDeviceForStream(STREAM_NOTIFICATION, DEVICE_OUT_SPEAKER); + + int newIndex = circularNoMinMaxIncrementVolume(STREAM_NOTIFICATION); + mAudioService.setStreamVolume(STREAM_NOTIFICATION, newIndex, FLAG_BLUETOOTH_ABS_VOLUME, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS( + eq(STREAM_NOTIFICATION), eq(newIndex), eq(DEVICE_OUT_BLE_SPEAKER)); + + mAudioService.adjustStreamVolume(STREAM_NOTIFICATION, ADJUST_LOWER, + FLAG_BLUETOOTH_ABS_VOLUME, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS( + eq(STREAM_NOTIFICATION), anyInt(), eq(DEVICE_OUT_BLE_SPEAKER)); + } + + @Test + public void flagAllowRingerModes_onSystemStreams_changesMode() throws Exception { + mAudioService.setStreamVolume(STREAM_SYSTEM, + mAudioService.getStreamMinVolume(STREAM_SYSTEM), /*flags=*/0, + mContext.getOpPackageName()); + mAudioService.setRingerModeInternal(RINGER_MODE_VIBRATE, mContext.getOpPackageName()); + + mAudioService.adjustStreamVolume(STREAM_SYSTEM, ADJUST_RAISE, FLAG_ALLOW_RINGER_MODES, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + assertNotEquals(mAudioService.getRingerModeInternal(), RINGER_MODE_VIBRATE); + } + + @Test + public void flagAllowRingerModesAbsent_onNonSystemStreams_noModeChange() throws Exception { + mAudioService.setStreamVolume(STREAM_MUSIC, + mAudioService.getStreamMinVolume(STREAM_MUSIC), /*flags=*/0, + mContext.getOpPackageName()); + mAudioService.setRingerModeInternal(RINGER_MODE_VIBRATE, mContext.getOpPackageName()); + + mAudioService.adjustStreamVolume(STREAM_MUSIC, ADJUST_RAISE, /*flags=*/0, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + assertEquals(mAudioService.getRingerModeInternal(), RINGER_MODE_VIBRATE); + } + + // --------------------- Permission tests --------------------- + + @Test + public void appOpsIgnore_noVolumeChange() throws Exception { + reset(mMockAppOpsManager); + when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), isNull(), isNull())) + .thenReturn(AppOpsManager.MODE_IGNORED); + + mAudioService.adjustStreamVolume(STREAM_MUSIC, ADJUST_LOWER, /*flags=*/0, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS( + eq(STREAM_MUSIC), anyInt(), anyInt()); + } + + @Test + public void modifyPhoneStateAbsent_noMuteVoiceCallScoAllowed() throws Exception { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .dropShellPermissionIdentity(); + + mAudioService.setDeviceForStream(STREAM_VOICE_CALL, DEVICE_OUT_USB_DEVICE); + mAudioService.adjustStreamVolume(STREAM_VOICE_CALL, ADJUST_MUTE, /*flags=*/0, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS( + eq(STREAM_VOICE_CALL), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + + mAudioService.setDeviceForStream(STREAM_BLUETOOTH_SCO, DEVICE_OUT_BLUETOOTH_SCO); + mAudioService.adjustStreamVolume(STREAM_BLUETOOTH_SCO, ADJUST_MUTE, /*flags=*/0, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem, times(0)).setStreamVolumeIndexAS( + eq(STREAM_BLUETOOTH_SCO), anyInt(), eq(DEVICE_OUT_USB_DEVICE)); + } + + // ----------------- AudioDeviceVolumeManager ----------------- + @Test + public void setDeviceVolume_checkIndex() { + final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC); + final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC); + final int midIndex = (minIndex + maxIndex) / 2; + final VolumeInfo volMedia = new VolumeInfo.Builder(STREAM_MUSIC) + .setMinVolumeIndex(minIndex) + .setMaxVolumeIndex(maxIndex) + .build(); + final VolumeInfo volMin = new VolumeInfo.Builder(volMedia).setVolumeIndex(minIndex).build(); + final VolumeInfo volMid = new VolumeInfo.Builder(volMedia).setVolumeIndex(midIndex).build(); + final AudioDeviceAttributes usbDevice = new AudioDeviceAttributes( + /*native type*/ AudioSystem.DEVICE_OUT_USB_DEVICE, /*address*/ "bla"); + + mAudioService.setDeviceVolume(volMin, usbDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + assertEquals(mAudioService.getDeviceVolume(volMin, usbDevice, + mContext.getOpPackageName()), volMin); + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); + + mAudioService.setDeviceVolume(volMid, usbDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + assertEquals(mAudioService.getDeviceVolume(volMid, usbDevice, + mContext.getOpPackageName()), volMid); + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); + } + + @Test + @RequiresFlagsDisabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) + public void configurablePreScaleAbsoluteVolume_checkIndex() throws Exception { + final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC); + final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC); + final VolumeInfo volMedia = new VolumeInfo.Builder(STREAM_MUSIC) + .setMinVolumeIndex(minIndex) + .setMaxVolumeIndex(maxIndex) + .build(); + final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes( + /*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "fake_ble"); + final int maxPreScaleIndex = 3; + final float[] preScale = new float[maxPreScaleIndex]; + preScale[0] = mContext.getResources().getFraction( + com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index1, + 1, 1); + preScale[1] = mContext.getResources().getFraction( + com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index2, + 1, 1); + preScale[2] = mContext.getResources().getFraction( + com.android.internal.R.fraction.config_prescaleAbsoluteVolume_index3, + 1, 1); + + for (int i = 0; i < maxPreScaleIndex; i++) { + final int targetIndex = (int) (preScale[i] * maxIndex); + final VolumeInfo volCur = new VolumeInfo.Builder(volMedia) + .setVolumeIndex(i + 1).build(); + // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:1~3) + mAudioService.setDeviceVolume(volCur, bleDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + assertEquals( + mAudioService.getDeviceVolume(volCur, bleDevice, mContext.getOpPackageName()), + volCur); + // Stream volume changes + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, targetIndex, + AudioSystem.DEVICE_OUT_BLE_HEADSET); + } + + // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4) + final VolumeInfo volIndex4 = new VolumeInfo.Builder(volMedia) + .setVolumeIndex(4).build(); + mAudioService.setDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + assertEquals( + mAudioService.getDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName()), + volIndex4); + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, maxIndex, + AudioSystem.DEVICE_OUT_BLE_HEADSET); + } + + @Test + @RequiresFlagsEnabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) + public void disablePreScaleAbsoluteVolume_checkIndex() throws Exception { + final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC); + final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC); + final VolumeInfo volMedia = new VolumeInfo.Builder(STREAM_MUSIC) + .setMinVolumeIndex(minIndex) + .setMaxVolumeIndex(maxIndex) + .build(); + final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes( + /*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "bla"); + final int maxPreScaleIndex = 3; + + for (int i = 0; i < maxPreScaleIndex; i++) { + final VolumeInfo volCur = new VolumeInfo.Builder(volMedia) + .setVolumeIndex(i + 1).build(); + // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:1~3) + mAudioService.setDeviceVolume(volCur, bleDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + // Stream volume changes + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, maxIndex, + AudioSystem.DEVICE_OUT_BLE_HEADSET); + } + + // Adjust stream volume with FLAG_ABSOLUTE_VOLUME set (index:4) + final VolumeInfo volIndex4 = new VolumeInfo.Builder(volMedia) + .setVolumeIndex(4).build(); + mAudioService.setDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + STREAM_MUSIC, maxIndex, + AudioSystem.DEVICE_OUT_BLE_HEADSET); + } + + // ---------------- DeviceVolumeBehaviorTest ---------------- + @Test + public void setDeviceVolumeBehavior_changesDeviceVolumeBehavior() { + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + for (int behavior : BASIC_VOLUME_BEHAVIORS) { + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, behavior, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + int actualBehavior = mAudioService.getDeviceVolumeBehavior(DEVICE_SPEAKER_OUT); + + assertWithMessage("Expected volume behavior to be " + behavior + + " but was instead " + actualBehavior) + .that(actualBehavior).isEqualTo(behavior); + } + } + + @Test + public void setToNewBehavior_triggersDeviceVolumeBehaviorDispatcher() { + TestDeviceVolumeBehaviorDispatcherStub dispatcher = + new TestDeviceVolumeBehaviorDispatcherStub(); + mAudioService.registerDeviceVolumeBehaviorDispatcher(true, dispatcher); + + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + for (int behavior : BASIC_VOLUME_BEHAVIORS) { + dispatcher.reset(); + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, behavior, + mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + assertThat(dispatcher.mTimesCalled).isEqualTo(1); + assertThat(dispatcher.mDevice).isEqualTo(DEVICE_SPEAKER_OUT); + assertWithMessage("Expected dispatched volume behavior to be " + behavior + + " but was instead " + dispatcher.mVolumeBehavior) + .that(dispatcher.mVolumeBehavior).isEqualTo(behavior); + } + } + + @Test + public void setToSameBehavior_doesNotTriggerDeviceVolumeBehaviorDispatcher() { + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + TestDeviceVolumeBehaviorDispatcherStub dispatcher = + new TestDeviceVolumeBehaviorDispatcherStub(); + mAudioService.registerDeviceVolumeBehaviorDispatcher(true, dispatcher); + + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + assertThat(dispatcher.mTimesCalled).isEqualTo(0); + } + + @Test + public void unregisterDeviceVolumeBehaviorDispatcher_noLongerTriggered() { + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + + TestDeviceVolumeBehaviorDispatcherStub dispatcher = + new TestDeviceVolumeBehaviorDispatcherStub(); + mAudioService.registerDeviceVolumeBehaviorDispatcher(true, dispatcher); + mAudioService.registerDeviceVolumeBehaviorDispatcher(false, dispatcher); + + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL, mContext.getOpPackageName()); + mTestLooper.dispatchAll(); + assertThat(dispatcher.mTimesCalled).isEqualTo(0); + } + + @Test + public void setDeviceVolumeBehavior_checkIsVolumeFixed() throws Exception { + when(mSpyAudioSystem.getDevicesForAttributes(any(), anyBoolean())).thenReturn( + new ArrayList<>(List.of(DEVICE_SPEAKER_OUT))); + + mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT, + AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED, mContext.getOpPackageName()); + + assertTrue(mAudioService.isVolumeFixed()); + } + + private int circularNoMinMaxIncrementVolume(int streamType) throws Exception { + final int streamMinVolume = mAm.getStreamMinVolume(streamType) + 1; + final int streamMaxVolume = mAm.getStreamMaxVolume(streamType) - 1; + + int streamVolume = mAudioService.getStreamVolume(streamType); + if (streamVolume + 1 > streamMaxVolume) { + return streamMinVolume; + } + return streamVolume + 1; + } +} diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 34929356fe8c..a529382bfb26 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -2144,13 +2144,14 @@ public class NetworkPolicyManagerServiceTest { assertFalse(mService.isUidNetworkingBlocked(UID_E, false)); } + @Ignore("Temporarily disabled until the feature is enabled") @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testBackgroundChainEnabled() throws Exception { verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true); } - + @Ignore("Temporarily disabled until the feature is enabled") @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testBackgroundChainOnProcStateChange() throws Exception { @@ -2180,6 +2181,7 @@ public class NetworkPolicyManagerServiceTest { assertTrue(mService.isUidNetworkingBlocked(UID_A, false)); } + @Ignore("Temporarily disabled until the feature is enabled") @Test @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE) public void testBackgroundChainOnAllowlistChange() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java index da8ec2e4ec15..f91f77a56385 100644 --- a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java @@ -17,12 +17,17 @@ package com.android.server.pdb; import static com.android.server.pdb.PersistentDataBlockService.DIGEST_SIZE_BYTES; +import static com.android.server.pdb.PersistentDataBlockService.FRP_CREDENTIAL_RESERVED_SIZE; +import static com.android.server.pdb.PersistentDataBlockService.FRP_SECRET_MAGIC; import static com.android.server.pdb.PersistentDataBlockService.FRP_SECRET_SIZE; +import static com.android.server.pdb.PersistentDataBlockService.HEADER_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_DATA_BLOCK_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_FRP_CREDENTIAL_HANDLE_SIZE; import static com.android.server.pdb.PersistentDataBlockService.MAX_TEST_MODE_DATA_SIZE; +import static com.android.server.pdb.PersistentDataBlockService.TEST_MODE_RESERVED_SIZE; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.anyInt; @@ -36,6 +41,7 @@ import static org.mockito.Mockito.when; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import android.Manifest; +import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; @@ -63,6 +69,7 @@ import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; @RunWith(JUnitParamsRunner.class) public class PersistentDataBlockServiceTest { @@ -397,6 +404,68 @@ public class PersistentDataBlockServiceTest { @Test @Parameters({"false", "true"}) + public void testPartitionFormat(boolean frpEnabled) throws Exception { + setUp(frpEnabled); + + /* + * 1. Fill the PDB with a specific value, so we can check regions that weren't touched + * by formatting + */ + FileChannel channel = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.WRITE, + StandardOpenOption.TRUNCATE_EXISTING); + byte[] bufArray = new byte[(int) mPdbService.getBlockDeviceSize()]; + Arrays.fill(bufArray, (byte) 0x7f); + ByteBuffer buf = ByteBuffer.wrap(bufArray); + channel.write(buf); + channel.close(); + + /* + * 2. Format it. + */ + mPdbService.formatPartitionLocked(true); + + /* + * 3. Check it. + */ + channel = FileChannel.open(mDataBlockFile.toPath(), StandardOpenOption.READ); + + // 3a. Skip the digest and header + channel.position(channel.position() + DIGEST_SIZE_BYTES + HEADER_SIZE); + + // 3b. Check the FRP data segment + assertContains("FRP data", readData(channel, mPdbService.getMaximumFrpDataSize()).array(), + (byte) 0); + + if (frpEnabled) { + // 3c. The FRP secret magic & value + assertThat(mPdbService.getFrpSecretMagicOffset()).isEqualTo(channel.position()); + assertThat(readData(channel, FRP_SECRET_MAGIC.length).array()).isEqualTo( + FRP_SECRET_MAGIC); + + assertThat(mPdbService.getFrpSecretDataOffset()).isEqualTo(channel.position()); + assertContains("FRP secret", readData(channel, FRP_SECRET_SIZE).array(), (byte) 0); + } + + // 3d. The test mode data (unmodified by formatPartitionLocked()). + assertThat(mPdbService.getTestHarnessModeDataOffset()).isEqualTo(channel.position()); + assertContains("Test data", readData(channel, TEST_MODE_RESERVED_SIZE).array(), + (byte) 0x7f); + + // 3e. The FRP credential segment + assertThat(mPdbService.getFrpCredentialDataOffset()).isEqualTo(channel.position()); + assertContains("FRP credential", readData(channel, FRP_CREDENTIAL_RESERVED_SIZE).array(), + (byte) 0); + + // 3f. OEM unlock byte. + assertThat(mPdbService.getOemUnlockDataOffset()).isEqualTo(channel.position()); + assertThat(new byte[]{1}).isEqualTo(readData(channel, 1).array()); + + // 3g. EOF + assertThat(channel.position()).isEqualTo(channel.size()); + } + + @Test + @Parameters({"false", "true"}) public void wipePermissionCheck(boolean frpEnabled) throws Exception { setUp(frpEnabled); denyOemUnlockPermission(); @@ -987,4 +1056,20 @@ public class PersistentDataBlockServiceTest { return buffer; } } + + @NonNull + private static ByteBuffer readData(FileChannel channel, int length) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(length); + assertThat(channel.read(buf)).isEqualTo(length); + buf.flip(); + assertThat(buf.limit()).isEqualTo(length); + return buf; + } + + private static void assertContains(String sectionName, byte[] buf, byte expected) { + for (int i = 0; i < buf.length; i++) { + assertWithMessage(sectionName + " is incorrect at offset " + i) + .that(buf[i]).isEqualTo(expected); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java index ae0a758449b5..65662d6b30b8 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java +++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java @@ -17,6 +17,7 @@ package com.android.server.webkit; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; @@ -179,4 +180,7 @@ public class TestSystemImpl implements SystemInterface { public boolean isMultiProcessDefaultEnabled() { return mMultiProcessDefault; } + + @Override + public void pinWebviewIfRequired(ApplicationInfo appInfo) {} } diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 295b124c06e0..a8f6fe86c823 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -346,7 +346,7 @@ public class SystemServicesTestRule implements TestRule { doReturn(true).when(amInternal).hasStartedUserState(anyInt()); doReturn(false).when(amInternal).shouldConfirmCredentials(anyInt()); doReturn(false).when(amInternal).isActivityStartsLoggingEnabled(); - doReturn(true).when(amInternal).getThemeOverlayReadiness(); + doReturn(true).when(amInternal).isThemeOverlayReady(anyInt()); LocalServices.addService(ActivityManagerInternal.class, amInternal); final ActivityManagerService amService = diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java index 6a15b0594428..f1d84cfc636d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java @@ -40,8 +40,10 @@ import android.view.WindowManager; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.server.wm.utils.CommonUtils; import com.android.window.flags.Flags; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -77,6 +79,11 @@ public class TrustedOverlayTests { }); } + @After + public void tearDown() { + CommonUtils.waitUntilActivityRemoved(mActivity); + } + @RequiresFlagsDisabled(Flags.FLAG_SURFACE_TRUSTED_OVERLAY) @Test public void setTrustedOverlayInputWindow() throws InterruptedException { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java index 368a96b372fe..0a1f3c78e0e6 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java @@ -192,6 +192,7 @@ abstract class DetectorSession { final Object mLock; final int mVoiceInteractionServiceUid; final Context mContext; + final int mUserId; @Nullable AttentionManagerInternal mAttentionManagerInternal = null; @@ -224,12 +225,13 @@ abstract class DetectorSession { @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid, Identity voiceInteractorIdentity, @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging, - @NonNull DetectorRemoteExceptionListener listener) { + @NonNull DetectorRemoteExceptionListener listener, int userId) { mRemoteExceptionListener = listener; mRemoteDetectionService = remoteDetectionService; mLock = lock; mContext = context; mToken = token; + mUserId = userId; mCallback = callback; mVoiceInteractionServiceUid = voiceInteractionServiceUid; mVoiceInteractorIdentity = voiceInteractorIdentity; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java index 9a4fbdc4516a..8d08c6bb5e4b 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java @@ -87,10 +87,10 @@ final class DspTrustedHotwordDetectorSession extends DetectorSession { @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid, Identity voiceInteractorIdentity, @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging, - @NonNull DetectorRemoteExceptionListener listener) { + @NonNull DetectorRemoteExceptionListener listener, int userId) { super(remoteHotwordDetectionService, lock, context, token, callback, voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService, - logging, listener); + logging, listener, userId); } @SuppressWarnings("GuardedBy") diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index f1f5458f161c..cfcc04b10107 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -72,6 +72,7 @@ import android.view.contentcapture.IContentCaptureManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.app.IVisualQueryDetectionAttentionListener; +import com.android.internal.app.IVoiceInteractionAccessibilitySettingsListener; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.server.LocalServices; @@ -147,8 +148,9 @@ final class HotwordDetectionConnection { final int mVoiceInteractionServiceUid; final ComponentName mHotwordDetectionComponentName; final ComponentName mVisualQueryDetectionComponentName; - final int mUser; + final int mUserId; final Context mContext; + final AccessibilitySettingsListener mAccessibilitySettingsListener; volatile HotwordDetectionServiceIdentity mIdentity; //TODO: Consider rename this to SandboxedDetectionIdentity private Instant mLastRestartInstant; @@ -204,6 +206,27 @@ final class HotwordDetectionConnection { } }; + /** Listen to changes of {@link Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED}. + * + * This is registered to the {@link VoiceInteractionManagerServiceImpl} where all settings + * listeners are centralized and notified. + */ + private final class AccessibilitySettingsListener extends + IVoiceInteractionAccessibilitySettingsListener.Stub { + @Override + public void onAccessibilityDetectionChanged(boolean enable) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Update settings change: " + enable); + } + VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked(); + if (session != null) { + session.updateAccessibilityEgressStateLocked(enable); + } + } + } + } + HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid, Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName, @@ -216,11 +239,12 @@ final class HotwordDetectionConnection { mVoiceInteractorIdentity = voiceInteractorIdentity; mHotwordDetectionComponentName = hotwordDetectionServiceName; mVisualQueryDetectionComponentName = visualQueryDetectionServiceName; - mUser = userId; + mUserId = userId; mDetectorType = detectorType; mRemoteExceptionListener = listener; mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION, KEY_RESTART_PERIOD_IN_SECONDS, 0); + mAccessibilitySettingsListener = new AccessibilitySettingsListener(); final Intent hotwordDetectionServiceIntent = new Intent(HotwordDetectionService.SERVICE_INTERFACE); @@ -792,7 +816,7 @@ final class HotwordDetectionConnection { ServiceConnection createLocked() { ServiceConnection connection = - new ServiceConnection(mContext, mIntent, mBindingFlags, mUser, + new ServiceConnection(mContext, mIntent, mBindingFlags, mUserId, ISandboxedDetectionService.Stub::asInterface, mRestartCount % MAX_ISOLATED_PROCESS_NUMBER, mDetectionServiceType); connection.connect(); @@ -998,7 +1022,7 @@ final class HotwordDetectionConnection { session = new DspTrustedHotwordDetectorSession(mRemoteHotwordDetectionService, mLock, mContext, token, callback, mVoiceInteractionServiceUid, mVoiceInteractorIdentity, mScheduledExecutorService, mDebugHotwordLogging, - mRemoteExceptionListener); + mRemoteExceptionListener, mUserId); } else if (detectorType == HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { if (mRemoteVisualQueryDetectionService == null) { mRemoteVisualQueryDetectionService = @@ -1007,7 +1031,8 @@ final class HotwordDetectionConnection { session = new VisualQueryDetectorSession( mRemoteVisualQueryDetectionService, mLock, mContext, token, callback, mVoiceInteractionServiceUid, mVoiceInteractorIdentity, - mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener); + mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener, + mUserId); } else { if (mRemoteHotwordDetectionService == null) { mRemoteHotwordDetectionService = @@ -1016,7 +1041,8 @@ final class HotwordDetectionConnection { session = new SoftwareTrustedHotwordDetectorSession( mRemoteHotwordDetectionService, mLock, mContext, token, callback, mVoiceInteractionServiceUid, mVoiceInteractorIdentity, - mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener); + mScheduledExecutorService, mDebugHotwordLogging, + mRemoteExceptionListener, mUserId); } mHotwordRecognitionCallback = callback; mDetectorSessions.put(detectorType, session); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java index f06c99729a19..120c161c1903 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java @@ -74,10 +74,10 @@ final class SoftwareTrustedHotwordDetectorSession extends DetectorSession { @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid, Identity voiceInteractorIdentity, @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging, - @NonNull DetectorRemoteExceptionListener listener) { + @NonNull DetectorRemoteExceptionListener listener, int userId) { super(remoteHotwordDetectionService, lock, context, token, callback, voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService, - logging, listener); + logging, listener, userId); } @SuppressWarnings("GuardedBy") diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java index e4ac993f2d50..aef8e6fabc9b 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java @@ -29,6 +29,7 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SharedMemory; +import android.provider.Settings; import android.service.voice.IDetectorSessionVisualQueryDetectionCallback; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.ISandboxedDetectionService; @@ -60,6 +61,7 @@ final class VisualQueryDetectorSession extends DetectorSession { private IVisualQueryDetectionAttentionListener mAttentionListener; private boolean mEgressingData; private boolean mQueryStreaming; + private boolean mEnableAccessibilityDataEgress; //TODO(b/261783819): Determines actual functionalities, e.g., startRecognition etc. VisualQueryDetectorSession( @@ -68,13 +70,17 @@ final class VisualQueryDetectorSession extends DetectorSession { @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid, Identity voiceInteractorIdentity, @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging, - @NonNull DetectorRemoteExceptionListener listener) { + @NonNull DetectorRemoteExceptionListener listener, int userId) { super(remoteService, lock, context, token, callback, voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService, - logging, listener); + logging, listener, userId); mEgressingData = false; mQueryStreaming = false; mAttentionListener = null; + mEnableAccessibilityDataEgress = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED, 0, + mUserId) == 1; // TODO: handle notify RemoteException to client } @@ -186,6 +192,16 @@ final class VisualQueryDetectorSession extends DetectorSession { "Cannot stream results without attention signals.")); return; } + if (!checkDetectedResultDataLocked(partialResult)) { + Slog.v(TAG, "Accessibility data can be egressed only when the " + + "isAccessibilityDetectionEnabled() is true."); + callback.onVisualQueryDetectionServiceFailure( + new VisualQueryDetectionServiceFailure( + ERROR_CODE_ILLEGAL_STREAMING_STATE, + "Cannot stream accessibility data without " + + "enabling the setting.")); + return; + } mQueryStreaming = true; callback.onResultDetected(partialResult); Slog.i(TAG, "Egressed from visual query detection process."); @@ -227,6 +243,12 @@ final class VisualQueryDetectorSession extends DetectorSession { mQueryStreaming = false; } } + + @SuppressWarnings("GuardedBy") + private boolean checkDetectedResultDataLocked(VisualQueryDetectedResult result) { + return result.getAccessibilityDetectionData() == null + || mEnableAccessibilityDataEgress; + } }; return mRemoteDetectionService.run( service -> service.detectWithVisualSignals(internalCallback)); @@ -251,6 +273,12 @@ final class VisualQueryDetectorSession extends DetectorSession { + " should not be called from VisualQueryDetectorSession."); } + void updateAccessibilityEgressStateLocked(boolean enable) { + if (DEBUG) { + Slog.d(TAG, "updateAccessibilityEgressStateLocked"); + } + mEnableAccessibilityDataEgress = enable; + } @SuppressWarnings("GuardedBy") public void dumpLocked(String prefix, PrintWriter pw) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index ecb0f9689ae7..889f8429077c 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -91,6 +91,7 @@ import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.app.IVisualQueryDetectionAttentionListener; import com.android.internal.app.IVisualQueryRecognitionStatusListener; import com.android.internal.app.IVoiceActionCheckCallback; +import com.android.internal.app.IVoiceInteractionAccessibilitySettingsListener; import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.app.IVoiceInteractionSessionListener; import com.android.internal.app.IVoiceInteractionSessionShowCallback; @@ -2179,6 +2180,44 @@ public class VoiceInteractionManagerService extends SystemService { } } + public boolean getAccessibilityDetectionEnabled() { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "registerAccessibilityDetectionSettingsListener called without" + + " running voice interaction service"); + return false; + } + return mImpl.getAccessibilityDetectionEnabled(); + } + } + + @Override + public void registerAccessibilityDetectionSettingsListener( + IVoiceInteractionAccessibilitySettingsListener listener) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "registerAccessibilityDetectionSettingsListener called without" + + " running voice interaction service"); + return; + } + mImpl.registerAccessibilityDetectionSettingsListenerLocked(listener); + } + } + + @Override + public void unregisterAccessibilityDetectionSettingsListener( + IVoiceInteractionAccessibilitySettingsListener listener) { + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "unregisterAccessibilityDetectionSettingsListener called " + + "without running voice interaction service"); + return; + } + mImpl.unregisterAccessibilityDetectionSettingsListenerLocked(listener); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 84b36d5948eb..e34e81908632 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -36,6 +36,7 @@ import android.app.IActivityManager; import android.app.IActivityTaskManager; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -45,10 +46,12 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; +import android.database.ContentObserver; import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.SoundTrigger; import android.media.AudioFormat; import android.media.permission.Identity; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -60,6 +63,7 @@ import android.os.ServiceManager; import android.os.SharedMemory; import android.os.SystemProperties; import android.os.UserHandle; +import android.provider.Settings; import android.service.voice.HotwordDetector; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback; @@ -77,6 +81,7 @@ import android.view.IWindowManager; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.app.IVisualQueryDetectionAttentionListener; import com.android.internal.app.IVoiceActionCheckCallback; +import com.android.internal.app.IVoiceInteractionAccessibilitySettingsListener; import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.util.function.pooled.PooledLambda; @@ -199,6 +204,10 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } }; + final ArrayList< + IVoiceInteractionAccessibilitySettingsListener> mAccessibilitySettingsListeners = + new ArrayList<IVoiceInteractionAccessibilitySettingsListener>(); + VoiceInteractionManagerServiceImpl(Context context, Handler handler, VoiceInteractionManagerService.VoiceInteractionManagerServiceStub stub, int userHandle, ComponentName service) { @@ -250,6 +259,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mContext.registerReceiver(mBroadcastReceiver, filter, null, handler, Context.RECEIVER_EXPORTED); + new AccessibilitySettingsContentObserver().register(mContext.getContentResolver()); } public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) { @@ -745,6 +755,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne + "exception occurred."); } }); + registerAccessibilityDetectionSettingsListenerLocked( + mHotwordDetectionConnection.mAccessibilitySettingsListener); } else if (detectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { // TODO: Logger events should be handled in session instead. Temporary adding the // checking to prevent confusion so VisualQueryDetection events won't be logged if the @@ -782,6 +794,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return; } mHotwordDetectionConnection.cancelLocked(); + unregisterAccessibilityDetectionSettingsListenerLocked( + mHotwordDetectionConnection.mAccessibilitySettingsListener); mHotwordDetectionConnection = null; } @@ -974,6 +988,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return; } mHotwordDetectionConnection.cancelLocked(); + unregisterAccessibilityDetectionSettingsListenerLocked( + mHotwordDetectionConnection.mAccessibilitySettingsListener); mHotwordDetectionConnection = null; } @@ -1015,6 +1031,29 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } + boolean getAccessibilityDetectionEnabled() { + return Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED, 0, + mUser) == 1; + } + + void registerAccessibilityDetectionSettingsListenerLocked( + IVoiceInteractionAccessibilitySettingsListener listener) { + if (DEBUG) { + Slog.d(TAG, "registerAccessibilityDetectionSettingsListener"); + } + mAccessibilitySettingsListeners.add(listener); + } + + void unregisterAccessibilityDetectionSettingsListenerLocked( + IVoiceInteractionAccessibilitySettingsListener listener) { + if (DEBUG) { + Slog.d(TAG, "unregisterAccessibilityDetectionSettingsListener"); + } + mAccessibilitySettingsListeners.remove(listener); + } + void startLocked() { Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); intent.setComponent(mComponent); @@ -1055,6 +1094,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } if (mHotwordDetectionConnection != null) { mHotwordDetectionConnection.cancelLocked(); + unregisterAccessibilityDetectionSettingsListenerLocked( + mHotwordDetectionConnection.mAccessibilitySettingsListener); mHotwordDetectionConnection = null; } if (mBound) { @@ -1101,4 +1142,41 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne interface DetectorRemoteExceptionListener { void onDetectorRemoteException(@NonNull IBinder token, int detectorType); } + + private final class AccessibilitySettingsContentObserver extends ContentObserver { + private Uri mAccessibilitySettingsEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED); + + AccessibilitySettingsContentObserver() { + super(null); + } + + public void register(ContentResolver contentResolver) { + contentResolver.registerContentObserver( + mAccessibilitySettingsEnabledUri, false, this, UserHandle.USER_ALL); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + Slog.i(TAG, "OnChange called with uri:" + uri); + if (mAccessibilitySettingsEnabledUri.equals(uri)) { + boolean enable = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED, 0, + mUser) == 1; + Slog.i(TAG, "Notifying listeners with Accessibility setting set to " + + enable); + mAccessibilitySettingsListeners.forEach( + listener -> { + try { + listener.onAccessibilityDetectionChanged(enable); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + ); + + } + } + } } diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java index c7cc1bd88bdf..49e9232ad535 100644 --- a/telecomm/java/android/telecom/CallAudioState.java +++ b/telecomm/java/android/telecom/CallAudioState.java @@ -199,6 +199,16 @@ public final class CallAudioState implements Parcelable { } /** + * @return Bit mask of all routes supported by this call, won't be changed by streaming state. + * + * @hide + */ + @CallAudioRoute + public int getRawSupportedRouteMask() { + return supportedRouteMask; + } + + /** * @return The {@link BluetoothDevice} through which audio is being routed. * Will not be {@code null} if {@link #getRoute()} returns {@link #ROUTE_BLUETOOTH}. */ diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 15a978d167e1..08c76af70511 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1434,6 +1434,31 @@ public class TelecomManager { } /** + * This API will return all {@link PhoneAccount}s registered via + * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}. If a {@link PhoneAccount} appears + * to be missing from the list, Telecom has either unregistered the {@link PhoneAccount} + * or the caller registered the {@link PhoneAccount} under a different user and does not + * have the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission. + * + * @return all the {@link PhoneAccount}s registered by the caller. + */ + @SuppressLint("RequiresPermission") + @FlaggedApi(Flags.FLAG_GET_REGISTERED_PHONE_ACCOUNTS) + public @NonNull List<PhoneAccount> getRegisteredPhoneAccounts() { + ITelecomService service = getTelecomService(); + if (service != null) { + try { + return service.getRegisteredPhoneAccounts( + mContext.getOpPackageName(), + mContext.getAttributionTag()).getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + throw new IllegalStateException("Telecom is not available"); + } + + /** * Returns a list of {@link PhoneAccountHandle}s including those which have not been enabled * by the user. * diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 7dba799e1057..302a472b77e4 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -93,6 +93,12 @@ interface ITelecomService { PhoneAccount getPhoneAccount(in PhoneAccountHandle account, String callingPackage); /** + * @see TelecomManager#getPhoneAccount + */ + ParceledListSlice<PhoneAccount> getRegisteredPhoneAccounts(String callingPackage, + String callingFeatureId); + + /** * @see TelecomManager#getAllPhoneAccountsCount */ int getAllPhoneAccountsCount(); diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java index aed8fb8c4503..a63db88cb614 100644 --- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java +++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java @@ -32,6 +32,8 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Telephony; +import android.provider.Telephony.Carriers.EditStatus; import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyManager; @@ -226,6 +228,23 @@ public final class TelephonyUtils { } /** + * Convert APN edited status to string. + * + * @param apnEditStatus APN edited status. + * @return APN edited status in string format. + */ + public static @NonNull String apnEditedStatusToString(@EditStatus int apnEditStatus) { + return switch (apnEditStatus) { + case Telephony.Carriers.UNEDITED -> "UNEDITED"; + case Telephony.Carriers.USER_EDITED -> "USER_EDITED"; + case Telephony.Carriers.USER_DELETED -> "USER_DELETED"; + case Telephony.Carriers.CARRIER_EDITED -> "CARRIER_EDITED"; + case Telephony.Carriers.CARRIER_DELETED -> "CARRIER_DELETED"; + default -> "UNKNOWN(" + apnEditStatus + ")"; + }; + } + + /** * Utility method to get user handle associated with this subscription. * * This method should be used internally as it returns null instead of throwing diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index d99abe882405..5d99acd87dd3 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -4803,12 +4803,51 @@ public class CarrierConfigManager { */ public static final String KEY_FCM_SENDER_ID_STRING = KEY_PREFIX + "fcm_sender_id_string"; + /** + * Indicates the supported protocol version in the parameter entitlement_version. + * The default value is 2. The possible value is 2 and 8. + * + * Reference: GSMA TS.43-v8 section 2.5 Protocol version control and + * Table 3. GET Parameters for Entitlement Configuration in section 2.3 + * HTTP GET method Parameters. + * @hide + */ + public static final String KEY_ENTITLEMENT_VERSION_INT = + KEY_PREFIX + "entitlement_version_int"; + + /** + * Controls the service entitlement status when receiving the VERS characteristic + * with both version and validity set to -1 or -2. + * If {@code true}, default service entitlement status is enabled. + * If {@code false}, default service entitlement status is disabled. + * + * Reference: GSMA TS.14-v8 section 2.1, overview + * @hide + */ + public static final String KEY_DEFAULT_SERVICE_ENTITLEMENT_STATUS_BOOL = + KEY_PREFIX + "default_service_entitlement_status_bool"; + + /** + * Indicates if UE can skip service entitlement check when the user turns on Wi-Fi Calling. + * UE still shows Wi-Fi Calling emergency address update web view when the user clicks + * "Update Emergency Address" on the WiFi calling setting. + * + * Note: this is effective only if the {@link #KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING} + * is set to this app. + * @hide + */ + public static final String KEY_SKIP_WFC_ACTIVATION_BOOL = + KEY_PREFIX + "skip_wfc_activation_bool"; + private static PersistableBundle getDefaults() { PersistableBundle defaults = new PersistableBundle(); defaults.putString(KEY_ENTITLEMENT_SERVER_URL_STRING, ""); defaults.putString(KEY_FCM_SENDER_ID_STRING, ""); defaults.putBoolean(KEY_SHOW_VOWIFI_WEBVIEW_BOOL, false); defaults.putBoolean(KEY_IMS_PROVISIONING_BOOL, false); + defaults.putBoolean(KEY_DEFAULT_SERVICE_ENTITLEMENT_STATUS_BOOL, false); + defaults.putBoolean(KEY_SKIP_WFC_ACTIVATION_BOOL, false); + defaults.putInt(KEY_ENTITLEMENT_VERSION_INT, 2); return defaults; } } diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 8679bd4baf2e..44d3fca6aec6 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -29,6 +29,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.provider.Telephony; import android.provider.Telephony.Carriers; +import android.provider.Telephony.Carriers.EditStatus; import android.telephony.Annotation.NetworkType; import android.telephony.ServiceState; import android.telephony.TelephonyManager; @@ -37,6 +38,7 @@ import android.util.ArrayMap; import android.util.Log; import com.android.internal.telephony.flags.Flags; +import com.android.internal.telephony.util.TelephonyUtils; import com.android.telephony.Rlog; import java.lang.annotation.Retention; @@ -571,6 +573,13 @@ public class ApnSetting implements Parcelable { private final boolean mEsimBootstrapProvisioning; /** + * The APN edited status. + * + * Note it is intended not using this field for {@link #equals(Object)} or {@link #hashCode()}. + */ + private final @EditStatus int mEditedStatus; + + /** * Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought * up by this APN setting. Note this value will only be used when MTU size is not provided * in {@code DataCallResponse#getMtuV4()} during network bring up. @@ -992,6 +1001,22 @@ public class ApnSetting implements Parcelable { return mEsimBootstrapProvisioning; } + /** + * @return APN edited status. APN could be added/edited/deleted by a user or carrier. + * + * @see Carriers#UNEDITED + * @see Carriers#USER_EDITED + * @see Carriers#USER_DELETED + * @see Carriers#CARRIER_EDITED + * @see Carriers#CARRIER_DELETED + * + * @hide + */ + @EditStatus + public int getEditedStatus() { + return mEditedStatus; + } + private ApnSetting(Builder builder) { this.mEntryName = builder.mEntryName; this.mApnName = builder.mApnName; @@ -1030,6 +1055,7 @@ public class ApnSetting implements Parcelable { this.mAlwaysOn = builder.mAlwaysOn; this.mInfrastructureBitmask = builder.mInfrastructureBitmask; this.mEsimBootstrapProvisioning = builder.mEsimBootstrapProvisioning; + this.mEditedStatus = builder.mEditedStatus; } /** @@ -1113,6 +1139,8 @@ public class ApnSetting implements Parcelable { Telephony.Carriers.INFRASTRUCTURE_BITMASK))) .setEsimBootstrapProvisioning(cursor.getInt( cursor.getColumnIndexOrThrow(Carriers.ESIM_BOOTSTRAP_PROVISIONING)) == 1) + .setEditedStatus(cursor.getInt( + cursor.getColumnIndexOrThrow(Carriers.EDITED_STATUS))) .buildWithoutCheck(); } @@ -1154,6 +1182,7 @@ public class ApnSetting implements Parcelable { .setAlwaysOn(apn.mAlwaysOn) .setInfrastructureBitmask(apn.mInfrastructureBitmask) .setEsimBootstrapProvisioning(apn.mEsimBootstrapProvisioning) + .setEditedStatus(apn.mEditedStatus) .buildWithoutCheck(); } @@ -1202,6 +1231,7 @@ public class ApnSetting implements Parcelable { sb.append(", ").append(mInfrastructureBitmask); sb.append(", ").append(Objects.hash(mUser, mPassword)); sb.append(", ").append(mEsimBootstrapProvisioning); + sb.append(", ").append(TelephonyUtils.apnEditedStatusToString(mEditedStatus)); return sb.toString(); } @@ -1748,6 +1778,7 @@ public class ApnSetting implements Parcelable { dest.writeBoolean(mAlwaysOn); dest.writeInt(mInfrastructureBitmask); dest.writeBoolean(mEsimBootstrapProvisioning); + dest.writeInt(mEditedStatus); } private static ApnSetting readFromParcel(Parcel in) { @@ -1785,6 +1816,7 @@ public class ApnSetting implements Parcelable { .setAlwaysOn(in.readBoolean()) .setInfrastructureBitmask(in.readInt()) .setEsimBootstrapProvisioning(in.readBoolean()) + .setEditedStatus(in.readInt()) .buildWithoutCheck(); } @@ -1868,6 +1900,7 @@ public class ApnSetting implements Parcelable { private boolean mAlwaysOn; private int mInfrastructureBitmask = INFRASTRUCTURE_CELLULAR | INFRASTRUCTURE_SATELLITE; private boolean mEsimBootstrapProvisioning; + private @EditStatus int mEditedStatus = Carriers.UNEDITED; /** * Default constructor for Builder. @@ -2310,6 +2343,8 @@ public class ApnSetting implements Parcelable { * * @param esimBootstrapProvisioning {@code true} if the APN is used for eSIM bootstrap * provisioning, {@code false} otherwise. + * + * @return The builder. * @hide */ @NonNull @@ -2319,6 +2354,26 @@ public class ApnSetting implements Parcelable { } /** + * Set the edited status. APN could be added/edited/deleted by a user or carrier. + * + * @param editedStatus The APN edited status + * @return The builder. + * + * @see Carriers#UNEDITED + * @see Carriers#USER_EDITED + * @see Carriers#USER_DELETED + * @see Carriers#CARRIER_EDITED + * @see Carriers#CARRIER_DELETED + * + * @hide + */ + @NonNull + public Builder setEditedStatus(@EditStatus int editedStatus) { + this.mEditedStatus = editedStatus; + return this; + } + + /** * Builds {@link ApnSetting} from this builder. * * @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)} diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java index b429407d8f31..d44a43e42a14 100644 --- a/telephony/java/android/telephony/emergency/EmergencyNumber.java +++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java @@ -37,6 +37,7 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; /** * A parcelable class that wraps and retrieves the information of number, service category(s) and @@ -300,8 +301,8 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu dest.writeInt(mEmergencyCallRouting); } - public static final @android.annotation.NonNull Parcelable.Creator<EmergencyNumber> CREATOR = - new Parcelable.Creator<EmergencyNumber>() { + public static final @NonNull Creator<EmergencyNumber> CREATOR = + new Creator<EmergencyNumber>() { @Override public EmergencyNumber createFromParcel(Parcel in) { return new EmergencyNumber(in); @@ -500,12 +501,94 @@ public final class EmergencyNumber implements Parcelable, Comparable<EmergencyNu @Override public String toString() { - return "EmergencyNumber:" + "Number-" + mNumber + "|CountryIso-" + mCountryIso - + "|Mnc-" + mMnc - + "|ServiceCategories-" + Integer.toBinaryString(mEmergencyServiceCategoryBitmask) - + "|Urns-" + mEmergencyUrns - + "|Sources-" + Integer.toBinaryString(mEmergencyNumberSourceBitmask) - + "|Routing-" + Integer.toBinaryString(mEmergencyCallRouting); + return String.format("[EmergencyNumber: %s, countryIso=%s, mnc=%s, src=%s, routing=%s, " + + "categories=%s, urns=%s]", + mNumber, + mCountryIso, + mMnc, + sourceBitmaskToString(mEmergencyNumberSourceBitmask), + routingToString(mEmergencyCallRouting), + categoriesToString(mEmergencyServiceCategoryBitmask), + (mEmergencyUrns == null ? "" : + mEmergencyUrns.stream().collect(Collectors.joining(",")))); + } + + /** + * @param categories emergency service category bitmask + * @return loggable string describing the category bitmask + */ + private String categoriesToString(@EmergencyServiceCategories int categories) { + StringBuilder sb = new StringBuilder(); + if ((categories & EMERGENCY_SERVICE_CATEGORY_AIEC) == EMERGENCY_SERVICE_CATEGORY_AIEC) { + sb.append("auto "); + } + if ((categories & EMERGENCY_SERVICE_CATEGORY_AMBULANCE) + == EMERGENCY_SERVICE_CATEGORY_AMBULANCE) { + sb.append("ambulance "); + } + if ((categories & EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE) + == EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE) { + sb.append("fire "); + } + if ((categories & EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD) + == EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD) { + sb.append("marine "); + } + if ((categories & EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE) + == EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE) { + sb.append("mountain "); + } + if ((categories & EMERGENCY_SERVICE_CATEGORY_POLICE) == EMERGENCY_SERVICE_CATEGORY_POLICE) { + sb.append("police "); + } + if ((categories & EMERGENCY_SERVICE_CATEGORY_MIEC) == EMERGENCY_SERVICE_CATEGORY_MIEC) { + sb.append("manual "); + } + return sb.toString(); + } + + /** + * @param routing emergency call routing type + * @return loggable string describing the routing type. + */ + private String routingToString(@EmergencyCallRouting int routing) { + return switch(routing) { + case EMERGENCY_CALL_ROUTING_EMERGENCY -> "emergency"; + case EMERGENCY_CALL_ROUTING_NORMAL -> "normal"; + case EMERGENCY_CALL_ROUTING_UNKNOWN -> "unknown"; + default -> "🤷"; + }; + } + + /** + * Builds a string describing the sources for an emergency number. + * @param sourceBitmask the source bitmask + * @return loggable string describing the sources. + */ + private String sourceBitmaskToString(@EmergencyNumberSources int sourceBitmask) { + StringBuilder sb = new StringBuilder(); + if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING) + == EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING) { + sb.append("net "); + } + if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_SIM) == EMERGENCY_NUMBER_SOURCE_SIM) { + sb.append("sim "); + } + if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_DATABASE) + == EMERGENCY_NUMBER_SOURCE_DATABASE) { + sb.append("db "); + } + if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG) + == EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG) { + sb.append("mdm "); + } + if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_DEFAULT) == EMERGENCY_NUMBER_SOURCE_DEFAULT) { + sb.append("def "); + } + if ((sourceBitmask & EMERGENCY_NUMBER_SOURCE_TEST) == EMERGENCY_NUMBER_SOURCE_TEST) { + sb.append("tst "); + } + return sb.toString(); } @Override diff --git a/tests/Internal/src/com/android/internal/protolog/OWNERS b/tests/Internal/src/com/android/internal/protolog/OWNERS new file mode 100644 index 000000000000..18cf2be9f7df --- /dev/null +++ b/tests/Internal/src/com/android/internal/protolog/OWNERS @@ -0,0 +1,3 @@ +# ProtoLog owners +# Bug component: 1157642 +include platform/development:/tools/winscope/OWNERS |