diff options
190 files changed, 3990 insertions, 1774 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 7552b5c00acc..173cf6c5f46b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -19286,11 +19286,11 @@ package android.hardware.camera2 { method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions(); method public boolean isCaptureProcessProgressAvailable(int); method public boolean isPostviewAvailable(int); - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE; field public static final int EXTENSION_AUTOMATIC = 0; // 0x0 field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1 field public static final int EXTENSION_BOKEH = 2; // 0x2 - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5 + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; // 0x5 field public static final int EXTENSION_FACE_RETOUCH = 1; // 0x1 field public static final int EXTENSION_HDR = 3; // 0x3 field public static final int EXTENSION_NIGHT = 4; // 0x4 @@ -19890,30 +19890,30 @@ package android.hardware.camera2 { field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100 } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureRequest { + @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureRequest { ctor public ExtensionCaptureRequest(); - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1 - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2 - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0 - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT; - } - - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureResult { + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1 + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2 + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0 + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT; + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") public final class ExtensionCaptureResult { ctor public ExtensionCaptureResult(); - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode_api") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT; } public class MultiResolutionImageReader implements java.lang.AutoCloseable { @@ -57915,7 +57915,7 @@ package android.webkit { method public abstract boolean getBuiltInZoomControls(); method public abstract int getCacheMode(); method public abstract String getCursiveFontFamily(); - method public abstract boolean getDatabaseEnabled(); + method @Deprecated public abstract boolean getDatabaseEnabled(); method @Deprecated public abstract String getDatabasePath(); method public abstract int getDefaultFixedFontSize(); method public abstract int getDefaultFontSize(); @@ -57961,7 +57961,7 @@ package android.webkit { method public abstract void setBuiltInZoomControls(boolean); method public abstract void setCacheMode(int); method public abstract void setCursiveFontFamily(String); - method public abstract void setDatabaseEnabled(boolean); + method @Deprecated public abstract void setDatabaseEnabled(boolean); method @Deprecated public abstract void setDatabasePath(String); method public abstract void setDefaultFixedFontSize(int); method public abstract void setDefaultFontSize(int); @@ -60157,7 +60157,7 @@ package android.widget { method public void setRadioGroupChecked(@IdRes int, @IdRes int); method public void setRelativeScrollPosition(@IdRes int, int); method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent); - method public void setRemoteAdapter(@IdRes int, android.content.Intent); + method @Deprecated public void setRemoteAdapter(@IdRes int, android.content.Intent); method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems); method public void setScrollPosition(@IdRes int, int); method public void setShort(@IdRes int, String, short); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 4b04d10a6e2c..a551f0d794e2 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2190,14 +2190,6 @@ 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); @@ -2233,11 +2225,11 @@ package android.app.ondeviceintelligence { } @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); + ctor public FeatureDetails(int, @NonNull android.os.PersistableBundle); + ctor public FeatureDetails(int); method public int describeContents(); method @NonNull public android.os.PersistableBundle getFeatureDetailParams(); - method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus(); + method public int getFeatureStatus(); 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 @@ -2247,35 +2239,14 @@ package android.app.ondeviceintelligence { 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 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 @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName(); - 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, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver); - method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver); - 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 requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,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); + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceException extends java.lang.Exception { + ctor public OnDeviceIntelligenceException(int, @NonNull String, @NonNull android.os.PersistableBundle); + ctor public OnDeviceIntelligenceException(int, @NonNull android.os.PersistableBundle); + ctor public OnDeviceIntelligenceException(int, @NonNull String); + ctor public OnDeviceIntelligenceException(int); 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 ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100; // 0x64 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 @@ -2291,10 +2262,28 @@ package android.app.ondeviceintelligence { 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 + field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200; // 0xc8 + } + + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final 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.OnDeviceIntelligenceException>); + 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.OnDeviceIntelligenceException>); + method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName(); + 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.OnDeviceIntelligenceException>); + method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingCallback); + method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback); + 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 requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); + 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 } - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> { - method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>); + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingCallback { + method public default void onDataAugmentRequest(@NonNull android.os.Bundle, @NonNull java.util.function.Consumer<android.os.Bundle>); + method public void onError(@NonNull android.app.ondeviceintelligence.OnDeviceIntelligenceException); + method public void onResult(@NonNull android.os.Bundle); } @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal { @@ -2307,8 +2296,8 @@ package android.app.ondeviceintelligence { method public void onSignalReceived(@NonNull android.os.PersistableBundle); } - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver { - method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content); + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingProcessingCallback extends android.app.ondeviceintelligence.ProcessingCallback { + method public void onPartialResult(@NonNull android.os.Bundle); } @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable { @@ -3348,7 +3337,7 @@ package android.app.wearable { field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3 field public static final int STATUS_SUCCESS = 1; // 0x1 field public static final int STATUS_UNKNOWN = 0; // 0x0 - field public static final int STATUS_UNSUPPORTED = 2; // 0x2 + field @Deprecated public static final int STATUS_UNSUPPORTED = 2; // 0x2 field @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; // 0x8 field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6 field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4 @@ -12956,38 +12945,26 @@ package android.service.ondeviceintelligence { ctor public OnDeviceIntelligenceService(); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback); - method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); - method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); + method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); + method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>); method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer); method public abstract void onInferenceServiceConnected(); method public abstract void onInferenceServiceDisconnected(); - method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); - method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>); + method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); + method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService"; } - public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception { - ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int); - ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String); - method public int getErrorCode(); - } - - public static class OnDeviceIntelligenceService.OnDeviceUpdateProcessingException extends android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException { - ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int); - ctor public OnDeviceIntelligenceService.OnDeviceUpdateProcessingException(int, @NonNull String); - field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1 - } - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service { ctor public OnDeviceSandboxedInferenceService(); method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>); method @NonNull public java.util.concurrent.Executor getCallbackExecutor(); method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); - method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver); - method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver); - method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>); - method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>); + method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingCallback); + method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback); + method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); + method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException; method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException; field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService"; diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 1e72a061d35a..62fc67b8dedf 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -1885,8 +1885,6 @@ 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/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index d01626e17f2d..fa4a4009ebc9 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -34,6 +34,8 @@ import android.app.contentsuggestions.ContentSuggestionsManager; import android.app.contentsuggestions.IContentSuggestionsManager; import android.app.ecm.EnhancedConfirmationFrameworkInitializer; import android.app.job.JobSchedulerFrameworkInitializer; +import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager; +import android.app.ondeviceintelligence.OnDeviceIntelligenceManager; import android.app.people.PeopleManager; import android.app.prediction.AppPredictionManager; import android.app.role.RoleFrameworkInitializer; @@ -1589,6 +1591,19 @@ public final class SystemServiceRegistry { return new WearableSensingManager(ctx.getOuterContext(), manager); }}); + registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class, + new CachedServiceFetcher<OnDeviceIntelligenceManager>() { + @Override + public OnDeviceIntelligenceManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder iBinder = ServiceManager.getServiceOrThrow( + Context.ON_DEVICE_INTELLIGENCE_SERVICE); + IOnDeviceIntelligenceManager manager = + IOnDeviceIntelligenceManager.Stub.asInterface(iBinder); + return new OnDeviceIntelligenceManager(ctx.getOuterContext(), manager); + } + }); + registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class, new CachedServiceFetcher<GrammaticalInflectionManager>() { @Override diff --git a/core/java/android/app/ondeviceintelligence/Content.java b/core/java/android/app/ondeviceintelligence/Content.java deleted file mode 100644 index 51bd156fc946..000000000000 --- a/core/java/android/app/ondeviceintelligence/Content.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 index 684c71f9144c..30c6e1924942 100644 --- a/core/java/android/app/ondeviceintelligence/DownloadCallback.java +++ b/core/java/android/app/ondeviceintelligence/DownloadCallback.java @@ -105,7 +105,7 @@ public interface DownloadCallback { } /** - * Called when model download via MDD completed. The remote implementation can populate any + * Called when model download is 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. diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java index 4a38c9224a3d..fd0379a046cc 100644 --- a/core/java/android/app/ondeviceintelligence/Feature.java +++ b/core/java/android/app/ondeviceintelligence/Feature.java @@ -34,7 +34,6 @@ import android.os.PersistableBundle; @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; diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java index f3cbd2621694..44930f2c34f4 100644 --- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java +++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java @@ -60,6 +60,9 @@ public final class FeatureDetails implements Parcelable { /** Underlying service is unavailable and feature status cannot be fetched. */ public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4; + /** + * @hide + */ @IntDef(value = { FEATURE_STATUS_UNAVAILABLE, FEATURE_STATUS_DOWNLOADABLE, diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl index aba563f84e1b..8fc269ea6dac 100644 --- a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl +++ b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl @@ -16,15 +16,14 @@ package android.app.ondeviceintelligence; -import android.app.ondeviceintelligence.IProcessingSignal; import android.os.PersistableBundle; /** - * Interface for Download callback to passed onto service implementation, + * Interface for Download callback to be passed onto service implementation, * * @hide */ -oneway interface IDownloadCallback { +interface IDownloadCallback { void onDownloadStarted(long bytesToDownload) = 1; void onDownloadProgress(long bytesDownloaded) = 2; void onDownloadFailed(int failureStatus, String errorMessage, in PersistableBundle errorParams) = 3; diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl index 360a8094723c..0dbe18156904 100644 --- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl +++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl @@ -21,7 +21,7 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; - import android.app.ondeviceintelligence.Content; + import android.os.Bundle; import android.app.ondeviceintelligence.Feature; import android.app.ondeviceintelligence.FeatureDetails; import android.app.ondeviceintelligence.IDownloadCallback; @@ -39,7 +39,7 @@ * * @hide */ - oneway interface IOnDeviceIntelligenceManager { + interface IOnDeviceIntelligenceManager { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)") void getVersion(in RemoteCallback remoteCallback) = 1; @@ -53,18 +53,20 @@ 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; + void requestFeatureDownload(in Feature feature, in ICancellationSignal signal, in IDownloadCallback callback) = 5; @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)") - void requestTokenInfo(in Feature feature, in Content request, in ICancellationSignal signal, + void requestTokenInfo(in Feature feature, in Bundle requestBundle, in ICancellationSignal signal, in ITokenInfoCallback tokenInfocallback) = 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; + void processRequest(in Feature feature, in Bundle requestBundle, 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 Bundle requestBundle, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal, in IStreamingResponseCallback streamingCallback) = 8; + + String getRemoteServicePackageName() = 9; } diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl index 0adf305f2920..45963d2af4e6 100644 --- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl +++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl @@ -1,8 +1,7 @@ package android.app.ondeviceintelligence; -import android.app.ondeviceintelligence.Content; -import android.app.ondeviceintelligence.IProcessingSignal; import android.os.PersistableBundle; +import android.os.Bundle; import android.os.RemoteCallback; /** @@ -11,7 +10,7 @@ import android.os.RemoteCallback; * @hide */ interface IResponseCallback { - void onSuccess(in Content result) = 1; + void onSuccess(in Bundle resultBundle) = 1; void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2; - void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 3; + void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 3; } diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl index 132e53e1ae2e..671abe31ce86 100644 --- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl +++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl @@ -1,10 +1,8 @@ package android.app.ondeviceintelligence; -import android.app.ondeviceintelligence.Content; -import android.app.ondeviceintelligence.IResponseCallback; -import android.app.ondeviceintelligence.IProcessingSignal; import android.os.PersistableBundle; import android.os.RemoteCallback; +import android.os.Bundle; /** @@ -13,8 +11,8 @@ import android.os.RemoteCallback; * @hide */ interface IStreamingResponseCallback { - void onNewContent(in Content result) = 1; - void onSuccess(in Content result) = 2; + void onNewContent(in Bundle processedResult) = 1; + void onSuccess(in Bundle result) = 2; void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3; - void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 4; + void onDataAugmentRequest(in Bundle processedContent, in RemoteCallback responseCallback) = 4; } diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java new file mode 100644 index 000000000000..03ff563a88c0 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java @@ -0,0 +1,198 @@ +/* + * 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.PersistableBundle; + +import androidx.annotation.IntDef; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Exception type to be used for errors related to on-device intelligence system service with + * appropriate error code. + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +public class OnDeviceIntelligenceException extends Exception { + + 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 returned when the OnDeviceIntelligenceManager service is unavailable. + */ + public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100; + + /** + * The connection to remote service failed and the processing state could not be updated. + */ + public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200; + + + /** + * Error code associated with the on-device intelligence failure. + * + * @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, + ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, + PROCESSING_UPDATE_STATUS_CONNECTION_FAILED + }, open = true) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + @interface OnDeviceIntelligenceError { + } + + private final int mErrorCode; + private final PersistableBundle mErrorParams; + + /** Returns the error code of the exception. */ + public int getErrorCode() { + return mErrorCode; + } + + /** Returns the error params of the exception. */ + @NonNull + public PersistableBundle getErrorParams() { + return mErrorParams; + } + + /** + * Creates a new OnDeviceIntelligenceException with the specified error code, error message and + * error params. + * + * @param errorCode The error code. + * @param errorMessage The error message. + * @param errorParams The error params. + */ + public OnDeviceIntelligenceException( + @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage, + @NonNull PersistableBundle errorParams) { + super(errorMessage); + this.mErrorCode = errorCode; + this.mErrorParams = errorParams; + } + + /** + * Creates a new OnDeviceIntelligenceException with the specified error code and error params. + * + * @param errorCode The error code. + * @param errorParams The error params. + */ + public OnDeviceIntelligenceException( + @OnDeviceIntelligenceError int errorCode, + @NonNull PersistableBundle errorParams) { + this.mErrorCode = errorCode; + this.mErrorParams = errorParams; + } + + /** + * Creates a new OnDeviceIntelligenceException with the specified error code and error message. + * + * @param errorCode The error code. + * @param errorMessage The error message. + */ + public OnDeviceIntelligenceException( + @OnDeviceIntelligenceError int errorCode, @NonNull String errorMessage) { + super(errorMessage); + this.mErrorCode = errorCode; + this.mErrorParams = new PersistableBundle(); + } + + /** + * Creates a new OnDeviceIntelligenceException with the specified error code. + * + * @param errorCode The error code. + */ + public OnDeviceIntelligenceException( + @OnDeviceIntelligenceError int errorCode) { + this.mErrorCode = errorCode; + this.mErrorParams = new PersistableBundle(); + } +} diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java index d195c4d52c22..a465e3cbb6ec 100644 --- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java +++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java @@ -28,6 +28,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.ComponentName; import android.content.Context; +import android.graphics.Bitmap; import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; @@ -36,6 +37,7 @@ import android.os.OutcomeReceiver; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; +import android.system.OsConstants; import androidx.annotation.IntDef; @@ -63,7 +65,7 @@ import java.util.function.LongConsumer; @SystemApi @SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE) @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) -public class OnDeviceIntelligenceManager { +public final class OnDeviceIntelligenceManager { /** * @hide */ @@ -118,14 +120,13 @@ public class OnDeviceIntelligenceManager { @Nullable @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName() { - String serviceConfigValue = mContext.getResources().getString( - R.string.config_defaultOnDeviceSandboxedInferenceService); - ComponentName componentName = ComponentName.unflattenFromString(serviceConfigValue); - if (componentName != null) { - return componentName.getPackageName(); + String result; + try{ + result = mService.getRemoteServicePackageName(); + } catch (RemoteException e){ + throw e.rethrowFromSystemServer(); } - - return null; + return result; } /** @@ -139,7 +140,7 @@ public class OnDeviceIntelligenceManager { public void getFeature( int featureId, @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceManagerException> featureReceiver) { + @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceException> featureReceiver) { try { IFeatureCallback callback = new IFeatureCallback.Stub() { @@ -154,7 +155,7 @@ public class OnDeviceIntelligenceManager { PersistableBundle errorParams) { Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> featureReceiver.onError( - new OnDeviceIntelligenceManagerException( + new OnDeviceIntelligenceException( errorCode, errorMessage, errorParams)))); } }; @@ -173,7 +174,7 @@ public class OnDeviceIntelligenceManager { @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures( @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceManagerException> featureListReceiver) { + @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceException> featureListReceiver) { try { IListFeaturesCallback callback = new IListFeaturesCallback.Stub() { @@ -188,7 +189,7 @@ public class OnDeviceIntelligenceManager { PersistableBundle errorParams) { Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> featureListReceiver.onError( - new OnDeviceIntelligenceManagerException( + new OnDeviceIntelligenceException( errorCode, errorMessage, errorParams)))); } }; @@ -211,7 +212,7 @@ public class OnDeviceIntelligenceManager { @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull Feature feature, @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceManagerException> featureDetailsReceiver) { + @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> featureDetailsReceiver) { try { IFeatureDetailsCallback callback = new IFeatureDetailsCallback.Stub() { @@ -226,7 +227,7 @@ public class OnDeviceIntelligenceManager { PersistableBundle errorParams) { Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> featureDetailsReceiver.onError( - new OnDeviceIntelligenceManagerException(errorCode, + new OnDeviceIntelligenceException(errorCode, errorMessage, errorParams)))); } }; @@ -243,9 +244,8 @@ public class OnDeviceIntelligenceManager { * * 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. + * In such cases, clients should query the feature status via {@link #getFeatureDetails} to + * check on the feature's download status. * * @param feature feature to request download for. * @param callback callback to populate updates about download status. @@ -284,7 +284,7 @@ public class OnDeviceIntelligenceManager { @Override public void onDownloadCompleted(PersistableBundle downloadParams) { Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( - () -> onDownloadCompleted(downloadParams))); + () -> callback.onDownloadCompleted(downloadParams))); } }; @@ -305,7 +305,8 @@ public class OnDeviceIntelligenceManager { * provided {@link Feature}. * * @param feature feature associated with the request. - * @param request request that contains the content data and associated params. + * @param request request and associated params represented by the Bundle + * data. * @param outcomeReceiver callback to populate the token info or exception in case of * failure. * @param cancellationSignal signal to invoke cancellation on the operation in the remote @@ -313,11 +314,11 @@ public class OnDeviceIntelligenceManager { * @param callbackExecutor executor to run the callback on. */ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) - public void requestTokenInfo(@NonNull Feature feature, @NonNull Content request, + public void requestTokenInfo(@NonNull Feature feature, @NonNull @InferenceParams Bundle request, @Nullable CancellationSignal cancellationSignal, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull OutcomeReceiver<TokenInfo, - OnDeviceIntelligenceManagerException> outcomeReceiver) { + OnDeviceIntelligenceException> outcomeReceiver) { try { ITokenInfoCallback callback = new ITokenInfoCallback.Stub() { @Override @@ -331,7 +332,7 @@ public class OnDeviceIntelligenceManager { PersistableBundle errorParams) { Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> outcomeReceiver.onError( - new OnDeviceIntelligenceManagerProcessingException( + new OnDeviceIntelligenceException( errorCode, errorMessage, errorParams)))); } }; @@ -357,30 +358,30 @@ public class OnDeviceIntelligenceManager { * failure. * * @param feature feature associated with the request. - * @param request request that contains the Content data and - * associated params. + * @param request request and associated params represented by the Bundle + * data. * @param requestType type of request being sent for processing the content. * @param cancellationSignal signal to invoke cancellation. * @param processingSignal signal to send custom signals in the * remote implementation. * @param callbackExecutor executor to run the callback on. - * @param responseCallback callback to populate the response content and + * @param processingCallback callback to populate the response content and * associated params. */ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) - public void processRequest(@NonNull Feature feature, @Nullable Content request, + public void processRequest(@NonNull Feature feature, @NonNull @InferenceParams Bundle request, @RequestType int requestType, @Nullable CancellationSignal cancellationSignal, @Nullable ProcessingSignal processingSignal, @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull ProcessingOutcomeReceiver responseCallback) { + @NonNull ProcessingCallback processingCallback) { try { IResponseCallback callback = new IResponseCallback.Stub() { @Override - public void onSuccess(Content result) { + public void onSuccess(@InferenceParams Bundle result) { Binder.withCleanCallingIdentity(() -> { - callbackExecutor.execute(() -> responseCallback.onResult(result)); + callbackExecutor.execute(() -> processingCallback.onResult(result)); }); } @@ -388,16 +389,16 @@ public class OnDeviceIntelligenceManager { public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) { Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( - () -> responseCallback.onError( - new OnDeviceIntelligenceManagerProcessingException( + () -> processingCallback.onError( + new OnDeviceIntelligenceException( errorCode, errorMessage, errorParams)))); } @Override - public void onDataAugmentRequest(@NonNull Content content, + public void onDataAugmentRequest(@NonNull @InferenceParams Bundle request, @NonNull RemoteCallback contentCallback) { Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( - () -> responseCallback.onDataAugmentRequest(content, result -> { + () -> processingCallback.onDataAugmentRequest(request, result -> { Bundle bundle = new Bundle(); bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result); callbackExecutor.execute(() -> contentCallback.sendResult(bundle)); @@ -430,15 +431,15 @@ public class OnDeviceIntelligenceManager { * 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 StreamedProcessingOutcomeReceiver#onNewContent}. After the streaming is complete, - * the service should call {@link StreamedProcessingOutcomeReceiver#onResult} and can optionally - * populate the complete the full response {@link Content} as part of the callback in cases - * when the final response contains an enhanced aggregation of the Contents already + * {@link StreamingProcessingCallback#onPartialResult}. After the streaming is complete, + * the service should call {@link StreamingProcessingCallback#onResult} and can optionally + * populate the complete the full response {@link Bundle} as part of the callback in cases + * when the final 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 request request and associated params represented by the Bundle + * data. * @param requestType type of request being sent for processing the content. * @param cancellationSignal signal to invoke cancellation. * @param processingSignal signal to send custom signals in the @@ -448,27 +449,27 @@ public class OnDeviceIntelligenceManager { * @param callbackExecutor executor to run the callback on. */ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) - public void processRequestStreaming(@NonNull Feature feature, @Nullable Content request, + public void processRequestStreaming(@NonNull Feature feature, @NonNull @InferenceParams Bundle request, @RequestType int requestType, @Nullable CancellationSignal cancellationSignal, @Nullable ProcessingSignal processingSignal, @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull StreamedProcessingOutcomeReceiver streamingResponseCallback) { + @NonNull StreamingProcessingCallback streamingProcessingCallback) { try { IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() { @Override - public void onNewContent(Content result) { + public void onNewContent(@InferenceParams Bundle result) { Binder.withCleanCallingIdentity(() -> { callbackExecutor.execute( - () -> streamingResponseCallback.onNewContent(result)); + () -> streamingProcessingCallback.onPartialResult(result)); }); } @Override - public void onSuccess(Content result) { + public void onSuccess(@InferenceParams Bundle result) { Binder.withCleanCallingIdentity(() -> { callbackExecutor.execute( - () -> streamingResponseCallback.onResult(result)); + () -> streamingProcessingCallback.onResult(result)); }); } @@ -477,18 +478,18 @@ public class OnDeviceIntelligenceManager { PersistableBundle errorParams) { Binder.withCleanCallingIdentity(() -> { callbackExecutor.execute( - () -> streamingResponseCallback.onError( - new OnDeviceIntelligenceManagerProcessingException( + () -> streamingProcessingCallback.onError( + new OnDeviceIntelligenceException( errorCode, errorMessage, errorParams))); }); } @Override - public void onDataAugmentRequest(@NonNull Content content, + public void onDataAugmentRequest(@NonNull @InferenceParams Bundle content, @NonNull RemoteCallback contentCallback) { Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( - () -> streamingResponseCallback.onDataAugmentRequest(content, + () -> streamingProcessingCallback.onDataAugmentRequest(content, contentResponse -> { Bundle bundle = new Bundle(); bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, @@ -519,7 +520,7 @@ public class OnDeviceIntelligenceManager { } - /** Request inference with provided Content and Params. */ + /** Request inference with provided Bundle and Params. */ public static final int REQUEST_TYPE_INFERENCE = 0; /** @@ -530,7 +531,7 @@ public class OnDeviceIntelligenceManager { */ public static final int REQUEST_TYPE_PREPARE = 1; - /** Request Embeddings of the passed-in Content. */ + /** Request Embeddings of the passed-in Bundle. */ public static final int REQUEST_TYPE_EMBEDDINGS = 2; /** @@ -547,154 +548,30 @@ public class OnDeviceIntelligenceManager { 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; - - /** - * Error code to be used for on device intelligence manager failures. - * - * @hide - */ - @IntDef( - value = { - ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE - }, open = true) - @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) - @interface OnDeviceIntelligenceManagerErrorCode { - } - - private final int mErrorCode; - private final PersistableBundle errorParams; - - public OnDeviceIntelligenceManagerException( - @OnDeviceIntelligenceManagerErrorCode int errorCode, @NonNull String errorMessage, - @NonNull PersistableBundle errorParams) { - super(errorMessage); - this.mErrorCode = errorCode; - this.errorParams = errorParams; - } - - public OnDeviceIntelligenceManagerException( - @OnDeviceIntelligenceManagerErrorCode 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} . + * {@link Bundle}s annotated with this type will be validated that they are in-effect read-only + * when passed to inference service via Binder IPC. Following restrictions apply : + * <ul> + * <li> Any primitive types or their collections can be added as usual.</li> + * <li>IBinder objects should *not* be added.</li> + * <li>Parcelable data which has no active-objects, should be added as + * {@link Bundle#putByteArray}</li> + * <li>Parcelables have active-objects, only following types will be allowed</li> + * <ul> + * <li>{@link Bitmap} set as {@link Bitmap#setImmutable()}</li> + * <li>{@link android.database.CursorWindow}</li> + * <li>{@link android.os.ParcelFileDescriptor} opened in + * {@link android.os.ParcelFileDescriptor#MODE_READ_ONLY}</li> + * <li>{@link android.os.SharedMemory} set to {@link OsConstants#PROT_READ}</li> + * </ul> + * </ul> + * + * In all other scenarios the system-server might throw a + * {@link android.os.BadParcelableException} if the Bundle validation fails. + * + * @hide */ - 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); - } + @Target({ElementType.PARAMETER, ElementType.FIELD}) + public @interface InferenceParams { } } diff --git a/core/java/android/app/ondeviceintelligence/ProcessingCallback.java b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java new file mode 100644 index 000000000000..4d936ea45c54 --- /dev/null +++ b/core/java/android/app/ondeviceintelligence/ProcessingCallback.java @@ -0,0 +1,71 @@ +/* + * 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.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams; + +import java.util.function.Consumer; + +/** + * Callback to populate the processed response or any error that occurred during the + * request processing. This callback also provides a method to request additional data to be + * augmented to the request-processing, using the partial response that was already + * processed in the remote implementation. + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +public interface ProcessingCallback { + /** + * Invoked when request has been processed and result is ready to be propagated to the + * caller. + * + * @param result Response to be passed as a result. + */ + void onResult(@NonNull @InferenceParams Bundle result); + + /** + * Called when the request processing fails. The failure details are indicated by the + * {@link OnDeviceIntelligenceException} passed as an argument to this method. + * + * @param error An exception with more details about the error that occurred. + */ + void onError(@NonNull OnDeviceIntelligenceException error); + + /** + * Callback to be invoked in cases where the remote service needs to perform retrieval or + * transformation operations based on a partially processed request, in order to augment the + * final response, by using the additional context sent via this callback. + * + * @param processedContent The content payload that should be used to augment ongoing request. + * @param contentConsumer The augmentation data that should be sent to remote + * service for further processing a request. Bundle passed in here is + * expected to be non-null or EMPTY when there is no response. + */ + default void onDataAugmentRequest( + @NonNull @InferenceParams Bundle processedContent, + @NonNull Consumer<Bundle> contentConsumer) { + contentConsumer.accept(Bundle.EMPTY); + } +} diff --git a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java deleted file mode 100644 index b0b6e1948cf0..000000000000 --- a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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; - -import java.util.function.Consumer; - -/** - * Response Callback to populate the processed response or any error that occurred during the - * request processing. This callback also provides a method to request additional data to be - * augmented to the request-processing, using the partial {@link Content} that was already - * processed in the remote implementation. - * - * @hide - */ -@SystemApi -@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) -public interface ProcessingOutcomeReceiver extends - OutcomeReceiver<Content, - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> { - /** - * Callback to be invoked in cases where the remote service needs to perform retrieval or - * transformation operations based on a partially processed request, in order to augment the - * final response, by using the additional context sent via this callback. - * - * @param content The content payload that should be used to augment ongoing request. - * @param contentConsumer The augmentation data that should be sent to remote - * service for further processing a request. - */ - default void onDataAugmentRequest(@NonNull Content content, - @NonNull Consumer<Content> contentConsumer) { - contentConsumer.accept(null); - } -} diff --git a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java index ac2b0329496c..41f1807a9b3d 100644 --- a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java +++ b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java @@ -21,19 +21,21 @@ import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.os.Bundle; +import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams; /** - * 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. + * Streaming variant of {@link ProcessingCallback} 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 StreamedProcessingOutcomeReceiver extends ProcessingOutcomeReceiver { +public interface StreamingProcessingCallback extends ProcessingCallback { /** - * Callback that would be invoked when a part of the response i.e. some {@link Content} is - * already processed and needs to be passed onto the caller. + * Callback that would be invoked when a part of the response i.e. some response is + * already processed, and needs to be passed onto the caller. */ - void onNewContent(@NonNull Content content); + void onPartialResult(@NonNull @InferenceParams Bundle partialResult); } diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java index fd72c491bf16..9573e6f940b4 100644 --- a/core/java/android/app/wearable/WearableSensingManager.java +++ b/core/java/android/app/wearable/WearableSensingManager.java @@ -95,11 +95,12 @@ public class WearableSensingManager { /** * The value of the status code that indicates one or more of the requested events are not * supported. + * + * @deprecated WearableSensingManager does not deal with events. Use {@link + * STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of + * {@link WearableSensingService}. */ - // TODO(b/324635656): Deprecate this status code. Update Javadoc: - // @deprecated WearableSensingManager does not deal with events. Use {@link - // STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of - // {@link WearableSensingService}. + @Deprecated public static final int STATUS_UNSUPPORTED = 2; /** @@ -121,7 +122,6 @@ public class WearableSensingManager { * The value of the status code that indicates the method called is not supported by the * implementation of {@link WearableSensingService}. */ - @FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE) public static final int STATUS_UNSUPPORTED_OPERATION = 6; diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 88527059b3f9..bd04634ac4f1 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -333,10 +333,9 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { } /** - * Specifies permissions necessary to launch this activity via - * {@link android.content.Context#startActivity} when passing content URIs. The default value is - * {@code none}, meaning no specific permissions are required. Setting this attribute restricts - * activity invocation based on the invoker's permissions. + * Specifies permissions necessary to launch this activity when passing content URIs. The + * default value is {@code none}, meaning no specific permissions are required. Setting this + * attribute restricts activity invocation based on the invoker's permissions. * @hide */ @RequiredContentUriPermission diff --git a/core/java/android/content/rollback/OWNERS b/core/java/android/content/rollback/OWNERS index 8e5a0d8af550..c328b7c36b8f 100644 --- a/core/java/android/content/rollback/OWNERS +++ b/core/java/android/content/rollback/OWNERS @@ -1,5 +1,3 @@ # Bug component: 819107 -ancr@google.com -harshitmahajan@google.com -robertogil@google.com +include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index dc8f4b448931..238c381fc00f 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -6098,7 +6098,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE = new Key<android.util.Range<Float>>("android.efv.paddingZoomFactorRange", new TypeReference<android.util.Range<Float>>() {{ }}); diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 083d49f36b03..85f9900c9bc2 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -142,7 +142,7 @@ public final class CameraExtensionCharacteristics { /** * An extension that aims to lock and stabilize a given region or object of interest. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final int EXTENSION_EYES_FREE_VIDEOGRAPHY = 5; /** @@ -559,7 +559,7 @@ public final class CameraExtensionCharacteristics { public ExtensionConnectionManager() { IntArray extensionList = new IntArray(EXTENSION_LIST.length); extensionList.addAll(EXTENSION_LIST); - if (Flags.concertMode()) { + if (Flags.concertModeApi()) { extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY); } @@ -762,7 +762,7 @@ public final class CameraExtensionCharacteristics { IntArray extensionList = new IntArray(EXTENSION_LIST.length); extensionList.addAll(EXTENSION_LIST); - if (Flags.concertMode()) { + if (Flags.concertModeApi()) { extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY); } @@ -1544,7 +1544,7 @@ public final class CameraExtensionCharacteristics { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE = CameraCharacteristics.EFV_PADDING_ZOOM_FACTOR_RANGE; } diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 9fb561bb4211..7754e328bbf9 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -3905,7 +3905,7 @@ public abstract class CameraMetadata<TKey> { * @see CaptureRequest#EFV_STABILIZATION_MODE * @hide */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final int EFV_STABILIZATION_MODE_OFF = 0; /** @@ -3913,7 +3913,7 @@ public abstract class CameraMetadata<TKey> { * @see CaptureRequest#EFV_STABILIZATION_MODE * @hide */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; /** @@ -3923,7 +3923,7 @@ public abstract class CameraMetadata<TKey> { * @see CaptureRequest#EFV_STABILIZATION_MODE * @hide */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index c0db77ca0f05..13d5c7e74e4b 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -4335,7 +4335,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = new Key<Float>("android.efv.paddingZoomFactor", float.class); @@ -4358,7 +4358,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Boolean> EFV_AUTO_ZOOM = new Key<Boolean>("android.efv.autoZoom", boolean.class); @@ -4379,7 +4379,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = new Key<Float>("android.efv.maxPaddingZoomFactor", float.class); @@ -4406,7 +4406,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Integer> EFV_STABILIZATION_MODE = new Key<Integer>("android.efv.stabilizationMode", int.class); @@ -4428,7 +4428,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }}); @@ -4445,7 +4445,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_ROTATE_VIEWPORT = new Key<Float>("android.efv.rotateViewport", float.class); diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index a01c23d984f4..7145501c718d 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -5940,7 +5940,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<int[]> EFV_PADDING_REGION = new Key<int[]>("android.efv.paddingRegion", int[].class); @@ -5961,7 +5961,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION = new Key<int[]>("android.efv.autoZoomPaddingRegion", int[].class); @@ -5984,7 +5984,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES = new Key<android.graphics.PointF[]>("android.efv.targetCoordinates", android.graphics.PointF[].class); @@ -6014,7 +6014,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = new Key<Float>("android.efv.paddingZoomFactor", float.class); @@ -6041,7 +6041,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Integer> EFV_STABILIZATION_MODE = new Key<Integer>("android.efv.stabilizationMode", int.class); @@ -6064,7 +6064,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Boolean> EFV_AUTO_ZOOM = new Key<Boolean>("android.efv.autoZoom", boolean.class); @@ -6081,7 +6081,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_ROTATE_VIEWPORT = new Key<Float>("android.efv.rotateViewport", float.class); @@ -6103,7 +6103,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }}); @@ -6124,7 +6124,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @hide */ @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = new Key<Float>("android.efv.maxPaddingZoomFactor", float.class); diff --git a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java index 32039c6ec0ba..c33956b59f2f 100644 --- a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java +++ b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java @@ -40,7 +40,7 @@ import com.android.internal.camera.flags.Flags; * @see CaptureRequest * @see CameraExtensionSession */ -@FlaggedApi(Flags.FLAG_CONCERT_MODE) +@FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public final class ExtensionCaptureRequest { /** @@ -74,7 +74,7 @@ public final class ExtensionCaptureRequest { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_PADDING_ZOOM_FACTOR; /** @@ -99,7 +99,7 @@ public final class ExtensionCaptureRequest { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureRequest.EFV_AUTO_ZOOM; /** @@ -125,7 +125,7 @@ public final class ExtensionCaptureRequest { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_MAX_PADDING_ZOOM_FACTOR; /** @@ -152,7 +152,7 @@ public final class ExtensionCaptureRequest { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureRequest.EFV_STABILIZATION_MODE; /** @@ -176,7 +176,7 @@ public final class ExtensionCaptureRequest { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureRequest.EFV_TRANSLATE_VIEWPORT; /** @@ -193,7 +193,7 @@ public final class ExtensionCaptureRequest { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureRequest.EFV_ROTATE_VIEWPORT; @@ -205,14 +205,14 @@ public final class ExtensionCaptureRequest { * <p>No stabilization.</p> * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final int EFV_STABILIZATION_MODE_OFF = CaptureRequest.EFV_STABILIZATION_MODE_OFF; /** * <p>Gimbal stabilization mode.</p> * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final int EFV_STABILIZATION_MODE_GIMBAL = CaptureRequest.EFV_STABILIZATION_MODE_GIMBAL; /** @@ -221,7 +221,7 @@ public final class ExtensionCaptureRequest { * stabilization to directionally steady the target region.</p> * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final int EFV_STABILIZATION_MODE_LOCKED = CaptureRequest.EFV_STABILIZATION_MODE_LOCKED; -}
\ No newline at end of file +} diff --git a/core/java/android/hardware/camera2/ExtensionCaptureResult.java b/core/java/android/hardware/camera2/ExtensionCaptureResult.java index 5c9990975a9b..95feb2fd268a 100644 --- a/core/java/android/hardware/camera2/ExtensionCaptureResult.java +++ b/core/java/android/hardware/camera2/ExtensionCaptureResult.java @@ -42,7 +42,7 @@ import com.android.internal.camera.flags.Flags; * @see CaptureRequest * @see CameraExtensionSession */ -@FlaggedApi(Flags.FLAG_CONCERT_MODE) +@FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public final class ExtensionCaptureResult { /** @@ -66,7 +66,7 @@ public final class ExtensionCaptureResult { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<int[]> EFV_PADDING_REGION = CaptureResult.EFV_PADDING_REGION; /** @@ -90,7 +90,7 @@ public final class ExtensionCaptureResult { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION = CaptureResult.EFV_AUTO_ZOOM_PADDING_REGION; /** @@ -113,7 +113,7 @@ public final class ExtensionCaptureResult { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES = CaptureResult.EFV_TARGET_COORDINATES; /** @@ -147,7 +147,7 @@ public final class ExtensionCaptureResult { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureResult.EFV_PADDING_ZOOM_FACTOR; /** @@ -174,7 +174,7 @@ public final class ExtensionCaptureResult { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureResult.EFV_STABILIZATION_MODE; /** @@ -199,7 +199,7 @@ public final class ExtensionCaptureResult { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureResult.EFV_AUTO_ZOOM; /** @@ -216,7 +216,7 @@ public final class ExtensionCaptureResult { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureResult.EFV_ROTATE_VIEWPORT; /** @@ -240,7 +240,7 @@ public final class ExtensionCaptureResult { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureResult.EFV_TRANSLATE_VIEWPORT; /** @@ -266,7 +266,7 @@ public final class ExtensionCaptureResult { @PublicKey @NonNull @ExtensionKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) + @FlaggedApi(Flags.FLAG_CONCERT_MODE_API) public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureResult.EFV_MAX_PADDING_ZOOM_FACTOR; -}
\ No newline at end of file +} diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl index c4f653cdfed7..9d46b559ce37 100644 --- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl +++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl @@ -25,5 +25,5 @@ parcelable CameraSessionConfig CameraMetadataNative sessionParameter; int sessionTemplateId; int sessionType; - int colorSpace; + int colorSpace = -1; } diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 5b7f8bb00f25..6d9b51cbd003 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -348,7 +348,23 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR); cameraOutput.setReadoutTimestampEnabled(false); cameraOutput.setPhysicalCameraId(output.physicalCameraId); - cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile); + if (Flags.extension10Bit()) { + boolean validDynamicRangeProfile = false; + for (long profile = DynamicRangeProfiles.STANDARD; + profile < DynamicRangeProfiles.PUBLIC_MAX; profile <<= 1) { + if (output.dynamicRangeProfile == profile) { + validDynamicRangeProfile = true; + break; + } + } + if (validDynamicRangeProfile) { + cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile); + } else { + Log.e(TAG, "Extension configured dynamic range profile " + + output.dynamicRangeProfile + + " is not valid, using default DynamicRangeProfile.STANDARD"); + } + } outputList.add(cameraOutput); mCameraConfigMap.put(cameraOutput.getSurface(), output); } @@ -363,9 +379,15 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType, outputList, new CameraExtensionUtils.HandlerExecutor(mHandler), new SessionStateHandler()); - if (sessionConfig.colorSpace != ColorSpaceProfiles.UNSPECIFIED) { - sessionConfiguration.setColorSpace( - ColorSpace.Named.values()[sessionConfig.colorSpace]); + if (Flags.extension10Bit()) { + if (sessionConfig.colorSpace >= 0 + && sessionConfig.colorSpace < ColorSpace.Named.values().length) { + sessionConfiguration.setColorSpace( + ColorSpace.Named.values()[sessionConfig.colorSpace]); + } else { + Log.e(TAG, "Extension configured color space " + sessionConfig.colorSpace + + " is not valid, using default unspecified color space"); + } } if ((sessionConfig.sessionParameter != null) && (!sessionConfig.sessionParameter.isEmpty())) { diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl index 73257ed7680a..799c7545968e 100644 --- a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl +++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl @@ -20,7 +20,6 @@ import android.app.ondeviceintelligence.IStreamingResponseCallback; import android.app.ondeviceintelligence.IResponseCallback; import android.app.ondeviceintelligence.ITokenInfoCallback; import android.app.ondeviceintelligence.IProcessingSignal; -import android.app.ondeviceintelligence.Content; import android.app.ondeviceintelligence.Feature; import android.os.ICancellationSignal; import android.os.PersistableBundle; @@ -35,12 +34,12 @@ import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback; */ oneway interface IOnDeviceSandboxedInferenceService { void registerRemoteStorageService(in IRemoteStorageService storageService); - void requestTokenInfo(int callerUid, in Feature feature, in Content request, in ICancellationSignal cancellationSignal, + void requestTokenInfo(int callerUid, in Feature feature, in Bundle request, in ICancellationSignal cancellationSignal, in ITokenInfoCallback tokenInfoCallback); - void processRequest(int callerUid, in Feature feature, in Content request, in int requestType, + void processRequest(int callerUid, in Feature feature, in Bundle request, in int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal, in IResponseCallback callback); - void processRequestStreaming(int callerUid, in Feature feature, in Content request, in int requestType, + void processRequestStreaming(int callerUid, in Feature feature, in Bundle request, in int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal, in IStreamingResponseCallback callback); void updateProcessingState(in Bundle processingState, diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java index fce3689bb8b3..27e862868858 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java @@ -32,7 +32,9 @@ import android.app.ondeviceintelligence.IDownloadCallback; import android.app.ondeviceintelligence.IFeatureCallback; import android.app.ondeviceintelligence.IFeatureDetailsCallback; import android.app.ondeviceintelligence.IListFeaturesCallback; +import android.app.ondeviceintelligence.OnDeviceIntelligenceException; import android.app.ondeviceintelligence.OnDeviceIntelligenceManager; +import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams; import android.content.Intent; import android.os.Binder; import android.os.Bundle; @@ -48,14 +50,8 @@ import android.util.Slog; import com.android.internal.infra.AndroidFuture; -import androidx.annotation.IntDef; - import java.io.File; import java.io.FileNotFoundException; -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.Map; import java.util.Objects; @@ -232,9 +228,9 @@ public abstract class OnDeviceIntelligenceService extends Service { * @param callbackExecutor executor to the run status callback on. * @param statusReceiver receiver to get status of the update state operation. */ - public final void updateProcessingState(@NonNull Bundle processingState, + public final void updateProcessingState(@NonNull @InferenceParams Bundle processingState, @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> statusReceiver) { + @NonNull OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> statusReceiver) { Objects.requireNonNull(callbackExecutor); if (mRemoteProcessingService == null) { throw new IllegalStateException("Remote processing service is unavailable."); @@ -254,7 +250,7 @@ public abstract class OnDeviceIntelligenceService extends Service { public void onFailure(int errorCode, String errorMessage) { Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> statusReceiver.onError( - new OnDeviceUpdateProcessingException( + new OnDeviceIntelligenceException( errorCode, errorMessage)))); } }); @@ -265,7 +261,7 @@ public abstract class OnDeviceIntelligenceService extends Service { } private OutcomeReceiver<Feature, - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback( + OnDeviceIntelligenceException> wrapFeatureCallback( IFeatureCallback featureCallback) { return new OutcomeReceiver<>() { @Override @@ -279,7 +275,7 @@ public abstract class OnDeviceIntelligenceService extends Service { @Override public void onError( - @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) { + @NonNull OnDeviceIntelligenceException exception) { try { featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(), exception.getErrorParams()); @@ -291,7 +287,7 @@ public abstract class OnDeviceIntelligenceService extends Service { } private OutcomeReceiver<List<Feature>, - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapListFeaturesCallback( + OnDeviceIntelligenceException> wrapListFeaturesCallback( IListFeaturesCallback listFeaturesCallback) { return new OutcomeReceiver<>() { @Override @@ -305,7 +301,7 @@ public abstract class OnDeviceIntelligenceService extends Service { @Override public void onError( - @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) { + @NonNull OnDeviceIntelligenceException exception) { try { listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(), exception.getErrorParams()); @@ -317,7 +313,7 @@ public abstract class OnDeviceIntelligenceService extends Service { } private OutcomeReceiver<FeatureDetails, - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureDetailsCallback( + OnDeviceIntelligenceException> wrapFeatureDetailsCallback( IFeatureDetailsCallback featureStatusCallback) { return new OutcomeReceiver<>() { @Override @@ -331,7 +327,7 @@ public abstract class OnDeviceIntelligenceService extends Service { @Override public void onError( - @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) { + @NonNull OnDeviceIntelligenceException exception) { try { featureStatusCallback.onFailure(exception.getErrorCode(), exception.getMessage(), exception.getErrorParams()); @@ -444,7 +440,7 @@ public abstract class OnDeviceIntelligenceService extends Service { */ public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature, @NonNull OutcomeReceiver<FeatureDetails, - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback); + OnDeviceIntelligenceException> featureDetailsCallback); /** @@ -455,7 +451,7 @@ public abstract class OnDeviceIntelligenceService extends Service { */ public abstract void onGetFeature(int callerUid, int featureId, @NonNull OutcomeReceiver<Feature, - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback); + OnDeviceIntelligenceException> featureCallback); /** * List all features which are available in the remote implementation. The implementation might @@ -465,7 +461,7 @@ public abstract class OnDeviceIntelligenceService extends Service { * @param listFeaturesCallback callback to populate the features list. */ public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>, - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback); + OnDeviceIntelligenceException> listFeaturesCallback); /** * Provides a long value representing the version of the remote implementation processing @@ -474,60 +470,4 @@ public abstract class OnDeviceIntelligenceService extends Service { * @param versionConsumer consumer to populate the version. */ public abstract void onGetVersion(@NonNull LongConsumer versionConsumer); - - - /** - * Exception type to be populated when calls to {@link #updateProcessingState} fail. - */ - public static class OnDeviceUpdateProcessingException extends - OnDeviceIntelligenceServiceException { - /** - * The connection to remote service failed and the processing state could not be updated. - */ - public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; - - - /** - * @hide - */ - @IntDef(value = { - PROCESSING_UPDATE_STATUS_CONNECTION_FAILED - }, open = true) - @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, - ElementType.FIELD}) - @Retention(RetentionPolicy.SOURCE) - public @interface ErrorCode { - } - - public OnDeviceUpdateProcessingException(@ErrorCode int errorCode) { - super(errorCode); - } - - public OnDeviceUpdateProcessingException(@ErrorCode int errorCode, - @NonNull String errorMessage) { - super(errorCode, errorMessage); - } - } - - /** - * Exception type to be used for surfacing errors to service implementation. - */ - public abstract static class OnDeviceIntelligenceServiceException extends Exception { - private final int mErrorCode; - - public OnDeviceIntelligenceServiceException(int errorCode) { - this.mErrorCode = errorCode; - } - - public OnDeviceIntelligenceServiceException(int errorCode, - @NonNull String errorMessage) { - super(errorMessage); - this.mErrorCode = errorCode; - } - - public int getErrorCode() { - return mErrorCode; - } - - } } diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java index 7f7f9c28c60e..d943c80e525b 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java @@ -27,20 +27,21 @@ import android.annotation.SdkConstant; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Service; -import android.app.ondeviceintelligence.Content; +import android.app.ondeviceintelligence.OnDeviceIntelligenceException; +import android.os.Bundle; import android.app.ondeviceintelligence.Feature; import android.app.ondeviceintelligence.IProcessingSignal; import android.app.ondeviceintelligence.IResponseCallback; import android.app.ondeviceintelligence.IStreamingResponseCallback; import android.app.ondeviceintelligence.ITokenInfoCallback; import android.app.ondeviceintelligence.OnDeviceIntelligenceManager; +import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.InferenceParams; import android.app.ondeviceintelligence.ProcessingSignal; -import android.app.ondeviceintelligence.ProcessingOutcomeReceiver; -import android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver; +import android.app.ondeviceintelligence.ProcessingCallback; +import android.app.ondeviceintelligence.StreamingProcessingCallback; import android.app.ondeviceintelligence.TokenInfo; import android.content.Context; import android.content.Intent; -import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; import android.os.HandlerExecutor; @@ -51,7 +52,6 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; -import android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException; import android.util.Log; import android.util.Slog; @@ -121,7 +121,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { } @Override - public void requestTokenInfo(int callerUid, Feature feature, Content request, + public void requestTokenInfo(int callerUid, Feature feature, Bundle request, ICancellationSignal cancellationSignal, ITokenInfoCallback tokenInfoCallback) { Objects.requireNonNull(feature); @@ -134,7 +134,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { } @Override - public void processRequestStreaming(int callerUid, Feature feature, Content request, + public void processRequestStreaming(int callerUid, Feature feature, Bundle request, int requestType, ICancellationSignal cancellationSignal, IProcessingSignal processingSignal, IStreamingResponseCallback callback) { @@ -151,7 +151,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { } @Override - public void processRequest(int callerUid, Feature feature, Content request, + public void processRequest(int callerUid, Feature feature, Bundle request, int requestType, ICancellationSignal cancellationSignal, IProcessingSignal processingSignal, IResponseCallback callback) { @@ -198,18 +198,17 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { @NonNull public abstract void onTokenInfoRequest( int callerUid, @NonNull Feature feature, - @NonNull Content request, + @NonNull @InferenceParams Bundle request, @Nullable CancellationSignal cancellationSignal, - @NonNull OutcomeReceiver<TokenInfo, - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback); + @NonNull OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback); /** * Invoked when caller provides a request for a particular feature to be processed in a * streaming manner. The expectation from the implementation is that when processing the * request, - * it periodically populates the {@link StreamedProcessingOutcomeReceiver#onNewContent} to continuously - * provide partial Content results for the caller to utilize. Optionally the implementation can - * provide the complete response in the {@link StreamedProcessingOutcomeReceiver#onResult} upon + * it periodically populates the {@link StreamingProcessingCallback#onPartialResult} to continuously + * provide partial Bundle results for the caller to utilize. Optionally the implementation can + * provide the complete response in the {@link StreamingProcessingCallback#onResult} upon * processing completion. * * @param callerUid UID of the caller that initiated this call chain. @@ -225,11 +224,11 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { @NonNull public abstract void onProcessRequestStreaming( int callerUid, @NonNull Feature feature, - @Nullable Content request, + @NonNull @InferenceParams Bundle request, @OnDeviceIntelligenceManager.RequestType int requestType, @Nullable CancellationSignal cancellationSignal, @Nullable ProcessingSignal processingSignal, - @NonNull StreamedProcessingOutcomeReceiver callback); + @NonNull StreamingProcessingCallback callback); /** * Invoked when caller provides a request for a particular feature to be processed in one shot @@ -251,11 +250,11 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { @NonNull public abstract void onProcessRequest( int callerUid, @NonNull Feature feature, - @Nullable Content request, + @NonNull @InferenceParams Bundle request, @OnDeviceIntelligenceManager.RequestType int requestType, @Nullable CancellationSignal cancellationSignal, @Nullable ProcessingSignal processingSignal, - @NonNull ProcessingOutcomeReceiver callback); + @NonNull ProcessingCallback callback); /** @@ -267,9 +266,9 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { * @param callback callback to populate the update status and if there are params * associated with the status. */ - public abstract void onUpdateProcessingState(@NonNull Bundle processingState, + public abstract void onUpdateProcessingState(@NonNull @InferenceParams Bundle processingState, @NonNull OutcomeReceiver<PersistableBundle, - OnDeviceUpdateProcessingException> callback); + OnDeviceIntelligenceException> callback); /** @@ -344,7 +343,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { /** * Returns the {@link Executor} to use for incoming IPC from request sender into your service * implementation. For e.g. see - * {@link ProcessingOutcomeReceiver#onDataAugmentRequest(Content, + * {@link ProcessingCallback#onDataAugmentRequest(Bundle, * Consumer)} where we use the executor to populate the consumer. * <p> * Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to @@ -380,13 +379,13 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { }); } - private ProcessingOutcomeReceiver wrapResponseCallback( + private ProcessingCallback wrapResponseCallback( IResponseCallback callback) { - return new ProcessingOutcomeReceiver() { + return new ProcessingCallback() { @Override - public void onResult(@androidx.annotation.NonNull Content response) { + public void onResult(@androidx.annotation.NonNull Bundle result) { try { - callback.onSuccess(response); + callback.onSuccess(result); } catch (RemoteException e) { Slog.e(TAG, "Error sending result: " + e); } @@ -394,7 +393,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { @Override public void onError( - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) { + OnDeviceIntelligenceException exception) { try { callback.onFailure(exception.getErrorCode(), exception.getMessage(), exception.getErrorParams()); @@ -404,8 +403,8 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { } @Override - public void onDataAugmentRequest(@NonNull Content content, - @NonNull Consumer<Content> contentCallback) { + public void onDataAugmentRequest(@NonNull Bundle content, + @NonNull Consumer<Bundle> contentCallback) { try { callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback)); @@ -416,22 +415,22 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { }; } - private StreamedProcessingOutcomeReceiver wrapStreamingResponseCallback( + private StreamingProcessingCallback wrapStreamingResponseCallback( IStreamingResponseCallback callback) { - return new StreamedProcessingOutcomeReceiver() { + return new StreamingProcessingCallback() { @Override - public void onNewContent(@androidx.annotation.NonNull Content content) { + public void onPartialResult(@androidx.annotation.NonNull Bundle partialResult) { try { - callback.onNewContent(content); + callback.onNewContent(partialResult); } catch (RemoteException e) { Slog.e(TAG, "Error sending result: " + e); } } @Override - public void onResult(@androidx.annotation.NonNull Content response) { + public void onResult(@androidx.annotation.NonNull Bundle result) { try { - callback.onSuccess(response); + callback.onSuccess(result); } catch (RemoteException e) { Slog.e(TAG, "Error sending result: " + e); } @@ -439,7 +438,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { @Override public void onError( - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) { + OnDeviceIntelligenceException exception) { try { callback.onFailure(exception.getErrorCode(), exception.getMessage(), exception.getErrorParams()); @@ -449,8 +448,8 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { } @Override - public void onDataAugmentRequest(@NonNull Content content, - @NonNull Consumer<Content> contentCallback) { + public void onDataAugmentRequest(@NonNull Bundle content, + @NonNull Consumer<Bundle> contentCallback) { try { callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback)); @@ -462,13 +461,13 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { } private RemoteCallback wrapRemoteCallback( - @androidx.annotation.NonNull Consumer<Content> contentCallback) { + @androidx.annotation.NonNull Consumer<Bundle> contentCallback) { return new RemoteCallback( result -> { if (result != null) { getCallbackExecutor().execute(() -> contentCallback.accept( result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, - Content.class))); + Bundle.class))); } else { getCallbackExecutor().execute( () -> contentCallback.accept(null)); @@ -476,8 +475,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { }); } - private OutcomeReceiver<TokenInfo, - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback( + private OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> wrapTokenInfoCallback( ITokenInfoCallback tokenInfoCallback) { return new OutcomeReceiver<>() { @Override @@ -491,7 +489,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { @Override public void onError( - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) { + OnDeviceIntelligenceException exception) { try { tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(), exception.getErrorParams()); @@ -503,7 +501,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { } @NonNull - private static OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> wrapOutcomeReceiver( + private static OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> wrapOutcomeReceiver( IProcessingUpdateStatusCallback callback) { return new OutcomeReceiver<>() { @Override @@ -518,7 +516,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { @Override public void onError( - @androidx.annotation.NonNull OnDeviceUpdateProcessingException error) { + @androidx.annotation.NonNull OnDeviceIntelligenceException error) { try { callback.onFailure(error.getErrorCode(), error.getMessage()); } catch (RemoteException e) { diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index a08264e625df..ccc17ecccbf9 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -819,19 +819,15 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { /** * Called when the keyphrase is spoken. * - * <p>This implicitly stops listening for the keyphrase once it's detected. Clients should - * start a recognition again once they are done handling this detection. + * <p>If {@code eventPayload.isRecognitionStopped()} returns true, this implicitly stops + * listening for the keyphrase once it's detected. Clients should start a recognition again + * once they are done handling this detection. * * @param eventPayload Payload data for the detection event. This may contain the trigger * audio, if requested when calling {@link - * AlwaysOnHotwordDetector#startRecognition(int)}. + * AlwaysOnHotwordDetector#startRecognition(int)} or if the audio comes from the {@link + * android.service.wearable.WearableSensingService}. */ - // TODO(b/324635656): Update Javadoc for 24Q3 release: - // 1. Prepend to the first paragraph: - // If {@code eventPayload.isRecognitionStopped()} returns true, this... - // 2. Append to the description for @param eventPayload: - // ...or if the audio comes from {@link - // android.service.wearable.WearableSensingService}. public abstract void onDetected(@NonNull EventPayload eventPayload); /** diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java index 60e9de72f154..937aecc8d718 100644 --- a/core/java/android/service/voice/HotwordDetectionService.java +++ b/core/java/android/service/voice/HotwordDetectionService.java @@ -363,9 +363,11 @@ public abstract class HotwordDetectionService extends Service * {@link HotwordDetector#startRecognition(ParcelFileDescriptor, AudioFormat, * PersistableBundle)} run} hotword recognition on audio coming from an external connected * microphone. - * <p> - * Upon invoking the {@code callback}, the system closes {@code audioStream} and sends the - * detection result to the {@link HotwordDetector.Callback hotword detector}. + * + * <p>Upon invoking the {@code callback}, the system will send the detection result to + * the {@link HotwordDetector}'s callback. If {@code + * options.getBoolean(KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, true)} returns true, + * the system will also close the {@code audioStream} after {@code callback} is invoked. * * @param audioStream Stream containing audio bytes returned from a microphone * @param audioFormat Format of the supplied audio @@ -375,11 +377,6 @@ public abstract class HotwordDetectionService extends Service * PersistableBundle)}. * @param callback The callback to use for responding to the detection request. */ - // TODO(b/324635656): Update Javadoc for 24Q3 release. Change the last paragraph to: - // <p>Upon invoking the {@code callback}, the system will send the detection result to - // the {@link HotwordDetector}'s callback. If {@code - // options.getBoolean(KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, true)} returns true, - // the system will also close the {@code audioStream} after {@code callback} is invoked. public void onDetect( @NonNull ParcelFileDescriptor audioStream, @NonNull AudioFormat audioFormat, diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index f68fcab94952..aff1d4a4ee12 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -119,3 +119,10 @@ flag { is_fixed_read_only: true bug: "324676775" } + +flag { + name: "handwriting_cursor_position" + namespace: "text" + description: "When handwriting is initiated in an unfocused TextView, cursor is placed at the end of the closest paragraph." + bug: "323376217" +} diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index a6724da02bf2..a4c3ed96f2ce 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -24,6 +24,7 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_ import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME_DEFAULT; +import android.annotation.NonNull; import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.SigningDetails.SignatureSchemeVersion; @@ -33,9 +34,12 @@ import android.content.pm.parsing.result.ParseResult; import android.os.Build; import android.os.Trace; import android.os.incremental.V4Signature; +import android.util.ArrayMap; import android.util.Pair; +import android.util.Slog; import android.util.jar.StrictJarFile; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; @@ -63,8 +67,14 @@ import java.util.zip.ZipEntry; */ public class ApkSignatureVerifier { + private static final String LOG_TAG = "ApkSignatureVerifier"; + private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>(); + @GuardedBy("sOverrideSigningDetails") + private static final ArrayMap<SigningDetails, SigningDetails> sOverrideSigningDetails = + new ArrayMap<>(); + /** * Verifies the provided APK and returns the certificates associated with each signer. */ @@ -95,7 +105,54 @@ public class ApkSignatureVerifier { if (result.isError()) { return input.error(result); } - return input.success(result.getResult().signingDetails); + SigningDetails signingDetails = result.getResult().signingDetails; + if (Build.isDebuggable()) { + SigningDetails overrideSigningDetails; + synchronized (sOverrideSigningDetails) { + overrideSigningDetails = sOverrideSigningDetails.get(signingDetails); + } + if (overrideSigningDetails != null) { + Slog.i(LOG_TAG, "Applying override signing details for APK " + apkPath); + signingDetails = overrideSigningDetails; + } + } + return input.success(signingDetails); + } + + /** + * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will + * behave as if they are signed by the {@code newSigningDetails}. + * + * @param oldSigningDetails the original signing detail of the package + * @param newSigningDetails the new signing detail that will replace the original one + */ + public static void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails, + @NonNull SigningDetails newSigningDetails) { + synchronized (sOverrideSigningDetails) { + sOverrideSigningDetails.put(oldSigningDetails, newSigningDetails); + } + } + + /** + * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by + * the old signing details. + * + * @param oldSigningDetails the original signing detail of the package + * @throws SecurityException if the build is not debuggable + */ + public static void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails) { + synchronized (sOverrideSigningDetails) { + sOverrideSigningDetails.remove(oldSigningDetails); + } + } + + /** + * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}. + */ + public static void clearOverrideSigningDetails() { + synchronized (sOverrideSigningDetails) { + sOverrideSigningDetails.clear(); + } } /** diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 66655fca8fc3..29c83509dbf2 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -16,6 +16,8 @@ package android.view; +import static com.android.text.flags.Flags.handwritingCursorPosition; + import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -557,7 +559,8 @@ public class HandwritingInitiator { } private void requestFocusWithoutReveal(View view) { - if (view instanceof EditText editText && !mState.mStylusDownWithinEditorBounds) { + if (!handwritingCursorPosition() && view instanceof EditText editText + && !mState.mStylusDownWithinEditorBounds) { // If the stylus down point was inside the EditText's bounds, then the EditText will // automatically set its cursor position nearest to the stylus down point when it // gains focus. If the stylus down point was outside the EditText's bounds (within @@ -576,6 +579,17 @@ public class HandwritingInitiator { } else { view.requestFocus(); } + if (handwritingCursorPosition() && view instanceof EditText editText) { + // Move the cursor to the end of the paragraph closest to the stylus down point. + view.getLocationInWindow(mTempLocation); + int line = editText.getLineAtCoordinate(mState.mStylusDownY - mTempLocation[1]); + int paragraphEnd = TextUtils.indexOf(editText.getText(), '\n', + editText.getLayout().getLineStart(line)); + if (paragraphEnd < 0) { + paragraphEnd = editText.getText().length(); + } + editText.setSelection(paragraphEnd); + } } /** diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java index 1ca51ad8d65c..644a7a925f81 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -244,6 +244,17 @@ public class AutofillFeatureFlags { public static final String DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY = "ignore_view_state_reset_to_empty"; + /** + * Bugfix flag, Autofill should ignore view updates if an Auth intent is showing. + * + * See frameworks/base/services/autofill/bugfixes.aconfig#relayout + * for more information. + * + * @hide + */ + public static final String DEVICE_CONFIG_IGNORE_RELAYOUT_WHEN_AUTH_PENDING = + "ignore_relayout_auth_pending"; + // END AUTOFILL FOR ALL APPS FLAGS // @@ -513,6 +524,14 @@ public class AutofillFeatureFlags { false); } + /** @hide */ + public static boolean shouldIgnoreRelayoutWhenAuthPending() { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_AUTOFILL, + DEVICE_CONFIG_IGNORE_RELAYOUT_WHEN_AUTH_PENDING, + false); + } + /** * Whether should enable multi-line filter * diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 1484bfbb9df9..e15baaeef570 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -974,7 +974,7 @@ public final class AutofillManager { mShouldIncludeInvisibleViewInAssistStructure = AutofillFeatureFlags.shouldIncludeInvisibleViewInAssistStructure(); - mRelayoutFix = Flags.relayout(); + mRelayoutFix = AutofillFeatureFlags.shouldIgnoreRelayoutWhenAuthPending(); mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration(); } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 14c53489ba3a..d12eda35c745 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -1203,7 +1203,11 @@ public abstract class WebSettings { * changes to this setting after that point. * * @param flag {@code true} if the WebView should use the database storage API + * @deprecated WebSQL is deprecated and this method will become a no-op on all + * Android versions once support is removed in Chromium. See + * https://developer.chrome.com/blog/deprecating-web-sql for more information. */ + @Deprecated public abstract void setDatabaseEnabled(boolean flag); /** @@ -1236,7 +1240,11 @@ public abstract class WebSettings { * * @return {@code true} if the database storage API is enabled * @see #setDatabaseEnabled + * @deprecated WebSQL is deprecated and this method will become a no-op on all + * Android versions once support is removed in Chromium. See + * https://developer.chrome.com/blog/deprecating-web-sql for more information. */ + @Deprecated public abstract boolean getDatabaseEnabled(); /** diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 139ebc38706e..d40eedaea77b 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -2112,6 +2112,17 @@ public class Editor { } } + boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect() + && canvas.isHighContrastTextEnabled(); + + // If high contrast text is drawing background rectangles behind the text, those cover up + // the cursor and correction highlighter etc. So just draw the text first, then draw the + // others on top of the text. If high contrast text isn't enabled: draw text last, as usual. + if (shouldDrawHighlightsOnTop) { + drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight, + selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop); + } + if (mCorrectionHighlighter != null) { mCorrectionHighlighter.draw(canvas, cursorOffsetVertical); } @@ -2136,9 +2147,19 @@ public class Editor { mInsertModeController.onDraw(canvas); } + if (!shouldDrawHighlightsOnTop) { + drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight, + selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop); + } + } + + private void drawLayout(Canvas canvas, Layout layout, List<Path> highlightPaths, + List<Paint> highlightPaints, Path selectionHighlight, Paint selectionHighlightPaint, + int cursorOffsetVertical, boolean shouldDrawHighlightsOnTop) { if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) { drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints, - selectionHighlight, selectionHighlightPaint, cursorOffsetVertical); + selectionHighlight, selectionHighlightPaint, cursorOffsetVertical, + shouldDrawHighlightsOnTop); } else { layout.draw(canvas, highlightPaths, highlightPaints, selectionHighlight, selectionHighlightPaint, cursorOffsetVertical); @@ -2147,14 +2168,13 @@ public class Editor { private void drawHardwareAccelerated(Canvas canvas, Layout layout, List<Path> highlightPaths, List<Paint> highlightPaints, - Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical) { + Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical, + boolean shouldDrawHighlightsOnTop) { final long lineRange = layout.getLineRangeForDraw(canvas); int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); if (lastLine < 0) return; - boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect() - && canvas.isHighContrastTextEnabled(); if (!shouldDrawHighlightsOnTop) { layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight, diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index a2d8d80096c5..e3caf709cfe8 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -5127,7 +5127,10 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the {@link AdapterView} * @param intent The intent of the service which will be * providing data to the RemoteViewsAdapter + * @deprecated use + * {@link #setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} instead */ + @Deprecated public void setRemoteAdapter(@IdRes int viewId, Intent intent) { if (remoteAdapterConversion()) { addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent)); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 172146215257..0373539c44ea 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -15490,8 +15490,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return x; } + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - int getLineAtCoordinate(float y) { + public int getLineAtCoordinate(float y) { y -= getTotalPaddingTop(); // Clamp the position to inside of the view. y = Math.max(0.0f, y); diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index 7b8cdff8e20b..7e77f150b63b 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.view.SurfaceControl; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -111,7 +112,8 @@ public final class TaskFragmentOperation implements Parcelable { /** * Creates a decor surface in the parent Task of the TaskFragment. The created decor surface * will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED} - * event callback. + * event callback. The decor surface can be used to draw the divider between TaskFragments or + * other decorations. */ public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14; @@ -135,6 +137,15 @@ public final class TaskFragmentOperation implements Parcelable { */ public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17; + /** + * Sets whether the decor surface will be boosted. When not boosted, the decor surface is placed + * below any TaskFragments in untrusted mode or any activities with uid different from the + * TaskFragmentOrganizer uid and just above its owner TaskFragment; when boosted, the decor + * surface is placed above all the non-boosted windows in the Task, the content of these + * non-boosted windows will be hidden and inputs are disabled. + */ + public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18; + @IntDef(prefix = { "OP_TYPE_" }, value = { OP_TYPE_UNKNOWN, OP_TYPE_CREATE_TASK_FRAGMENT, @@ -155,6 +166,7 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE, OP_TYPE_SET_DIM_ON_TASK, OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH, + OP_TYPE_SET_DECOR_SURFACE_BOOSTED, }) @Retention(RetentionPolicy.SOURCE) public @interface OperationType {} @@ -186,12 +198,18 @@ public final class TaskFragmentOperation implements Parcelable { private final boolean mMoveToBottomIfClearWhenLaunch; + private final boolean mBooleanValue; + + @Nullable + private final SurfaceControl.Transaction mSurfaceTransaction; + private TaskFragmentOperation(@OperationType int opType, @Nullable TaskFragmentCreationParams taskFragmentCreationParams, @Nullable IBinder activityToken, @Nullable Intent activityIntent, @Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken, @Nullable TaskFragmentAnimationParams animationParams, - boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) { + boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch, + boolean booleanValue, @Nullable SurfaceControl.Transaction surfaceTransaction) { mOpType = opType; mTaskFragmentCreationParams = taskFragmentCreationParams; mActivityToken = activityToken; @@ -202,6 +220,8 @@ public final class TaskFragmentOperation implements Parcelable { mIsolatedNav = isolatedNav; mDimOnTask = dimOnTask; mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch; + mBooleanValue = booleanValue; + mSurfaceTransaction = surfaceTransaction; } private TaskFragmentOperation(Parcel in) { @@ -215,6 +235,8 @@ public final class TaskFragmentOperation implements Parcelable { mIsolatedNav = in.readBoolean(); mDimOnTask = in.readBoolean(); mMoveToBottomIfClearWhenLaunch = in.readBoolean(); + mBooleanValue = in.readBoolean(); + mSurfaceTransaction = in.readTypedObject(SurfaceControl.Transaction.CREATOR); } @Override @@ -229,6 +251,8 @@ public final class TaskFragmentOperation implements Parcelable { dest.writeBoolean(mIsolatedNav); dest.writeBoolean(mDimOnTask); dest.writeBoolean(mMoveToBottomIfClearWhenLaunch); + dest.writeBoolean(mBooleanValue); + dest.writeTypedObject(mSurfaceTransaction, flags); } @NonNull @@ -324,6 +348,22 @@ public final class TaskFragmentOperation implements Parcelable { return mMoveToBottomIfClearWhenLaunch; } + /** Returns the boolean value for this operation. */ + public boolean getBooleanValue() { + return mBooleanValue; + } + + /** + * Returns {@link SurfaceControl.Transaction} associated with this operation. Currently, this is + * only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to specify a + * {@link SurfaceControl.Transaction} that should be applied together with the transaction to + * change the decor surface layers. + */ + @Nullable + public SurfaceControl.Transaction getSurfaceTransaction() { + return mSurfaceTransaction; + } + @Override public String toString() { final StringBuilder sb = new StringBuilder(); @@ -349,6 +389,10 @@ public final class TaskFragmentOperation implements Parcelable { sb.append(", isolatedNav=").append(mIsolatedNav); sb.append(", dimOnTask=").append(mDimOnTask); sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch); + sb.append(", booleanValue=").append(mBooleanValue); + if (mSurfaceTransaction != null) { + sb.append(", surfaceTransaction=").append(mSurfaceTransaction); + } sb.append('}'); return sb.toString(); @@ -358,7 +402,7 @@ public final class TaskFragmentOperation implements Parcelable { public int hashCode() { return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask, - mMoveToBottomIfClearWhenLaunch); + mMoveToBottomIfClearWhenLaunch, mBooleanValue, mSurfaceTransaction); } @Override @@ -376,7 +420,9 @@ public final class TaskFragmentOperation implements Parcelable { && Objects.equals(mAnimationParams, other.mAnimationParams) && mIsolatedNav == other.mIsolatedNav && mDimOnTask == other.mDimOnTask - && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch; + && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch + && mBooleanValue == other.mBooleanValue + && Objects.equals(mSurfaceTransaction, other.mSurfaceTransaction); } @Override @@ -414,6 +460,11 @@ public final class TaskFragmentOperation implements Parcelable { private boolean mMoveToBottomIfClearWhenLaunch; + private boolean mBooleanValue; + + @Nullable + private SurfaceControl.Transaction mSurfaceTransaction; + /** * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}. */ @@ -505,13 +556,37 @@ public final class TaskFragmentOperation implements Parcelable { } /** + * Sets the boolean value for this operation. + * TODO(b/327338038) migrate other boolean values to use shared mBooleanValue + */ + @NonNull + public Builder setBooleanValue(boolean booleanValue) { + mBooleanValue = booleanValue; + return this; + } + + /** + * Sets {@link SurfaceControl.Transaction} associated with this operation. Currently, this + * is only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to + * specify a {@link SurfaceControl.Transaction} that should be applied together with the + * transaction to change the decor surface layers. + */ + @NonNull + public Builder setSurfaceTransaction( + @Nullable SurfaceControl.Transaction surfaceTransaction) { + mSurfaceTransaction = surfaceTransaction; + return this; + } + + /** * Constructs the {@link TaskFragmentOperation}. */ @NonNull public TaskFragmentOperation build() { return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams, - mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch); + mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch, mBooleanValue, + mSurfaceTransaction); } } } diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java index 2c493031ea8a..2db3e658530f 100644 --- a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java +++ b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java @@ -16,6 +16,8 @@ package com.android.internal.accessibility.common; +import android.os.SystemProperties; + /** * Collection of common constants for accessibility magnification. */ @@ -31,6 +33,7 @@ public final class MagnificationConstants { /** Minimum supported value for magnification scale. */ public static final float SCALE_MIN_VALUE = 1.0f; - /** Maximum supported value for magnification scale. */ - public static final float SCALE_MAX_VALUE = 8.0f; + /** Maximum supported value for magnification scale. Default of 8.0. */ + public static final float SCALE_MAX_VALUE = + Float.parseFloat(SystemProperties.get("ro.config.max_magnification_scale", "8.0")); } diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml index 6e804c0b428a..9ad577ad8bf6 100644 --- a/core/res/res/values-watch/themes_device_defaults.xml +++ b/core/res/res/values-watch/themes_device_defaults.xml @@ -181,19 +181,95 @@ a similar way. <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert"> <item name="android:windowFullscreen">true</item> - <!-- Color palette Dark --> + <!-- Dialog attributes --> + <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> + <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + <!-- Color palette --> <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> - <item name="colorForeground">@color/foreground_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + <item name="colorAccentPrimary">@color/accent_primary_device_default</item> + <item name="colorAccentSecondary">@color/accent_secondary_device_default</item> + <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item> + <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item> + <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item> + <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item> + <item name="colorSurface">@color/surface_dark</item> + <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item> + <item name="colorSurfaceVariant">@color/surface_variant_dark</item> + <item name="colorSurfaceHeader">@color/surface_header_dark</item> + <item name="colorError">@color/error_color_device_default_dark</item> <item name="colorBackground">@color/background_device_default_dark</item> <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item> - <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item> - <item name="colorButtonNormal">@color/button_normal_device_default_dark</item> - <item name="colorError">@color/error_color_device_default_dark</item> - <item name="disabledAlpha">@dimen/disabled_alpha_device_default</item> - <item name="primaryContentAlpha">@dimen/primary_content_alpha_device_default</item> - <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item> + <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> + <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> + <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item> + <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item> + <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item> + <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> + + <!-- Progress bar attributes --> + <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item> + <item name="progressBarCornerRadius">@dimen/config_progressBarCornerRadius</item> + + <!-- Toolbar attributes --> + <item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item> + + <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item> + <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item> + <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item> + <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item> + <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item> + <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item> + <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item> + <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item> + <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item> + <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item> + <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item> + <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item> + <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item> + <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item> + <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item> + <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item> + <item name="materialColorErrorContainer">@color/system_error_container_dark</item> + <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item> + <item name="materialColorPrimaryInverse">@color/system_primary_light</item> + <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item> + <item name="materialColorSurfaceInverse">@color/system_surface_light</item> + <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item> + <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item> + <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item> + <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item> + <item name="materialColorOnBackground">@color/system_on_background_dark</item> + <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item> + <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item> + <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item> + <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item> + <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item> + <item name="materialColorOnError">@color/system_on_error_dark</item> + <item name="materialColorSurface">@color/system_surface_dark</item> + <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item> + <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item> + <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item> + <item name="materialColorOutline">@color/system_outline_dark</item> + <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item> + <item name="materialColorOnPrimary">@color/system_on_primary_dark</item> + <item name="materialColorOnSurface">@color/system_on_surface_dark</item> + <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item> + <item name="materialColorPrimary">@color/system_primary_dark</item> + <item name="materialColorSecondary">@color/system_secondary_dark</item> + <item name="materialColorTertiary">@color/system_tertiary_dark</item> + <item name="materialColorError">@color/system_error_dark</item> </style> <!-- DeviceDefault theme for a window that should look like the Settings app. --> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index d89f23614179..5e900f773a65 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -3287,10 +3287,9 @@ --> <attr name="enableOnBackInvokedCallback" format="boolean"/> - <!-- Specifies permissions necessary to launch this activity via - {@link android.content.Context#startActivity} when passing content URIs. The default - value is {@code none}, meaning no specific permissions are required. Setting this - attribute restricts activity invocation based on the invoker's permissions. If the + <!-- Specifies permissions necessary to launch this activity when passing content URIs. The + default value is {@code none}, meaning no specific permissions are required. Setting + this attribute restricts activity invocation based on the invoker's permissions. If the invoker doesn't have the required permissions, the activity start will be denied via a {@link java.lang.SecurityException}. diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index b60b806f3444..a5c962412024 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -23,6 +23,8 @@ import static android.view.MotionEvent.ACTION_UP; import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import static android.view.stylus.HandwritingTestUtil.createView; +import static com.android.text.flags.Flags.handwritingCursorPosition; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assume.assumeFalse; @@ -129,6 +131,7 @@ public class HandwritingInitiatorTest { public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() { mTestView1.setText("hello"); when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); + when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0); mHandwritingInitiator.onInputConnectionCreated(mTestView1); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; @@ -148,9 +151,51 @@ public class HandwritingInitiatorTest { // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE // events so that the events are not dispatched to the view tree. assertThat(onTouchEventResult2).isTrue(); - // Since the stylus down point was inside the TextView's bounds, the handwriting initiator - // does not need to set the cursor position. - verify(mTestView1, never()).setSelection(anyInt()); + if (handwritingCursorPosition()) { + // Cursor is placed at the end of the text. + verify(mTestView1).setSelection(5); + } else { + // Since the stylus down point was inside the TextView's bounds, the handwriting + // initiator does not need to set the cursor position. + verify(mTestView1, never()).setSelection(anyInt()); + } + } + + @Test + public void onTouchEvent_startHandwriting_multipleParagraphs() { + // End of line 0 is offset 10, end of line 1 is offset 20, end of line 2 is offset 30, end + // of line 3 is offset 40. + mTestView1.setText("line 0 \nline 1 \nline 2 \nline 3 "); + mTestView1.layout(0, 0, 500, 500); + when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); + when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(2); + + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + final int x1 = (sHwArea1.left + sHwArea1.right) / 2; + final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + mHandwritingSlop * 2; + final int y2 = y1; + + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2); + + // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once. + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); + assertThat(onTouchEventResult1).isFalse(); + // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE + // events so that the events are not dispatched to the view tree. + assertThat(onTouchEventResult2).isTrue(); + if (handwritingCursorPosition()) { + // Cursor is placed at the end of the paragraph containing line 2. + verify(mTestView1).setSelection(30); + } else { + // Since the stylus down point was inside the TextView's bounds, the handwriting + // initiator does not need to set the cursor position. + verify(mTestView1, never()).setSelection(anyInt()); + } } @Test @@ -197,6 +242,7 @@ public class HandwritingInitiatorTest { public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() { mTestView1.setText("hello"); when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); + when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0); if (!mInitiateWithoutConnection) { mHandwritingInitiator.onInputConnectionCreated(mTestView1); @@ -214,9 +260,14 @@ public class HandwritingInitiatorTest { // Stylus movement within extended HandwritingArea should trigger IMM.startHandwriting once. verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); - // Since the stylus down point was outside the TextView's bounds, the handwriting initiator - // sets the cursor position. - verify(mTestView1).setSelection(4); + if (handwritingCursorPosition()) { + // Cursor is placed at the end of the text. + verify(mTestView1).setSelection(5); + } else { + // Since the stylus down point was outside the TextView's bounds, the handwriting + // initiator sets the cursor position. + verify(mTestView1).setSelection(4); + } } @Test @@ -246,6 +297,8 @@ public class HandwritingInitiatorTest { public void onTouchEvent_startHandwriting_servedViewUpdate_stylusMoveInExtendedHWArea() { mTestView1.setText("hello"); when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); + when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0); + // The stylus down point is between mTestView1 and mTestView2, but it is within the // extended handwriting area of both views. It is closer to mTestView1. final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2; @@ -278,9 +331,14 @@ public class HandwritingInitiatorTest { // Handwriting is started for this view since the stylus down point is closest to this // view. verify(mHandwritingInitiator).startHandwriting(mTestView1); - // Since the stylus down point was outside the TextView's bounds, the handwriting initiator - // sets the cursor position. - verify(mTestView1).setSelection(4); + if (handwritingCursorPosition()) { + // Cursor is placed at the end of the text. + verify(mTestView1).setSelection(5); + } else { + // Since the stylus down point was outside the TextView's bounds, the handwriting + // initiator sets the cursor position. + verify(mTestView1).setSelection(4); + } } diff --git a/data/etc/OWNERS b/data/etc/OWNERS index 245f21658b7a..701d145fe805 100644 --- a/data/etc/OWNERS +++ b/data/etc/OWNERS @@ -12,3 +12,4 @@ yamasani@google.com per-file preinstalled-packages* = file:/MULTIUSER_OWNERS per-file services.core.protolog.json = file:/services/core/java/com/android/server/wm/OWNERS +per-file core.protolog.pb = file:/services/core/java/com/android/server/wm/OWNERS diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 0f12438613cf..749f0e12aa03 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -553,6 +553,8 @@ applications that come with the platform <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/> <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases --> <permission name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE"/> + <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest --> + <permission name="android.permission.USE_ON_DEVICE_INTELLIGENCE" /> <!-- Permission required for CTS test - CtsTelephonyProviderTestCases --> <permission name="android.permission.WRITE_APN_SETTINGS"/> <!-- Permission required for GTS test - GtsStatsdHostTestCases --> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 9585842a2014..4455a3caa7d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1259,12 +1259,14 @@ public class BubbleController implements ConfigurationChangeListener, * Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble * exists for this entry, and it is able to bubble, a new bubble will be created. * - * This is the method to use when opening a bubble via a notification or in a state where + * <p>This is the method to use when opening a bubble via a notification or in a state where * the device might not be unlocked. * * @param entry the entry to use for the bubble. */ public void expandStackAndSelectBubble(BubbleEntry entry) { + ProtoLog.d(WM_SHELL_BUBBLES, "opening bubble from notification key=%s mIsStatusBarShade=%b", + entry.getKey(), mIsStatusBarShade); if (mIsStatusBarShade) { mNotifEntryToExpandOnShadeUnlock = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 23bdd08e6b24..6524c96fb21a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -449,17 +449,21 @@ public class BubbleStackView extends FrameLayout @Override public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target, - @NonNull MagnetizedObject draggedObject) { - if (draggedObject.getUnderlyingObject() instanceof View view) { + @NonNull MagnetizedObject<?> draggedObject) { + Object underlyingObject = draggedObject.getUnderlyingObject(); + if (underlyingObject instanceof View) { + View view = (View) underlyingObject; animateDismissBubble(view, true); } } @Override public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, - @NonNull MagnetizedObject draggedObject, + @NonNull MagnetizedObject<?> draggedObject, float velX, float velY, boolean wasFlungOut) { - if (draggedObject.getUnderlyingObject() instanceof View view) { + Object underlyingObject = draggedObject.getUnderlyingObject(); + if (underlyingObject instanceof View) { + View view = (View) underlyingObject; animateDismissBubble(view, false); if (wasFlungOut) { @@ -474,7 +478,9 @@ public class BubbleStackView extends FrameLayout @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target, @NonNull MagnetizedObject<?> draggedObject) { - if (draggedObject.getUnderlyingObject() instanceof View view) { + Object underlyingObject = draggedObject.getUnderlyingObject(); + if (underlyingObject instanceof View) { + View view = (View) underlyingObject; mExpandedAnimationController.dismissDraggedOutBubble( view /* bubble */, mDismissView.getHeight() /* translationYBy */, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index fb0ed1587055..6a3c8d2f599a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -138,9 +138,9 @@ public class DesktopModeVisualIndicator { @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) { final Region region = new Region(); int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM - ? 2 * layout.stableInsets().top - : mContext.getResources().getDimensionPixelSize( - com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height); + ? mContext.getResources().getDimensionPixelSize( + com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height) + : 2 * layout.stableInsets().top; // A thin, short Rect at the top of the screen. if (windowingMode == WINDOWING_MODE_FREEFORM) { int fromFreeformWidth = mContext.getResources().getDimensionPixelSize( 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 dd8c1a0f2e02..4a3130f3c54b 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 @@ -78,6 +78,7 @@ import com.android.wm.shell.sysui.ShellSharedConstants import com.android.wm.shell.transition.OneShotRemoteHandler import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.KtProtoLog +import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener import java.io.PrintWriter @@ -960,7 +961,8 @@ class DesktopTasksController( } /** - * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area. + * Perform checks required on drag end. If indicator indicates a windowing mode change, perform + * that change. Otherwise, ensure bounds are up to date. * * @param taskInfo the task being dragged. * @param position position of surface when drag ends. @@ -971,7 +973,8 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, position: Point, inputCoordinate: PointF, - taskBounds: Rect + taskBounds: Rect, + validDragArea: Rect ) { if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) { return @@ -994,10 +997,21 @@ class DesktopTasksController( releaseVisualIndicator() snapToHalfScreen(taskInfo, SnapPosition.RIGHT) } - DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR, DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> { + // If task bounds are outside valid drag area, snap them inward and perform a + // transaction to set bounds. + if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary( + taskBounds, validDragArea)) { + val wct = WindowContainerTransaction() + wct.setBounds(taskInfo.token, taskBounds) + transitions.startTransition(TRANSIT_CHANGE, wct, null) + } releaseVisualIndicator() } + DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> { + throw IllegalArgumentException("Should not be receiving TO_DESKTOP_INDICATOR for " + + "a freeform task.") + } } // A freeform drag-move ended, remove the indicator immediately. releaseVisualIndicator() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 952e2d4b3b9a..86c8f04f8138 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -436,7 +436,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) { - mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason); + if (ENABLE_SHELL_TRANSITIONS) { + mStageCoordinator.dismissSplitScreen(toTopTaskId, exitReason); + } else { + mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java index 7f16c5e3592e..af11ebc515d7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java @@ -17,6 +17,7 @@ package com.android.wm.shell.splitscreen; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -45,6 +46,8 @@ public class SplitScreenShellCommandHandler implements return runSetSideStagePosition(args, pw); case "switchSplitPosition": return runSwitchSplitPosition(); + case "exitSplitScreen": + return runExitSplitScreen(args, pw); default: pw.println("Invalid command: " + args[0]); return false; @@ -91,6 +94,17 @@ public class SplitScreenShellCommandHandler implements return true; } + private boolean runExitSplitScreen(String[] args, PrintWriter pw) { + if (args.length < 2) { + // First argument is the action name. + pw.println("Error: task id should be provided as arguments"); + return false; + } + final int taskId = Integer.parseInt(args[1]); + mController.exitSplitScreen(taskId, EXIT_REASON_UNKNOWN); + return true; + } + @Override public void printShellCommandHelp(PrintWriter pw, String prefix) { pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>"); @@ -101,5 +115,7 @@ public class SplitScreenShellCommandHandler implements pw.println(prefix + " Sets the position of the side-stage."); pw.println(prefix + "switchSplitPosition"); pw.println(prefix + " Reverses the split."); + pw.println(prefix + "exitSplitScreen <taskId>"); + pw.println(prefix + " Exits split screen and leaves the provided split task on top."); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index ec907fd9bd12..36368df9af36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1451,6 +1451,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mExitSplitScreenOnHide = exitSplitScreenOnHide; } + /** Exits split screen with legacy transition */ void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b", toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive()); @@ -1470,6 +1471,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, applyExitSplitScreen(childrenToTop, wct, exitReason); } + /** Exits split screen with legacy transition */ private void exitSplitScreen(@Nullable StageTaskListener childrenToTop, @ExitReason int exitReason) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b", @@ -1547,6 +1549,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + void dismissSplitScreen(int toTopTaskId, @ExitReason int exitReason) { + if (!mMainStage.isActive()) return; + final int stage = getStageOfTask(toTopTaskId); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareExitSplitScreen(stage, wct); + mSplitTransitions.startDismissTransition(wct, this, stage, exitReason); + } + /** * Overridden by child classes. */ @@ -1612,6 +1622,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // User has used a keyboard shortcut to go back to fullscreen from split case EXIT_REASON_DESKTOP_MODE: // One of the children enters desktop mode + case EXIT_REASON_UNKNOWN: + // Unknown reason return true; default: return false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index b2eeea7048bc..c59a1b452303 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -19,9 +19,11 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_CHANGE; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; +import android.graphics.Rect; import android.os.Handler; import android.util.SparseArray; import android.view.Choreographer; @@ -186,7 +188,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { final FluidResizeTaskPositioner taskPositioner = new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration, - mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */); + mDisplayController); final CaptionTouchEventListener touchEventListener = new CaptionTouchEventListener(taskInfo, taskPositioner); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); @@ -286,8 +288,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mDragPointerId = e.getPointerId(0); } final int dragPointerIdx = e.findPointerIndex(mDragPointerId); - mDragPositioningCallback.onDragPositioningEnd( + final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); + DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(newTaskBounds, + mWindowDecorByTaskId.get(mTaskId).calculateValidDragArea()); + if (newTaskBounds != taskInfo.configuration.windowConfiguration.getBounds()) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setBounds(taskInfo.token, newTaskBounds); + mTransitions.startTransition(TRANSIT_CHANGE, wct, null); + } final boolean wasDragging = mIsDragging; mIsDragging = false; return wasDragging; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 91e9601c6a27..9a48922fd238 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -16,6 +16,7 @@ package com.android.wm.shell.windowdecor; +import android.annotation.NonNull; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; @@ -87,6 +88,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL } @Override + @NonNull Rect calculateValidDragArea() { final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(), R.dimen.caption_left_buttons_width); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 98ff0eed9c11..918cefbee9f4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -639,7 +639,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mDesktopTasksController.onDragPositioningEnd(taskInfo, position, new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)), - newTaskBounds); + newTaskBounds, decoration.calculateValidDragArea()); if (touchingButton && !mHasLongClicked) { // We need the input event to not be consumed here to end the ripple // effect on the touched button. We will reset drag state in the ensuing @@ -867,8 +867,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } if (mTransitionDragActive) { // Do not create an indicator at all if we're not past transition height. - if (ev.getRawY() < mContext.getResources().getDimensionPixelSize(com.android - .wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height) + DisplayLayout layout = mDisplayController + .getDisplayLayout(relevantDecor.mTaskInfo.displayId); + if (ev.getRawY() < 2 * layout.stableInsets().top && mMoveToDesktopAnimator == null) { return; } @@ -1086,18 +1087,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { windowDecoration.createResizeVeil(); final DragPositioningCallback dragPositioningCallback; - final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize( - R.dimen.desktop_mode_fullscreen_from_desktop_height); if (!DesktopModeStatus.isVeiledResizeEnabled()) { dragPositioningCallback = new FluidResizeTaskPositioner( mTaskOrganizer, mTransitions, windowDecoration, mDisplayController, - mDragStartListener, mTransactionFactory, transitionAreaHeight); + mDragStartListener, mTransactionFactory); windowDecoration.setTaskDragResizer( (FluidResizeTaskPositioner) dragPositioningCallback); } else { dragPositioningCallback = new VeiledResizeTaskPositioner( mTaskOrganizer, windowDecoration, mDisplayController, - mDragStartListener, mTransitions, transitionAreaHeight); + mDragStartListener, mTransitions); windowDecoration.setTaskDragResizer( (VeiledResizeTaskPositioner) dragPositioningCallback); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index c9669a788994..4c9e17155625 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.windowingModeToString; import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; import android.content.Context; @@ -499,6 +500,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Determine valid drag area for this task based on elements in the app chip. */ @Override + @NonNull Rect calculateValidDragArea() { final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder) mWindowDecorViewHolder).getAppNameTextWidth(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java index 5afbd54088d1..82c399ad8152 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java @@ -131,7 +131,7 @@ public class DragPositioningCallbackUtility { t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top); } - private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, + static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, PointF repositionStartPoint, float x, float y) { final float deltaX = x - repositionStartPoint.x; final float deltaY = y - repositionStartPoint.y; @@ -140,49 +140,32 @@ public class DragPositioningCallbackUtility { } /** - * Calculates the new position of the top edge of the task and returns true if it is below the - * disallowed area. - * - * @param disallowedAreaForEndBoundsHeight the height of the area that where the task positioner - * should not finalize the bounds using WCT#setBounds - * @param taskBoundsAtDragStart the bounds of the task on the first drag input event - * @param repositionStartPoint initial input coordinate - * @param y the y position of the motion event - * @return true if the top of the task is below the disallowed area + * If task bounds are outside of provided drag area, snap the bounds to be just inside the + * drag area. + * @param repositionTaskBounds bounds determined by task positioner + * @param validDragArea the area that task must be positioned inside + * @return whether bounds were modified */ - static boolean isBelowDisallowedArea(int disallowedAreaForEndBoundsHeight, - Rect taskBoundsAtDragStart, PointF repositionStartPoint, float y) { - final float deltaY = y - repositionStartPoint.y; - final float topPosition = taskBoundsAtDragStart.top + deltaY; - return topPosition > disallowedAreaForEndBoundsHeight; - } - - /** - * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If - * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the - * valid drag area. - */ - static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, - PointF repositionStartPoint, float x, float y, Rect validDragArea) { - updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint, - x, y); - snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea); - } - - private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) { + public static boolean snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) { // If we were never supplied a valid drag area, do not restrict movement. // Otherwise, we restrict deltas to keep task position inside the Rect. - if (validDragArea.width() == 0) return; + if (validDragArea.width() == 0) return false; + boolean result = false; if (repositionTaskBounds.left < validDragArea.left) { repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0); + result = true; } else if (repositionTaskBounds.left > validDragArea.right) { repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0); + result = true; } if (repositionTaskBounds.top < validDragArea.top) { repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top); + result = true; } else if (repositionTaskBounds.top > validDragArea.bottom) { repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top); + result = true; } + return result; } private static float getMinWidth(DisplayController displayController, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java index 6bfc7cdcb33e..6f8b3d5aaaad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java @@ -60,9 +60,6 @@ class FluidResizeTaskPositioner implements DragPositioningCallback, private final Rect mTaskBoundsAtDragStart = new Rect(); private final PointF mRepositionStartPoint = new PointF(); private final Rect mRepositionTaskBounds = new Rect(); - // If a task move (not resize) finishes with the positions y less than this value, do not - // finalize the bounds there using WCT#setBounds - private final int mDisallowedAreaForEndBoundsHeight; private boolean mHasDragResized; private boolean mIsResizingOrAnimatingResize; private int mCtrlType; @@ -70,11 +67,9 @@ class FluidResizeTaskPositioner implements DragPositioningCallback, @Surface.Rotation private int mRotation; FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, Transitions transitions, - WindowDecoration windowDecoration, DisplayController displayController, - int disallowedAreaForEndBoundsHeight) { + WindowDecoration windowDecoration, DisplayController displayController) { this(taskOrganizer, transitions, windowDecoration, displayController, - dragStartListener -> {}, SurfaceControl.Transaction::new, - disallowedAreaForEndBoundsHeight); + dragStartListener -> {}, SurfaceControl.Transaction::new); } FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, @@ -82,15 +77,13 @@ class FluidResizeTaskPositioner implements DragPositioningCallback, WindowDecoration windowDecoration, DisplayController displayController, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Supplier<SurfaceControl.Transaction> supplier, - int disallowedAreaForEndBoundsHeight) { + Supplier<SurfaceControl.Transaction> supplier) { mTaskOrganizer = taskOrganizer; mTransitions = transitions; mWindowDecoration = windowDecoration; mDisplayController = displayController; mDragStartListener = dragStartListener; mTransactionSupplier = supplier; - mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight; } @Override @@ -157,14 +150,10 @@ class FluidResizeTaskPositioner implements DragPositioningCallback, wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds); } mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this); - } else if (mCtrlType == CTRL_TYPE_UNDEFINED - && DragPositioningCallbackUtility.isBelowDisallowedArea( - mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint, - y)) { + } else if (mCtrlType == CTRL_TYPE_UNDEFINED) { final WindowContainerTransaction wct = new WindowContainerTransaction(); - DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds, - mTaskBoundsAtDragStart, mRepositionStartPoint, x, y, - mWindowDecoration.calculateValidDragArea()); + DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds, + mTaskBoundsAtDragStart, mRepositionStartPoint, x, y); wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds); mTransitions.startTransition(TRANSIT_CHANGE, wct, this); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index 5c69d5542227..c12a93edcaf3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -54,9 +54,6 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, private final Rect mTaskBoundsAtDragStart = new Rect(); private final PointF mRepositionStartPoint = new PointF(); private final Rect mRepositionTaskBounds = new Rect(); - // If a task move (not resize) finishes with the positions y less than this value, do not - // finalize the bounds there using WCT#setBounds - private final int mDisallowedAreaForEndBoundsHeight; private final Supplier<SurfaceControl.Transaction> mTransactionSupplier; private int mCtrlType; private boolean mIsResizingOrAnimatingResize; @@ -66,25 +63,22 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, DesktopModeWindowDecoration windowDecoration, DisplayController displayController, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Transitions transitions, - int disallowedAreaForEndBoundsHeight) { + Transitions transitions) { this(taskOrganizer, windowDecoration, displayController, dragStartListener, - SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight); + SurfaceControl.Transaction::new, transitions); } public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, DesktopModeWindowDecoration windowDecoration, DisplayController displayController, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Supplier<SurfaceControl.Transaction> supplier, Transitions transitions, - int disallowedAreaForEndBoundsHeight) { + Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) { mDesktopWindowDecoration = windowDecoration; mTaskOrganizer = taskOrganizer; mDisplayController = displayController; mDragStartListener = dragStartListener; mTransactionSupplier = supplier; mTransitions = transitions; - mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight; } @Override @@ -151,13 +145,10 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, // won't be called. resetVeilIfVisible(); } - } else if (DragPositioningCallbackUtility.isBelowDisallowedArea( - mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint, - y)) { + } else { final WindowContainerTransaction wct = new WindowContainerTransaction(); - DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds, - mTaskBoundsAtDragStart, mRepositionStartPoint, x, y, - mDesktopWindowDecoration.calculateValidDragArea()); + DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds, + mTaskBoundsAtDragStart, mRepositionStartPoint, x, y); wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds); mTransitions.startTransition(TRANSIT_CHANGE, wct, this); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt index 9703dce8bf53..bd39aa6ace42 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt @@ -68,17 +68,17 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() { ) var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT) - assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight)) + assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top)) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT) assertThat(testRegion.bounds).isEqualTo(Rect( DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2, -50, DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2, - 2 * STABLE_INSETS.top)) + transitionHeight)) testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT) - assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight)) + assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top)) } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt index e60be7186b1e..e6fabcfec58a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt @@ -189,8 +189,9 @@ class DragPositioningCallbackUtilityTest { DISPLAY_BOUNDS.right - 100, DISPLAY_BOUNDS.bottom - 100) - DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS, - startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(), + DragPositioningCallbackUtility.updateTaskBounds(repositionTaskBounds, STARTING_BOUNDS, + startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat()) + DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea) assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left) assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt index de6903d9a06a..ce7b63322b4a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt @@ -125,8 +125,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { mockWindowDecoration, mockDisplayController, mockDragStartListener, - mockTransactionFactory, - DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT + mockTransactionFactory ) } @@ -576,31 +575,6 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { }) } - @Test - fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() { - taskPositioner.onDragPositioningStart( - CTRL_TYPE_UNDEFINED, // drag - STARTING_BOUNDS.right.toFloat(), - STARTING_BOUNDS.top.toFloat() - ) - - val newX = STARTING_BOUNDS.right.toFloat() + 5 - val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1 - taskPositioner.onDragPositioningMove( - newX, - newY - ) - - taskPositioner.onDragPositioningEnd(newX, newY) - - verify(mockTransitions, never()).startTransition( - eq(WindowManager.TRANSIT_CHANGE), argThat { wct -> - return@argThat wct.changes.any { (token, change) -> - token == taskBinder && - ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) - }}, eq(taskPositioner)) - } - private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean { return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) && bounds == configuration.windowConfiguration.bounds @@ -656,70 +630,6 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_drag_taskPositionedInStableBounds() { - taskPositioner.onDragPositioningStart( - CTRL_TYPE_UNDEFINED, // drag - STARTING_BOUNDS.left.toFloat(), - STARTING_BOUNDS.top.toFloat() - ) - - val newX = STARTING_BOUNDS.left.toFloat() - val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5 - taskPositioner.onDragPositioningMove( - newX, - newY - ) - verify(mockTransaction).setPosition(any(), eq(newX), eq(newY)) - - taskPositioner.onDragPositioningEnd( - newX, - newY - ) - // Verify task's top bound is set to stable bounds top since dragged outside stable bounds - // but not in disallowed end bounds area. - verify(mockTransitions).startTransition( - eq(WindowManager.TRANSIT_CHANGE), argThat { wct -> - return@argThat wct.changes.any { (token, change) -> - token == taskBinder && - (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && - change.configuration.windowConfiguration.bounds.top == - STABLE_BOUNDS_LANDSCAPE.top - }}, eq(taskPositioner)) - } - - @Test - fun testDragResize_drag_taskPositionedInValidDragArea() { - taskPositioner.onDragPositioningStart( - CTRL_TYPE_UNDEFINED, // drag - STARTING_BOUNDS.left.toFloat(), - STARTING_BOUNDS.top.toFloat() - ) - - val newX = VALID_DRAG_AREA.left - 500f - val newY = VALID_DRAG_AREA.bottom + 500f - taskPositioner.onDragPositioningMove( - newX, - newY - ) - verify(mockTransaction).setPosition(any(), eq(newX), eq(newY)) - - taskPositioner.onDragPositioningEnd( - newX, - newY - ) - verify(mockTransitions).startTransition( - eq(WindowManager.TRANSIT_CHANGE), argThat { wct -> - return@argThat wct.changes.any { (token, change) -> - token == taskBinder && - (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && - change.configuration.windowConfiguration.bounds.top == - VALID_DRAG_AREA.bottom && - change.configuration.windowConfiguration.bounds.left == - VALID_DRAG_AREA.left - }}, eq(taskPositioner)) - } - - @Test fun testDragResize_drag_updatesStableBoundsOnRotate() { // Test landscape stable bounds performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(), diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index 86253f35a51d..7f6e538f0bbf 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -138,8 +138,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { mockDisplayController, mockDragStartListener, mockTransactionFactory, - mockTransitions, - DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT + mockTransitions ) } @@ -355,68 +354,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_drag_taskPositionedInStableBounds() { - taskPositioner.onDragPositioningStart( - CTRL_TYPE_UNDEFINED, // drag - STARTING_BOUNDS.left.toFloat(), - STARTING_BOUNDS.top.toFloat() - ) - - val newX = STARTING_BOUNDS.left.toFloat() - val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5 - taskPositioner.onDragPositioningMove( - newX, - newY - ) - verify(mockTransaction).setPosition(any(), eq(newX), eq(newY)) - - taskPositioner.onDragPositioningEnd( - newX, - newY - ) - // Verify task's top bound is set to stable bounds top since dragged outside stable bounds - // but not in disallowed end bounds area. - verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct -> - return@argThat wct.changes.any { (token, change) -> - token == taskBinder && - (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && - change.configuration.windowConfiguration.bounds.top == - STABLE_BOUNDS_LANDSCAPE.top }}, - eq(taskPositioner)) - } - - @Test - fun testDragResize_drag_taskPositionedInValidDragArea() { - taskPositioner.onDragPositioningStart( - CTRL_TYPE_UNDEFINED, // drag - STARTING_BOUNDS.left.toFloat(), - STARTING_BOUNDS.top.toFloat() - ) - - val newX = VALID_DRAG_AREA.left - 500f - val newY = VALID_DRAG_AREA.bottom + 500f - taskPositioner.onDragPositioningMove( - newX, - newY - ) - verify(mockTransaction).setPosition(any(), eq(newX), eq(newY)) - - taskPositioner.onDragPositioningEnd( - newX, - newY - ) - verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct -> - return@argThat wct.changes.any { (token, change) -> - token == taskBinder && - (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && - change.configuration.windowConfiguration.bounds.top == - VALID_DRAG_AREA.bottom && - change.configuration.windowConfiguration.bounds.left == - VALID_DRAG_AREA.left }}, - eq(taskPositioner)) - } - - @Test fun testDragResize_drag_updatesStableBoundsOnRotate() { // Test landscape stable bounds performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(), diff --git a/packages/CrashRecovery/OWNERS b/packages/CrashRecovery/OWNERS index daa02111f71f..8337fd2453df 100644 --- a/packages/CrashRecovery/OWNERS +++ b/packages/CrashRecovery/OWNERS @@ -1,3 +1 @@ -ancr@google.com -harshitmahajan@google.com -robertogil@google.com +include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java index 0fcec268fe9c..d0fee44f791f 100644 --- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java @@ -707,7 +707,7 @@ public class RescueParty { if (pm.getModuleInfo(packageName, 0) != null) { return true; } - } catch (PackageManager.NameNotFoundException ignore) { + } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) { } return isPersistentSystemApp(packageName); diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt index 6aef24d846a9..9cfdffd55267 100644 --- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt +++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt @@ -18,34 +18,34 @@ package com.android.credentialmanager import android.os.Build import androidx.test.platform.app.InstrumentationRegistry -import platform.test.screenshot.GoldenImagePathManager +import platform.test.screenshot.GoldenPathManager import platform.test.screenshot.PathConfig /** The assets path to be used by all CredentialManager screenshot tests. */ private const val ASSETS_PREFIX = "frameworks/base/packages/CredentialManager" private const val ASSETS_PATH = "${ASSETS_PREFIX}/tests/robotests/screenshot/customization/assets" -private const val ASSETS_PATH_ROBO = - "${ASSETS_PREFIX}/tests/robotests/customization/assets" +private const val ASSETS_PATH_ROBO = "${ASSETS_PREFIX}/tests/robotests/customization/assets" private val isRobolectric = Build.FINGERPRINT.contains("robolectric") -class CredentialManagerGoldenImagePathManager( - pathConfig: PathConfig, - assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH -) : GoldenImagePathManager( +class CredentialManagerGoldenPathManager( + pathConfig: PathConfig, + assetsPathRelativeToBuildRoot: String = if (isRobolectric) ASSETS_PATH_ROBO else ASSETS_PATH +) : + GoldenPathManager( appContext = InstrumentationRegistry.getInstrumentation().context, assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot, deviceLocalPath = - InstrumentationRegistry.getInstrumentation() + InstrumentationRegistry.getInstrumentation() .targetContext .filesDir .absolutePath .toString() + "/credman_screenshots", pathConfig = pathConfig, -) { + ) { override fun toString(): String { // This string is appended to all actual/expected screenshots on the device, so make sure // it is a static value. - return "CredentialManagerGoldenImagePathManager" + return "CredentialManagerGoldenPathManager" } -}
\ No newline at end of file +} diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt index 28d83ee157b6..b8432137b35e 100644 --- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt +++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt @@ -60,7 +60,7 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) { @get:Rule val screenshotRule = ComposeScreenshotTestRule( emulationSpec, - CredentialManagerGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec)) + CredentialManagerGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)) ) @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule() diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt index e79176bdebcf..56b1c2e3d5a4 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/passkey/SinglePasskeyScreen.kt @@ -56,7 +56,7 @@ fun SinglePasskeyScreen( headerContent = { SignInHeader( icon = entry.icon, - title = stringResource(R.string.use_password_title), + title = stringResource(R.string.use_passkey_title), ) }, accountContent = { diff --git a/packages/PackageInstaller/res/values-watch/themes.xml b/packages/PackageInstaller/res/values-watch/themes.xml index 5e52008c7fd6..814d08a54f15 100644 --- a/packages/PackageInstaller/res/values-watch/themes.xml +++ b/packages/PackageInstaller/res/values-watch/themes.xml @@ -16,5 +16,11 @@ --> <resources> - <style name="DialogWhenLarge" parent="@android:style/Theme.DeviceDefault.NoActionBar"/> + <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/DataStore/README.md b/packages/SettingsLib/DataStore/README.md new file mode 100644 index 000000000000..30cb9932f104 --- /dev/null +++ b/packages/SettingsLib/DataStore/README.md @@ -0,0 +1,164 @@ +# Datastore library + +This library aims to manage datastore in a consistent way. + +## Overview + +A datastore is required to extend the `BackupRestoreStorage` class and implement +either `Observable` or `KeyedObservable` interface, which enforces: + +- Backup and restore: Datastore should support + [data backup](https://developer.android.com/guide/topics/data/backup) to + preserve user experiences on a new device. +- Observer pattern: The + [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to + monitor data change in the datastore and + - trigger + [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\)) + automatically. + - track data change event to log metrics. + - update internal state and take action. + +### Backup and restore + +The Android backup framework provides +[BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper) +and +[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper) +to back up a datastore. However, there are several caveats when implement +`BackupHelper`: + +- performBackup: The data is updated incrementally but it is not well + documented. The `ParcelFileDescriptor` state parameters are normally ignored + and data is updated even there is no change. +- restoreEntity: The implementation must take care not to seek or close the + underlying data source, nor read more than size() bytes from the stream when + restore (see + [BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)). + It is possible a `BackupHelper` prevents other `BackupHelper`s from + restoring data. +- writeNewStateDescription: Existing implementations rarely notice that this + callback is invoked after all entities are restored, and check if necessary + data are all restored in `restoreEntity` (e.g. + [BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)), + which is not robust sometimes. + +This library provides more clear API and offers some improvements: + +- The implementation only needs to focus on the `BackupRestoreEntity` + interface. The `InputStream` of restore will ensure bounded data are read, + and close the stream will be no-op. +- The library computes checksum of the backup data automatically, so that + unchanged data will not be sent to Android backup system. +- Data compression is supported: + - ZIP best compression is enabled by default, no extra effort needs to be + taken. + - It is safe to switch between compression and no compression in future, + the backup data will add 1 byte header to recognize the codec. + - To support other compression algorithms, simply wrap over the + `InputStream` and `OutputStream`. Actually, the checksum is computed in + this way by + [CheckedInputStream](https://developer.android.com/reference/java/util/zip/CheckedInputStream) + and + [CheckedOutputStream](https://developer.android.com/reference/java/util/zip/CheckedOutputStream), + see `BackupRestoreStorage` implementation for more details. +- Enhanced forward compatibility for file is enabled: If a backup includes + data that didn't exist in earlier versions of the app, the data can still be + successfully restored in those older versions. This is achieved by extending + the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will + treat each file as an entity and do the backup / restore. +- Manual `BackupManager.dataChanged` call is unnecessary now, the library will + do the invocation (see next section). + +### Observer pattern + +Manual `BackupManager.dataChanged` call is required by current backup framework. +In practice, it is found that `SharedPreferences` usages foget to invoke the +API. Besides, there are common use cases to log metrics when data is changed. +Consequently, observer pattern is employed to resolve the issues. + +If the datastore is key-value based (e.g. `SharedPreferences`), implements the +`KeyedObservable` interface to offer fine-grained observer. Otherwise, +implements `Observable`. The library provides thread-safe implementations +(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be +helpful. + +Keep in mind that the implementation should call `KeyedObservable.notifyChange` +/ `Observable.notifyChange` whenever internal data is changed, so that the +registered observer will be notified properly. + +## Usage and example + +For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To +back up other file based storage, extend the `BackupRestoreFileStorage` class. + +Here is an example of customized datastore, which has a string to back up: + +```kotlin +class MyDataStore : ObservableBackupRestoreStorage() { + // Another option is make it a StringEntity type and maintain a String field inside StringEntity + @Volatile // backup/restore happens on Binder thread + var data: String? = null + private set + + fun setData(data: String?) { + this.data = data + notifyChange(ChangeReason.UPDATE) + } + + override val name: String + get() = "MyData" + + override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = + listOf(StringEntity("data")) + + private inner class StringEntity(override val key: String) : BackupRestoreEntity { + override fun backup( + backupContext: BackupContext, + outputStream: OutputStream, + ) = + if (data != null) { + outputStream.write(data!!.toByteArray(UTF_8)) + EntityBackupResult.UPDATE + } else { + EntityBackupResult.DELETE + } + + override fun restore(restoreContext: RestoreContext, inputStream: InputStream) { + data = String(inputStream.readAllBytes(), UTF_8) + // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you + } + } + + override fun onRestoreFinished() { + // TODO: Update state with the restored data. Use this callback instead "restore()" in case + // the restore action involves several entities. + // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you + } +} +``` + +In the application class: + +```kotlin +class MyApplication : Application() { + override fun onCreate() { + super.onCreate(); + BackupRestoreStorageManager.getInstance(this).add(MyDataStore()); + } +} +``` + +In the custom `BackupAgentHelper` class: + +```kotlin +class MyBackupAgentHelper : BackupAgentHelper() { + override fun onCreate() { + BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this); + } + + override fun onRestoreFinished() { + BackupRestoreStorageManager.getInstance(this).onRestoreFinished(); + } +} +``` diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java index 18e8fc38ddb0..f47041df6ee3 100644 --- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java +++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java @@ -85,7 +85,7 @@ public class RestrictedLockUtils { */ @RequiresApi(Build.VERSION_CODES.M) public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) { - final Intent intent = getShowAdminSupportDetailsIntent(context, admin); + final Intent intent = getShowAdminSupportDetailsIntent(admin); int targetUserId = UserHandle.myUserId(); if (admin != null) { if (admin.user != null @@ -98,9 +98,16 @@ public class RestrictedLockUtils { } /** - * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}. + * @deprecated No context needed. Use {@link #getShowAdminSupportDetailsIntent(EnforcedAdmin)}. */ public static Intent getShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) { + return getShowAdminSupportDetailsIntent(admin); + } + + /** + * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}. + */ + public static Intent getShowAdminSupportDetailsIntent(EnforcedAdmin admin) { final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS); if (admin != null) { if (admin.component != null) { diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt index f5fba7fb3cc8..d59076082c66 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt @@ -17,28 +17,25 @@ package com.android.settingslib.spa.screenshot.util import androidx.test.platform.app.InstrumentationRegistry -import platform.test.screenshot.GoldenImagePathManager +import platform.test.screenshot.GoldenPathManager import platform.test.screenshot.PathConfig -/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */ -class SettingsGoldenImagePathManager( - pathConfig: PathConfig, - assetsPathRelativeToBuildRoot: String -) : - GoldenImagePathManager( +/** A [GoldenPathManager] that should be used for all Settings screenshot tests. */ +class SettingsGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) : + GoldenPathManager( appContext = InstrumentationRegistry.getInstrumentation().context, assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot, deviceLocalPath = - InstrumentationRegistry.getInstrumentation() - .targetContext - .filesDir - .absolutePath - .toString() + "/settings_screenshots", + InstrumentationRegistry.getInstrumentation() + .targetContext + .filesDir + .absolutePath + .toString() + "/settings_screenshots", pathConfig = pathConfig, ) { override fun toString(): String { // This string is appended to all actual/expected screenshots on the device, so make sure // it is a static value. - return "SettingsGoldenImagePathManager" + return "SettingsGoldenPathManager" } } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt index ae85675ab1b8..16f6b5e773c9 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt @@ -44,7 +44,7 @@ class SettingsScreenshotTestRule( private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) private val screenshotRule = ScreenshotTestRule( - SettingsGoldenImagePathManager( + SettingsGoldenPathManager( getEmulatedDevicePathConfig(emulationSpec), assetsPathRelativeToBuildRoot ) diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml index 3586dcb2909c..b21895b31256 100644 --- a/packages/SettingsLib/res/layout/dialog_with_icon.xml +++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml @@ -35,12 +35,14 @@ android:id="@+id/dialog_with_icon_title" android:layout_width="match_parent" android:layout_height="wrap_content" + android:hyphenationFrequency="fullFast" android:gravity="center" style="@style/DialogWithIconTitle"/> <TextView android:id="@+id/dialog_with_icon_message" android:layout_width="match_parent" android:layout_height="wrap_content" + android:hyphenationFrequency="fullFast" android:gravity="center" style="@style/TextAppearanceSmall"/> diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java index fe1529d11cd8..9c518de18582 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java @@ -192,7 +192,7 @@ public class VolumeControlProfileTest { mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); final Executor executor = (command -> new Thread(command).start()); - final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {}; + final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {}; mProfile.registerCallback(executor, callback); verify(mService).registerCallback(executor, callback); @@ -200,7 +200,7 @@ public class VolumeControlProfileTest { @Test public void unregisterCallback_verifyIsCalled() { - final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {}; + final BluetoothVolumeControl.Callback callback = new BluetoothVolumeControl.Callback() {}; mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService); mProfile.unregisterCallback(callback); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index eb2d13dc9eb5..43ea3ec3de4e 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -698,6 +698,9 @@ <!-- Permission required for CTS test - CtsWearableSensingServiceTestCases --> <uses-permission android:name="android.permission.MANAGE_WEARABLE_SENSING_SERVICE" /> + <!-- Permission required for CTS test - OnDeviceIntelligenceManagerTest --> + <uses-permission android:name="android.permission.USE_ON_DEVICE_INTELLIGENCE" /> + <!-- Permission required for CTS test - CallAudioInterceptionTest --> <uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" /> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 85bdb295cbb2..1c9863087704 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -115,10 +115,10 @@ flag { } flag { - name: "notifications_background_media_icons" + name: "notifications_background_icons" namespace: "systemui" - description: "Updates icons for media notifications in the background." - bug: "315143160" + description: "Moves part of the notification icon updates to the background." + bug: "315143361" metadata { purpose: PURPOSE_BUGFIX } @@ -463,6 +463,13 @@ flag { } flag { + name: "enable_contextual_tips_frequency_cap" + description: "Enables frequency capping for contextual tips, e.g. 1x/day, 2x/week, 3x/lifetime." + namespace: "systemui" + bug: "322891421" +} + +flag { name: "enable_contextual_tips" description: "Enables showing contextual tips." namespace: "systemui" diff --git a/packages/SystemUI/customization/res/values/ids.xml b/packages/SystemUI/customization/res/values/ids.xml new file mode 100644 index 000000000000..5eafbfc1f0b1 --- /dev/null +++ b/packages/SystemUI/customization/res/values/ids.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- View ids for elements in large weather clock --> + <item type="id" name="weather_clock_time" /> + <item type="id" name="weather_clock_date" /> + <item type="id" name="weather_clock_weather_icon" /> + <item type="id" name="weather_clock_temperature" /> + <item type="id" name="weather_clock_alarm_dnd" /> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt index 87b1bbb9ff70..1adf414c9ef0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt @@ -40,7 +40,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository -import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import java.util.Optional @@ -50,7 +49,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -136,12 +134,17 @@ class HomeControlsDreamStartableTest : SysuiTestCase() { @Test @DisableFlags(FLAG_HOME_PANEL_DREAM) - fun testStartDoesNotRunDreamServiceWhenFlagIsDisabled() = + fun testStartDisablesDreamServiceWhenFlagIsDisabled() = testScope.runTest { selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL) startable.start() runCurrent() - verify(packageManager, never()).setComponentEnabledSetting(any(), any(), any()) + verify(packageManager) + .setComponentEnabledSetting( + eq(componentName), + eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED), + eq(PackageManager.DONT_KILL_APP) + ) } private fun ControlsServiceInfo( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt index a1f885c64312..c0e5a9bc9990 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt @@ -114,7 +114,7 @@ class DisabledByPolicyInteractorTest : SysuiTestCase() { DisabledByPolicyInteractor.PolicyResult.TileDisabled(ADMIN) ) - val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, ADMIN) + val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(ADMIN) assertThat(result).isTrue() verify(activityStarter).postStartActivityDismissingKeyguard(intentCaptor.capture(), any()) assertThat(intentCaptor.value.filterEquals(expectedIntent)).isTrue() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index e683f34c4ea1..278bf9b545a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters +import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel import com.android.systemui.kosmos.testScope @@ -699,6 +700,25 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { } @Test + fun alphaOnFullQsExpansion() = + testScope.runTest { + val viewState = ViewStateAccessor() + val alpha by collectLastValue(underTest.keyguardAlpha(viewState)) + + showLockscreenWithQSExpanded() + + // Alpha fades out as QS expands + shadeRepository.setQsExpansion(0.5f) + assertThat(alpha).isWithin(0.01f).of(0.5f) + shadeRepository.setQsExpansion(0.9f) + assertThat(alpha).isWithin(0.01f).of(0.1f) + + // Ensure that alpha is set back to 1f when QS is fully expanded + shadeRepository.setQsExpansion(1f) + assertThat(alpha).isEqualTo(1f) + } + + @Test fun shadeCollapseFadeIn() = testScope.runTest { val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt new file mode 100644 index 000000000000..183a58a495a3 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt @@ -0,0 +1,241 @@ +/* + * 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.statusbar.policy + +import android.app.Notification +import android.platform.test.annotations.EnableFlags +import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.SysuiTestCase +import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.settings.FakeGlobalSettings +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.invocation.InvocationOnMock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@SmallTest +@RunWithLooper +@RunWith(AndroidJUnit4::class) +@EnableFlags(NotificationThrottleHun.FLAG_NAME) +class AvalancheControllerTest : SysuiTestCase() { + + private val mAvalancheController = AvalancheController() + + // For creating mocks + @get:Rule var rule: MockitoRule = MockitoJUnit.rule() + @Mock private val runnableMock: Runnable? = null + + // For creating TestableHeadsUpManager + @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null + private val mUiEventLoggerFake = UiEventLoggerFake() + private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer())) + private val mGlobalSettings = FakeGlobalSettings() + private val mSystemClock = FakeSystemClock() + private val mExecutor = FakeExecutor(mSystemClock) + private var testableHeadsUpManager: BaseHeadsUpManager? = null + + @Before + fun setUp() { + // Use default non-a11y timeout + Mockito.`when`( + mAccessibilityMgr!!.getRecommendedTimeoutMillis( + ArgumentMatchers.anyInt(), + ArgumentMatchers.anyInt() + ) + ) + .then { i: InvocationOnMock -> i.getArgument(0) } + + // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null + testableHeadsUpManager = + TestableHeadsUpManager( + mContext, + mLogger, + mExecutor, + mGlobalSettings, + mSystemClock, + mAccessibilityMgr, + mUiEventLoggerFake, + mAvalancheController + ) + } + + private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry { + val entry = testableHeadsUpManager!!.createHeadsUpEntry() + + entry.setEntry( + NotificationEntryBuilder() + .setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, ""))) + .build() + ) + return entry + } + + @Test + fun testUpdate_isShowing_runsRunnable() { + // Entry is showing + val headsUpEntry = createHeadsUpEntry(id = 0) + mAvalancheController.headsUpEntryShowing = headsUpEntry + + // Update + mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel") + + // Runnable was run + Mockito.verify(runnableMock, Mockito.times(1)).run() + } + + @Test + fun testUpdate_noneShowingAndNotNext_showNow() { + val headsUpEntry = createHeadsUpEntry(id = 0) + + // None showing + mAvalancheController.headsUpEntryShowing = null + + // Entry is NOT next + mAvalancheController.clearNext() + + // Update + mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel") + + // Entry is showing now + Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(headsUpEntry) + } + + @Test + fun testUpdate_isNext_addsRunnable() { + // Another entry is already showing + val otherShowingEntry = createHeadsUpEntry(id = 0) + mAvalancheController.headsUpEntryShowing = otherShowingEntry + + // Entry is next + val headsUpEntry = createHeadsUpEntry(id = 1) + mAvalancheController.addToNext(headsUpEntry, runnableMock!!) + + // Entry has one Runnable + val runnableList: List<Runnable?>? = mAvalancheController.nextMap[headsUpEntry] + Truth.assertThat(runnableList).isNotNull() + Truth.assertThat(runnableList!!.size).isEqualTo(1) + + // Update + mAvalancheController.update(headsUpEntry, runnableMock, "testLabel") + + // Entry has two Runnables + Truth.assertThat(runnableList.size).isEqualTo(2) + } + + @Test + fun testUpdate_isNotNextWithOtherHunShowing_isNext() { + val headsUpEntry = createHeadsUpEntry(id = 0) + + // Another entry is already showing + val otherShowingEntry = createHeadsUpEntry(id = 1) + mAvalancheController.headsUpEntryShowing = otherShowingEntry + + // Entry is NOT next + mAvalancheController.clearNext() + + // Update + mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel") + + // Entry is next + Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isTrue() + } + + @Test + fun testDelete_isNext_removedFromNext_runnableNotRun() { + // Entry is next + val headsUpEntry = createHeadsUpEntry(id = 0) + mAvalancheController.addToNext(headsUpEntry, runnableMock!!) + + // Delete + mAvalancheController.delete(headsUpEntry, runnableMock, "testLabel") + + // Entry was removed from next + Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isFalse() + + // Runnable was not run + Mockito.verify(runnableMock, Mockito.times(0)).run() + } + + @Test + fun testDelete_wasDropped_removedFromDropSet() { + // Entry was dropped + val headsUpEntry = createHeadsUpEntry(id = 0) + mAvalancheController.debugDropSet.add(headsUpEntry) + + // Delete + mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel") + + // Entry was removed from dropSet + Truth.assertThat(mAvalancheController.debugDropSet.contains(headsUpEntry)).isFalse() + } + + @Test + fun testDelete_wasDropped_runnableNotRun() { + // Entry was dropped + val headsUpEntry = createHeadsUpEntry(id = 0) + mAvalancheController.debugDropSet.add(headsUpEntry) + + // Delete + mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel") + + // Runnable was not run + Mockito.verify(runnableMock, Mockito.times(0)).run() + } + + @Test + fun testDelete_isShowing_runnableRun() { + // Entry is showing + val headsUpEntry = createHeadsUpEntry(id = 0) + mAvalancheController.headsUpEntryShowing = headsUpEntry + + // Delete + mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel") + + // Runnable was run + Mockito.verify(runnableMock, Mockito.times(1)).run() + } + + @Test + fun testDelete_isShowing_showNext() { + // Entry is showing + val showingEntry = createHeadsUpEntry(id = 0) + mAvalancheController.headsUpEntryShowing = showingEntry + + // There's another entry waiting to show next + val nextEntry = createHeadsUpEntry(id = 1) + mAvalancheController.addToNext(nextEntry, runnableMock!!) + + // Delete + mAvalancheController.delete(showingEntry, runnableMock, "testLabel") + + // Next entry is shown + Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index db4d42f4c864..830bcef55046 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -35,13 +35,10 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.app.ActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.app.Person; import android.content.Intent; -import android.os.UserHandle; -import android.service.notification.StatusBarNotification; import android.testing.TestableLooper; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -77,6 +74,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake(); private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer())); + private AvalancheController mAvalancheController = new AvalancheController(); + @Mock private AccessibilityManagerWrapper mAccessibilityMgr; protected static final int TEST_MINIMUM_DISPLAY_TIME = 400; @@ -99,7 +98,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { private BaseHeadsUpManager createHeadsUpManager() { return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings, - mSystemClock, mAccessibilityMgr, mUiEventLoggerFake); + mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, mAvalancheController); } private NotificationEntry createStickyEntry(int id) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java index c032d7cb06b2..61a79d897b0b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java @@ -28,8 +28,8 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.testing.TestableLooper; -import androidx.test.filters.SmallTest; import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -76,6 +76,7 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { @Mock private UiEventLogger mUiEventLogger; @Mock private JavaAdapter mJavaAdapter; @Mock private ShadeInteractor mShadeInteractor; + private AvalancheController mAvalancheController = new AvalancheController(); private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone { TestableHeadsUpManagerPhone( @@ -92,7 +93,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, JavaAdapter javaAdapter, - ShadeInteractor shadeInteractor + ShadeInteractor shadeInteractor, + AvalancheController avalancheController ) { super( context, @@ -109,7 +111,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { accessibilityManagerWrapper, uiEventLogger, javaAdapter, - shadeInteractor + shadeInteractor, + avalancheController ); mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; mAutoDismissTime = TEST_AUTO_DISMISS_TIME; @@ -131,12 +134,15 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { mAccessibilityManagerWrapper, mUiEventLogger, mJavaAdapter, - mShadeInteractor + mShadeInteractor, + mAvalancheController ); } @Before public void setUp() { + // TODO(b/315362456) create separate test with the flag disabled + // then modify this file to test with the flag enabled mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME); when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false)); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java index 27476299cf18..d8f77f054b49 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java @@ -43,9 +43,10 @@ class TestableHeadsUpManager extends BaseHeadsUpManager { GlobalSettings globalSettings, SystemClock systemClock, AccessibilityManagerWrapper accessibilityManagerWrapper, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + AvalancheController avalancheController) { super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock, - executor, accessibilityManagerWrapper, uiEventLogger); + executor, accessibilityManagerWrapper, uiEventLogger, avalancheController); mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME; mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt index 0ef3d200d1fa..a90d4b2b6061 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt @@ -118,6 +118,12 @@ class DragToInteractView(context: Context) : FrameLayout(context) { iconResId = R.drawable.pip_ic_close_white ) ) + + // Ensure this is unfocusable & uninteractable + isClickable = false + isFocusable = false + importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO + // END DragToInteractView modification } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java index d3e85e092b3a..1f0459978c3c 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -113,13 +113,8 @@ class MenuAnimationController { /* Moves position without updating underlying percentage position. Can be animated. */ void moveToPosition(PointF position, boolean animateMovement) { - if (Flags.floatingMenuImeDisplacementAnimation()) { - moveToPositionX(position.x, animateMovement); - moveToPositionY(position.y, animateMovement); - } else { - moveToPositionX(position.x, /* animateMovement = */ false); - moveToPositionY(position.y, /* animateMovement = */ false); - } + moveToPositionX(position.x, animateMovement); + moveToPositionY(position.y, animateMovement); } void moveToPositionX(float positionX) { @@ -127,7 +122,7 @@ class MenuAnimationController { } void moveToPositionX(float positionX, boolean animateMovement) { - if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) { + if (animateMovement) { springMenuWith(DynamicAnimation.TRANSLATION_X, createSpringForce(), /* velocity = */ 0, @@ -142,7 +137,7 @@ class MenuAnimationController { } void moveToPositionY(float positionY, boolean animateMovement) { - if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) { + if (animateMovement) { springMenuWith(DynamicAnimation.TRANSLATION_Y, createSpringForce(), /* velocity = */ 0, @@ -455,7 +450,7 @@ class MenuAnimationController { ? MIN_PERCENT : Math.min(MAX_PERCENT, position.y / draggableBounds.height()); - if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) { + if (!writeToPosition) { mMenuView.onEdgeChangedIfNeeded(); } else { mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY)); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java index e57323b81490..35fe6b14ee28 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java @@ -74,6 +74,12 @@ class MenuMessageView extends LinearLayout implements addView(mTextView, Index.TEXT_VIEW, new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1)); addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + + // The message box is not focusable, but will announce its contents when it appears. + // The textView and button are still interactable. + setClickable(false); + setFocusable(false); + setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index 577bbc0bd840..270fedcf3617 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -101,6 +101,10 @@ class MenuView extends FrameLayout implements loadLayoutResources(); addView(mTargetFeaturesView); + + setClickable(false); + setFocusable(false); + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } @Override @@ -224,8 +228,7 @@ class MenuView extends FrameLayout implements } // We can skip animating if FAB is not visible - if (Flags.floatingMenuImeDisplacementAnimation() - && animateMovement && getVisibility() == VISIBLE) { + if (animateMovement && getVisibility() == VISIBLE) { mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true); // onArrivalAtPosition() is called at the end of the animation. } else { @@ -331,7 +334,7 @@ class MenuView extends FrameLayout implements mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked); } - if (Flags.floatingMenuOverlapsNavBarsFlag() && !Flags.floatingMenuAnimatedTuck()) { + if (!Flags.floatingMenuAnimatedTuck()) { if (isMoveToTucked) { final float halfWidth = getMenuWidth() / 2.0f; final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java index 4865fcedc457..760e1c374e31 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -34,7 +34,6 @@ import android.view.WindowMetrics; import androidx.annotation.DimenRes; -import com.android.systemui.Flags; import com.android.systemui.res.R; import java.lang.annotation.Retention; @@ -155,11 +154,6 @@ class MenuViewAppearance { final int margin = getMenuMargin(); final Rect draggableBounds = new Rect(getWindowAvailableBounds()); - if (!Flags.floatingMenuOverlapsNavBarsFlag()) { - // Initializes start position for mapping the translation of the menu view. - draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0); - } - draggableBounds.top += margin; draggableBounds.right -= getMenuWidth(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index cd3b8a68fb48..97e38f4fc718 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -319,6 +319,9 @@ class MenuViewLayer extends FrameLayout implements if (Flags.floatingMenuAnimatedTuck()) { setClipChildren(true); } + setClickable(false); + setFocusable(false); + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } @Override @@ -443,21 +446,18 @@ class MenuViewLayer extends FrameLayout implements } public void onMoveToTuckedChanged(boolean moveToTuck) { - if (Flags.floatingMenuOverlapsNavBarsFlag()) { - if (moveToTuck) { - final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds(); - final int[] location = getLocationOnScreen(); - bounds.offset( - location[0], - location[1] - ); - - setClipBounds(bounds); - } - // Instead of clearing clip bounds when moveToTuck is false, - // wait until the spring animation finishes. + if (moveToTuck) { + final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds(); + final int[] location = getLocationOnScreen(); + bounds.offset( + location[0], + location[1] + ); + + setClipBounds(bounds); } - // Function is a no-operation if flag is disabled. + // Instead of clearing clip bounds when moveToTuck is false, + // wait until the spring animation finishes. } private void onSpringAnimationsEndAction() { @@ -475,9 +475,7 @@ class MenuViewLayer extends FrameLayout implements setClipBounds(null); } } - if (Flags.floatingMenuImeDisplacementAnimation()) { - mMenuView.onArrivalAtPosition(false); - } + mMenuView.onArrivalAtPosition(false); } void dispatchAccessibilityAction(int id) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java index bc9d1ffd259b..6b1240b87b72 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java @@ -20,11 +20,9 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_ import android.content.Context; import android.graphics.PixelFormat; -import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; -import com.android.systemui.Flags; import com.android.systemui.util.settings.SecureSettings; /** @@ -88,14 +86,9 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu { params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; params.windowAnimations = android.R.style.Animation_Translucent; // Insets are configured to allow the menu to display over navigation and system bars. - if (Flags.floatingMenuOverlapsNavBarsFlag()) { - params.setFitInsetsTypes(0); - params.layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - } else { - params.setFitInsetsTypes( - WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()); - } + params.setFitInsetsTypes(0); + params.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; return params; } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 9de71c1880fe..8bd675c44943 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -881,7 +881,7 @@ public class AuthContainerView extends LinearLayout final Runnable endActionRunnable = () -> { setVisibility(View.INVISIBLE); - if (Flags.customBiometricPrompt()) { + if (Flags.customBiometricPrompt() && constraintBp()) { mPromptSelectorInteractorProvider.get().resetPrompt(); } removeWindowIfAttached(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt index b87fadf995e6..4d88f4945952 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.biometrics.data.repository import android.hardware.biometrics.Flags import android.hardware.biometrics.PromptInfo +import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed @@ -151,6 +152,7 @@ constructor( val hasCredentialViewShown = kind.value !is PromptKind.Biometric val showBpForCredential = Flags.customBiometricPrompt() && + constraintBp() && !Utils.isBiometricAllowed(promptInfo) && isDeviceCredentialAllowed(promptInfo) && promptInfo.contentView != null diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index e48f05d09fc4..7bb75bf5ca9b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -22,6 +22,7 @@ import android.content.Context import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricPrompt +import android.hardware.biometrics.Flags import android.hardware.face.FaceManager import android.text.method.ScrollingMovementMethod import android.util.Log @@ -166,11 +167,14 @@ object BiometricViewBinder { titleView.text = viewModel.title.first() subtitleView.text = viewModel.subtitle.first() descriptionView.text = viewModel.description.first() - BiometricCustomizedViewBinder.bind( - customizedViewContainer, - view.requireViewById(R.id.space_above_content), - viewModel - ) + + if (Flags.customBiometricPrompt() && constraintBp()) { + BiometricCustomizedViewBinder.bind( + customizedViewContainer, + view.requireViewById(R.id.space_above_content), + viewModel + ) + } // set button listeners negativeButton.setOnClickListener { legacyCallback.onButtonNegative() } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt index 3469cfa210ba..e457601a6d52 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt @@ -86,7 +86,12 @@ object PromptIconViewBinder { launch { var width = 0 var height = 0 - viewModel.activeAuthType.collect { activeAuthType -> + combine(promptViewModel.size, viewModel.activeAuthType, ::Pair).collect { + (_, activeAuthType) -> + // Every time after bp shows, [isIconViewLoaded] is set to false in + // [BiometricViewSizeBinder]. Then when biometric prompt view is redrew + // (when size or activeAuthType changes), we need to update + // [isIconViewLoaded] here to keep it correct. when (activeAuthType) { AuthType.Fingerprint, AuthType.Coex -> { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt index 9c28f1c16546..9949e4c7d3d4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt @@ -141,6 +141,9 @@ constructor( /** Hide the side fingerprint sensor indicator */ private fun hide() { if (overlayView != null) { + val lottie = overlayView!!.requireViewById<LottieAnimationView>(R.id.sidefps_animation) + lottie.pauseAnimation() + lottie.removeAllLottieOnCompositionLoadedListener() windowManager.get().removeView(overlayView) overlayView = null } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 61aeffe03b5d..86b0b4455f61 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -29,6 +29,7 @@ import android.util.Log import android.view.HapticFeedbackConstants import android.view.MotionEvent import com.android.systemui.Flags.bpTalkback +import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor @@ -217,18 +218,12 @@ constructor( */ val faceMode: Flow<Boolean> = combine(modalities, isConfirmationRequired, fingerprintStartMode) { - modalities: BiometricModalities, - isConfirmationRequired: Boolean, - fingerprintStartMode: FingerprintStartMode -> - if (modalities.hasFaceAndFingerprint) { - if (isConfirmationRequired) { - false - } else { - !fingerprintStartMode.isStarted - } - } else { - false - } + modalities, + isConfirmationRequired, + fingerprintStartMode -> + modalities.hasFaceAndFingerprint && + !isConfirmationRequired && + fingerprintStartMode == FingerprintStartMode.Pending } .distinctUntilChanged() @@ -248,14 +243,11 @@ constructor( * asset to be loaded before determining the prompt size. */ val isIconViewLoaded: Flow<Boolean> = - combine(credentialKind, _isIconViewLoaded.asStateFlow()) { credentialKind, isIconViewLoaded - -> - if (credentialKind is PromptKind.Biometric) { - isIconViewLoaded - } else { - true + combine(modalities, _isIconViewLoaded.asStateFlow()) { modalities, isIconViewLoaded -> + val noIcon = modalities.isEmpty + noIcon || isIconViewLoaded } - } + .distinctUntilChanged() // Sets whether the prompt's iconView animation has been loaded in the view yet. fun setIsIconViewLoaded(iconViewLoaded: Boolean) { @@ -284,7 +276,7 @@ constructor( promptSelectorInteractor.prompt .map { when { - !customBiometricPrompt() || it == null -> null + !(customBiometricPrompt() && constraintBp()) || it == null -> null it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme) it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap) else -> @@ -304,7 +296,7 @@ constructor( promptSelectorInteractor.prompt .map { when { - !customBiometricPrompt() || it == null -> "" + !(customBiometricPrompt() && constraintBp()) || it == null -> "" it.logoDescription != null -> it.logoDescription else -> try { @@ -329,7 +321,7 @@ constructor( /** Custom content view for the prompt. */ val contentView: Flow<PromptContentView?> = promptSelectorInteractor.prompt - .map { if (customBiometricPrompt()) it?.contentView else null } + .map { if (customBiometricPrompt() && constraintBp()) it?.contentView else null } .distinctUntilChanged() private val originalDescription = diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index 3819e614aca0..4f4f3d0324b3 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -97,6 +97,14 @@ class FalsingCollectorImpl implements FalsingCollector { } }; + private final KeyguardStateController.Callback mKeyguardStateControllerCallback = + new KeyguardStateController.Callback() { + @Override + public void onKeyguardShowingChanged() { + updateSensorRegistration(); + } + }; + private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback = new KeyguardUpdateMonitorCallback() { @@ -176,6 +184,8 @@ class FalsingCollectorImpl implements FalsingCollector { mStatusBarStateController.addCallback(mStatusBarStateListener); mState = mStatusBarStateController.getState(); + mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); + mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback); mJavaAdapter.alwaysCollectFlow( @@ -364,6 +374,24 @@ class FalsingCollectorImpl implements FalsingCollector { } else { sessionEnd(); } + updateSensorRegistration(); + } + + private boolean shouldBeRegisteredToSensors() { + return mScreenOn + && (mState == StatusBarState.KEYGUARD + || (mState == StatusBarState.SHADE + && mKeyguardStateController.isOccluded() + && mKeyguardStateController.isShowing())) + && !mShowingAod; + } + + private void updateSensorRegistration() { + if (shouldBeRegisteredToSensors()) { + registerSensors(); + } else { + unregisterSensors(); + } } private void sessionStart() { @@ -371,7 +399,6 @@ class FalsingCollectorImpl implements FalsingCollector { logDebug("Starting Session"); mSessionStarted = true; mFalsingDataProvider.setJustUnlockedWithFace(false); - registerSensors(); mFalsingDataProvider.onSessionStarted(); } } @@ -380,7 +407,6 @@ class FalsingCollectorImpl implements FalsingCollector { if (mSessionStarted) { logDebug("Ending Session"); mSessionStarted = false; - unregisterSensors(); mFalsingDataProvider.onSessionEnd(); } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index a4011fd7718c..1003050caf7c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -69,6 +69,7 @@ import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl; import com.android.systemui.toast.ToastModule; import com.android.systemui.unfold.SysUIUnfoldStartableModule; import com.android.systemui.unfold.UnfoldTransitionModule; +import com.android.systemui.util.kotlin.SysUICoroutinesModule; import com.android.systemui.volume.dagger.VolumeModule; import com.android.systemui.wallpapers.dagger.WallpaperModule; @@ -117,6 +118,7 @@ import javax.inject.Named; ShadeModule.class, StartCentralSurfacesModule.class, SceneContainerFrameworkModule.class, + SysUICoroutinesModule.class, SysUIUnfoldStartableModule.class, UnfoldTransitionModule.Startables.class, ToastModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt index 6cd94c623ff7..14525269eb6b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartable.kt @@ -30,7 +30,7 @@ import kotlinx.coroutines.launch class HomeControlsDreamStartable @Inject constructor( - private val context: Context, + context: Context, private val packageManager: PackageManager, private val homeControlsComponentInteractor: HomeControlsComponentInteractor, @Background private val bgScope: CoroutineScope, @@ -39,10 +39,13 @@ constructor( private val componentName = ComponentName(context, HomeControlsDreamService::class.java) override fun start() { - if (!homePanelDream()) return bgScope.launch { - homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent -> - setEnableHomeControlPanel(selectedPanelComponent != null) + if (homePanelDream()) { + homeControlsComponentInteractor.panelComponent.collect { selectedPanelComponent -> + setEnableHomeControlPanel(selectedPanelComponent != null) + } + } else { + setEnableHomeControlPanel(false) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt index 027a739b4abf..bf225633fa86 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import javax.inject.Inject -class MediaCarouselViewModel @Inject constructor(mediaDataManager: MediaDataManager) { - val isMediaVisible: Boolean = mediaDataManager.hasActiveMediaOrRecommendation() +class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) { + val isMediaVisible: Boolean + get() = mediaDataManager.hasActiveMediaOrRecommendation() } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index f4903f1f054f..768bb8e2e917 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -131,6 +131,7 @@ import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeViewController; +import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.rotation.RotationButton; @@ -199,6 +200,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; private final KeyguardStateController mKeyguardStateController; private final ShadeViewController mShadeViewController; + private final PanelExpansionInteractor mPanelExpansionInteractor; private final NotificationRemoteInputManager mNotificationRemoteInputManager; private final OverviewProxyService mOverviewProxyService; private final NavigationModeController mNavigationModeController; @@ -537,6 +539,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, KeyguardStateController keyguardStateController, ShadeViewController shadeViewController, + PanelExpansionInteractor panelExpansionInteractor, NotificationRemoteInputManager notificationRemoteInputManager, NotificationShadeDepthController notificationShadeDepthController, @Main Handler mainHandler, @@ -575,6 +578,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; mKeyguardStateController = keyguardStateController; mShadeViewController = shadeViewController; + mPanelExpansionInteractor = panelExpansionInteractor; mNotificationRemoteInputManager = notificationRemoteInputManager; mOverviewProxyService = overviewProxyService; mNavigationModeController = navigationModeController; @@ -749,7 +753,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements final Display display = mView.getDisplay(); mView.setComponents(mRecentsOptional); if (mCentralSurfacesOptionalLazy.get().isPresent()) { - mView.setComponents(mShadeViewController); + mView.setComponents(mShadeViewController, mPanelExpansionInteractor); } mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer); mView.setOnVerticalChangedListener(this::onVerticalChanged); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index c5190a21f079..1927f4932ee0 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -75,6 +75,7 @@ import com.android.systemui.recents.Recents; import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shade.ShadeViewController; +import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.shared.rotation.FloatingRotationButton; import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback; import com.android.systemui.shared.rotation.RotationButtonController; @@ -149,7 +150,9 @@ public class NavigationBarView extends FrameLayout { private NavigationBarInflaterView mNavigationInflaterView; private Optional<Recents> mRecentsOptional = Optional.empty(); @Nullable - private ShadeViewController mPanelView; + private ShadeViewController mShadeViewController; + @Nullable + private PanelExpansionInteractor mPanelExpansionInteractor; private RotationContextButton mRotationContextButton; private FloatingRotationButton mFloatingRotationButton; private RotationButtonController mRotationButtonController; @@ -347,8 +350,9 @@ public class NavigationBarView extends FrameLayout { } /** */ - public void setComponents(ShadeViewController panel) { - mPanelView = panel; + public void setComponents(ShadeViewController svc, PanelExpansionInteractor pei) { + mShadeViewController = svc; + mPanelExpansionInteractor = pei; updatePanelSystemUiStateFlags(); } @@ -750,10 +754,10 @@ public class NavigationBarView extends FrameLayout { private void updatePanelSystemUiStateFlags() { if (SysUiState.DEBUG) { - Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView); + Log.d(TAG, "Updating panel sysui state flags: panelView=" + mShadeViewController); } - if (mPanelView != null) { - mPanelView.updateSystemUiStateFlags(); + if (mShadeViewController != null) { + mShadeViewController.updateSystemUiStateFlags(); } } @@ -801,7 +805,8 @@ public class NavigationBarView extends FrameLayout { */ void updateSlippery() { setSlippery(!isQuickStepSwipeUpEnabled() || - (mPanelView != null && mPanelView.isFullyExpanded() && !mPanelView.isCollapsing())); + (mPanelExpansionInteractor != null && mPanelExpansionInteractor.isFullyExpanded() + && !mPanelExpansionInteractor.isCollapsing())); } void setSlippery(boolean slippery) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 35cac4b2adb2..145674747bb6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -587,7 +587,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy name = "handleClick"; if (mState.disabledByPolicy) { Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent( - mContext, mEnforcedAdmin); + mEnforcedAdmin); mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); } else { mQSLogger.logHandleClick(mTileSpec, msg.arg1); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java index 32deb30d926b..6b654beea149 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java @@ -34,11 +34,11 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.drawable.CircleFramedDrawable; -import com.android.systemui.res.R; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.PseudoGridView; import com.android.systemui.qs.QSUserSwitcherEvent; import com.android.systemui.qs.user.UserSwitchDialogController; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; import com.android.systemui.statusbar.policy.UserSwitcherController; @@ -186,7 +186,7 @@ public class UserDetailView extends PseudoGridView { (UserRecord) view.getTag(); if (userRecord.isDisabledByAdmin()) { final Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent( - mContext, userRecord.enforcedAdmin); + userRecord.enforcedAdmin); mController.startActivity(intent); } else if (userRecord.isSwitchToEnabled) { MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt index d1f8945cc091..87b89ea6810a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt @@ -96,10 +96,7 @@ constructor( is PolicyResult.TileEnabled -> false is PolicyResult.TileDisabled -> { val intent = - RestrictedLockUtils.getShowAdminSupportDetailsIntent( - context, - policyResult.admin - ) + RestrictedLockUtils.getShowAdminSupportDetailsIntent(policyResult.admin) activityStarter.postStartActivityDismissingKeyguard(intent, 0) true } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java index 861a2edebf14..539b0c2dd599 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java @@ -19,6 +19,7 @@ package com.android.systemui.settings.brightness; import static com.android.systemui.Flags.hapticBrightnessSlider; import android.content.Context; +import android.content.Intent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -33,6 +34,7 @@ import com.android.systemui.Gefingerpoken; import com.android.systemui.classifier.Classifier; import com.android.systemui.haptics.slider.HapticSliderViewBinder; import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.res.R; import com.android.systemui.statusbar.VibratorHelper; @@ -62,6 +64,7 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV private final UiEventLogger mUiEventLogger; private final SeekableSliderHapticPlugin mBrightnessSliderHapticPlugin; + private final ActivityStarter mActivityStarter; private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() { @Override @@ -84,11 +87,13 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV BrightnessSliderView brightnessSliderView, FalsingManager falsingManager, UiEventLogger uiEventLogger, - SeekableSliderHapticPlugin brightnessSliderHapticPlugin) { + SeekableSliderHapticPlugin brightnessSliderHapticPlugin, + ActivityStarter activityStarter) { super(brightnessSliderView); mFalsingManager = falsingManager; mUiEventLogger = uiEventLogger; mBrightnessSliderHapticPlugin = brightnessSliderHapticPlugin; + mActivityStarter = activityStarter; } /** @@ -131,7 +136,15 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV @Override public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) { - mView.setEnforcedAdmin(admin); + if (admin == null) { + mView.setAdminBlocker(null); + } else { + mView.setAdminBlocker(() -> { + Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(admin); + mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); + return true; + }); + } } private void setMirror(ToggleSlider toggleSlider) { @@ -259,18 +272,21 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV private final UiEventLogger mUiEventLogger; private final VibratorHelper mVibratorHelper; private final SystemClock mSystemClock; + private final ActivityStarter mActivityStarter; @Inject public Factory( FalsingManager falsingManager, UiEventLogger uiEventLogger, VibratorHelper vibratorHelper, - SystemClock clock + SystemClock clock, + ActivityStarter activityStarter ) { mFalsingManager = falsingManager; mUiEventLogger = uiEventLogger; mVibratorHelper = vibratorHelper; mSystemClock = clock; + mActivityStarter = activityStarter; } /** @@ -292,7 +308,8 @@ public class BrightnessSliderController extends ViewController<BrightnessSliderV if (hapticBrightnessSlider()) { HapticSliderViewBinder.bind(viewRoot, plugin); } - return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger, plugin); + return new BrightnessSliderController( + root, mFalsingManager, mUiEventLogger, plugin, mActivityStarter); } /** Get the layout to inflate based on what slider to use */ diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java index c43d20cdf52f..92006a473ed8 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java @@ -31,7 +31,6 @@ import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; import com.android.systemui.res.R; @@ -120,9 +119,8 @@ public class BrightnessSliderView extends FrameLayout { * @param admin * @see ToggleSeekBar#setEnforcedAdmin */ - public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) { - mSlider.setEnabled(admin == null); - mSlider.setEnforcedAdmin(admin); + void setAdminBlocker(ToggleSeekBar.AdminBlocker blocker) { + mSlider.setAdminBlocker(blocker); } /** diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java index a5a0ae70045e..288ff09602f4 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java @@ -17,20 +17,15 @@ package com.android.systemui.settings.brightness; import android.content.Context; -import android.content.Intent; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.SeekBar; -import com.android.settingslib.RestrictedLockUtils; -import com.android.systemui.Dependency; -import com.android.systemui.plugins.ActivityStarter; - public class ToggleSeekBar extends SeekBar { private String mAccessibilityLabel; - private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null; + private AdminBlocker mAdminBlocker; public ToggleSeekBar(Context context) { super(context); @@ -46,10 +41,7 @@ public class ToggleSeekBar extends SeekBar { @Override public boolean onTouchEvent(MotionEvent event) { - if (mEnforcedAdmin != null) { - Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent( - mContext, mEnforcedAdmin); - Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0); + if (mAdminBlocker != null && mAdminBlocker.block()) { return true; } if (!isEnabled()) { @@ -71,7 +63,12 @@ public class ToggleSeekBar extends SeekBar { } } - public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) { - mEnforcedAdmin = admin; + void setAdminBlocker(AdminBlocker blocker) { + mAdminBlocker = blocker; + setEnabled(blocker == null); + } + + interface AdminBlocker { + boolean block(); } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index de21a73e312b..a343dedb6742 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -37,9 +37,6 @@ interface ShadeViewController { */ val isPanelExpanded: Boolean - /** Returns whether the shade is in the process of collapsing. */ - val isCollapsing: Boolean - /** Returns whether shade's height is zero. */ val isFullyCollapsed: Boolean @@ -102,19 +99,6 @@ interface ShadeViewController { fun showAodUi() /** - * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead. - * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we - * should be explicit about the what state we're checking. - * - * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD - */ - @Deprecated( - "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" + - "{@link #isOnAod()}, {@link #isOnKeyguard()} instead." - ) - fun isFullyExpanded(): Boolean - - /** * Sends an external (e.g. Status Bar) touch event to the Shade touch handler. * * This is different from [startInputFocusTransfer] as it doesn't rely on setting the launcher diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt index b67156f4b982..c9140b525c1f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt @@ -33,31 +33,33 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : ShadeBackActionInteractor, ShadeLockscreenInteractor, PanelExpansionInteractor { - override fun expandToNotifications() {} - override val isExpanded: Boolean = false + @Deprecated("Use ShadeInteractor instead") override fun expandToNotifications() {} + @Deprecated("Use ShadeInteractor instead") override val isExpanded: Boolean = false override val isPanelExpanded: Boolean = false override fun animateCollapseQs(fullyCollapse: Boolean) {} override fun canBeCollapsed(): Boolean = false - override val isCollapsing: Boolean = false + @Deprecated("Use ShadeAnimationInteractor instead") override val isCollapsing: Boolean = false override val isFullyCollapsed: Boolean = false override val isTracking: Boolean = false override val isViewEnabled: Boolean = false override fun shouldHideStatusBarIconsWhenExpanded() = false - override fun blockExpansionForCurrentTouch() {} + @Deprecated("Not supported by scenes") override fun blockExpansionForCurrentTouch() {} override fun disableHeader(state1: Int, state2: Int, animated: Boolean) {} override fun startExpandLatencyTracking() {} override fun startBouncerPreHideAnimation() {} override fun dozeTimeTick() {} override fun resetViews(animate: Boolean) {} override val barState: Int = 0 + @Deprecated("Only supported by very old devices that will not adopt scenes.") override fun closeUserSwitcherIfOpen(): Boolean { return false } override fun onBackPressed() {} + @Deprecated("According to b/318376223, shade predictive back is not be supported.") override fun onBackProgressed(progressFraction: Float) {} override fun setAlpha(alpha: Int, animate: Boolean) {} override fun setAlphaChangeAnimationEndAction(r: Runnable) {} - override fun setPulsing(pulsing: Boolean) {} + @Deprecated("Not supported by scenes") override fun setPulsing(pulsing: Boolean) {} override fun setQsScrimEnabled(qsScrimEnabled: Boolean) {} override fun setAmbientIndicationTop(ambientIndicationTop: Int, ambientTextVisible: Boolean) {} override fun updateSystemUiStateFlags() {} @@ -66,14 +68,18 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : override fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) {} override fun transitionToExpandedShade(delay: Long) {} - override fun resetViewGroupFade() {} + @Deprecated("Not supported by scenes") override fun resetViewGroupFade() {} + @Deprecated("Not supported by scenes") override fun setKeyguardTransitionProgress(keyguardAlpha: Float, keyguardTranslationY: Int) {} - override fun setOverStretchAmount(amount: Float) {} + @Deprecated("Not supported by scenes") override fun setOverStretchAmount(amount: Float) {} + @Deprecated("TODO(b/325072511) delete this") override fun setKeyguardStatusBarAlpha(alpha: Float) {} override fun showAodUi() {} - override fun isFullyExpanded(): Boolean { - return false - } + @Deprecated( + "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" + + "{@link #isOnAod()}, {@link #isOnKeyguard()} instead." + ) + override val isFullyExpanded = false override fun handleExternalTouch(event: MotionEvent): Boolean { return false } @@ -84,6 +90,7 @@ class ShadeViewControllerEmptyImpl @Inject constructor() : override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl() override val shadeFoldAnimator = ShadeFoldAnimatorEmptyImpl() + @Deprecated("Use SceneInteractor.currentScene instead.") override val legacyPanelExpansion = flowOf(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt index 01118bd1406f..bd96a33c2f41 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractor.kt @@ -41,4 +41,20 @@ interface PanelExpansionInteractor { * backwards-compatibility and should not be consumed by newer code. */ @Deprecated("Use SceneInteractor.currentScene instead.") val legacyPanelExpansion: Flow<Float> + + /** + * This method should not be used anymore, you should probably use [.isShadeFullyOpen] instead. + * It was overused as indicating if shade is open or we're on keyguard/AOD. Moving forward we + * should be explicit about the what state we're checking. + * + * @return if panel is covering the screen, which means we're in expanded shade or keyguard/AOD + */ + @Deprecated( + "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" + + "{@link #isOnAod()}, {@link #isOnKeyguard()} instead." + ) + val isFullyExpanded: Boolean + + /** Returns whether the shade is in the process of collapsing. */ + @Deprecated("Use ShadeAnimationInteractor instead") val isCollapsing: Boolean } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt index 20f73b00d8a7..c5a69680b5ab 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt @@ -35,6 +35,8 @@ class PanelExpansionInteractorImpl @Inject constructor( sceneInteractor: SceneInteractor, + shadeInteractor: ShadeInteractor, + shadeAnimationInteractor: ShadeAnimationInteractor, ) : PanelExpansionInteractor { /** @@ -93,6 +95,16 @@ constructor( } } + @Deprecated( + "depends on the state you check, use {@link #isShadeFullyExpanded()},\n" + + "{@link #isOnAod()}, {@link #isOnKeyguard()} instead." + ) + override val isFullyExpanded = shadeInteractor.isAnyFullyExpanded.value + + @Deprecated("Use ShadeAnimationInteractor instead") + override val isCollapsing = + shadeAnimationInteractor.isAnyCloseAnimationRunning.value || + shadeAnimationInteractor.isLaunchingActivity.value private fun SceneKey.isExpandable(): Boolean { return this == Scenes.Shade || this == Scenes.QuickSettings } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt index 5a777e8574d6..134c983f7b30 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractor.kt @@ -17,7 +17,6 @@ package com.android.systemui.shade.domain.interactor import com.android.systemui.shade.data.repository.ShadeAnimationRepository -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -38,5 +37,5 @@ abstract class ShadeAnimationInteractor( * completes the close. Important: if QS is collapsing back to shade, this will be false because * that is not considered "closing". */ - abstract val isAnyCloseAnimationRunning: Flow<Boolean> + abstract val isAnyCloseAnimationRunning: StateFlow<Boolean> } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt index 2a7658a8eed7..f364d6ddf939 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorEmptyImpl.kt @@ -19,7 +19,7 @@ package com.android.systemui.shade.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.shade.data.repository.ShadeAnimationRepository import javax.inject.Inject -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.MutableStateFlow /** Implementation of ShadeAnimationInteractor for shadeless SysUI variants. */ @SysUISingleton @@ -28,5 +28,5 @@ class ShadeAnimationInteractorEmptyImpl constructor( shadeAnimationRepository: ShadeAnimationRepository, ) : ShadeAnimationInteractor(shadeAnimationRepository) { - override val isAnyCloseAnimationRunning = flowOf(false) + override val isAnyCloseAnimationRunning = MutableStateFlow(false) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt index eaac8ae9dd3a..d9982e39e958 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImpl.kt @@ -18,21 +18,26 @@ package com.android.systemui.shade.domain.interactor import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeAnimationRepository import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn /** Implementation of ShadeAnimationInteractor compatible with the scene container framework. */ @SysUISingleton class ShadeAnimationInteractorSceneContainerImpl @Inject constructor( + @Background scope: CoroutineScope, shadeAnimationRepository: ShadeAnimationRepository, sceneInteractor: SceneInteractor, ) : ShadeAnimationInteractor(shadeAnimationRepository) { @@ -56,4 +61,5 @@ constructor( } } .distinctUntilChanged() + .stateIn(scope, SharingStarted.Eagerly, false) } 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 072f56d2429d..dcfccd8398b2 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 @@ -61,7 +61,7 @@ public class MediaCoordinator implements Coordinator { return false; } - if (!Flags.notificationsBackgroundMediaIcons()) { + if (!Flags.notificationsBackgroundIcons()) { inflateOrUpdateIcons(entry); } @@ -73,14 +73,14 @@ public class MediaCoordinator implements Coordinator { @Override public void onEntryInit(@NonNull NotificationEntry entry) { // We default to STATE_ICONS_UNINFLATED anyway, so there's no need to initialize it. - if (!Flags.notificationsBackgroundMediaIcons()) { + if (!Flags.notificationsBackgroundIcons()) { mIconsState.put(entry, STATE_ICONS_UNINFLATED); } } @Override public void onEntryAdded(@NonNull NotificationEntry entry) { - if (Flags.notificationsBackgroundMediaIcons()) { + if (Flags.notificationsBackgroundIcons()) { if (isMediaNotification(entry.getSbn())) { inflateOrUpdateIcons(entry); } @@ -94,7 +94,7 @@ public class MediaCoordinator implements Coordinator { mIconsState.put(entry, STATE_ICONS_UNINFLATED); } - if (Flags.notificationsBackgroundMediaIcons()) { + if (Flags.notificationsBackgroundIcons()) { if (isMediaNotification(entry.getSbn())) { inflateOrUpdateIcons(entry); } @@ -120,7 +120,7 @@ public class MediaCoordinator implements Coordinator { break; case STATE_ICONS_INFLATED: try { - mIconManager.updateIcons(entry); + mIconManager.updateIcons(entry, /* usingCache = */ false); } catch (InflationException e) { reportInflationError(entry, e); mIconsState.put(entry, STATE_ICONS_ERROR); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index 6400ff6b5c24..4bbe0357b335 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -138,7 +138,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { if (entry.rowExists()) { mLogger.logUpdatingRow(entry, params); - mIconManager.updateIcons(entry); + mIconManager.updateIcons(entry, /* usingCache = */ false); ExpandableNotificationRow row = entry.getRow(); row.reset(); updateRow(entry, row); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index a5f42bb99e10..a900e45adbe7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -28,14 +28,24 @@ import android.view.View import android.widget.ImageView import com.android.app.tracing.traceSection import com.android.internal.statusbar.StatusBarIcon +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.InflationException import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * Inflates and updates icons associated with notifications @@ -53,9 +63,18 @@ class IconManager constructor( private val notifCollection: CommonNotifCollection, private val launcherApps: LauncherApps, - private val iconBuilder: IconBuilder + private val iconBuilder: IconBuilder, + @Application private val applicationCoroutineScope: CoroutineScope, + @Background private val bgCoroutineContext: CoroutineContext, + @Main private val mainCoroutineContext: CoroutineContext, ) : ConversationIconManager { private var unimportantConversationKeys: Set<String> = emptySet() + /** + * A map of running jobs for fetching the person avatar from launcher. The key is the + * notification entry key. + */ + private var launcherPeopleAvatarIconJobs: ConcurrentHashMap<String, Job> = + ConcurrentHashMap<String, Job>() fun attach() { notifCollection.addCollectionListener(entryListener) @@ -136,13 +155,23 @@ constructor( * @throws InflationException Exception if required icons are not valid or specified */ @Throws(InflationException::class) - fun updateIcons(entry: NotificationEntry) = + fun updateIcons(entry: NotificationEntry, usingCache: Boolean = false) = traceSection("IconManager.updateIcons") { if (!entry.icons.areIconsAvailable) { return@traceSection } - entry.icons.smallIconDescriptor = null - entry.icons.peopleAvatarDescriptor = null + + if (usingCache && !Flags.notificationsBackgroundIcons()) { + Log.wtf( + TAG, + "Updating using the cache is not supported when the " + + "notifications_background_conversation_icons flag is off" + ) + } + if (!usingCache || !Flags.notificationsBackgroundIcons()) { + entry.icons.smallIconDescriptor = null + entry.icons.peopleAvatarDescriptor = null + } val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry) val notificationContentDescription = @@ -188,7 +217,7 @@ constructor( @Throws(InflationException::class) private fun getIconDescriptor(entry: NotificationEntry, redact: Boolean): StatusBarIcon { val n = entry.sbn.notification - val showPeopleAvatar = isImportantConversation(entry) && !redact + val showPeopleAvatar = !redact && isImportantConversation(entry) val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor val smallIconDescriptor = entry.icons.smallIconDescriptor @@ -208,26 +237,18 @@ constructor( }) ?: throw InflationException("No icon in notification from " + entry.sbn.packageName) - val ic = - StatusBarIcon( - entry.sbn.user, - entry.sbn.packageName, - icon, - n.iconLevel, - n.number, - iconBuilder.getIconContentDescription(n) - ) + val sbi = icon.toStatusBarIcon(entry) // Cache if important conversation. if (isImportantConversation(entry)) { if (showPeopleAvatar) { - entry.icons.peopleAvatarDescriptor = ic + entry.icons.peopleAvatarDescriptor = sbi } else { - entry.icons.smallIconDescriptor = ic + entry.icons.smallIconDescriptor = sbi } } - return ic + return sbi } @Throws(InflationException::class) @@ -243,16 +264,69 @@ constructor( } } + private fun Icon.toStatusBarIcon(entry: NotificationEntry): StatusBarIcon { + val n = entry.sbn.notification + return StatusBarIcon( + entry.sbn.user, + entry.sbn.packageName, + /* icon = */ this, + n.iconLevel, + n.number, + iconBuilder.getIconContentDescription(n) + ) + } + + private suspend fun getLauncherShortcutIconForPeopleAvatar(entry: NotificationEntry) = + withContext(bgCoroutineContext) { + var icon: Icon? = null + val shortcut = entry.ranking.conversationShortcutInfo + if (shortcut != null) { + try { + icon = launcherApps.getShortcutIcon(shortcut) + } catch (e: Exception) { + Log.e( + TAG, + "Error calling LauncherApps#getShortcutIcon for notification $entry: $e" + ) + } + } + + // Once we have the icon, updating it should happen on the main thread. + if (icon != null) { + withContext(mainCoroutineContext) { + val iconDescriptor = icon.toStatusBarIcon(entry) + + // Cache the value + entry.icons.peopleAvatarDescriptor = iconDescriptor + + // Update the icons using the cached value + updateIcons(entry = entry, usingCache = true) + } + } + } + @Throws(InflationException::class) - private fun createPeopleAvatar(entry: NotificationEntry): Icon? { + private fun createPeopleAvatar(entry: NotificationEntry): Icon { var ic: Icon? = null - val shortcut = entry.ranking.conversationShortcutInfo - if (shortcut != null) { - ic = launcherApps.getShortcutIcon(shortcut) + if (Flags.notificationsBackgroundIcons()) { + // Ideally we want to get the icon from launcher, but this is a binder transaction that + // may take longer so let's kick it off on a background thread and use a placeholder in + // the meantime. + // Cancel the previous job if necessary. + launcherPeopleAvatarIconJobs[entry.key]?.cancel() + launcherPeopleAvatarIconJobs[entry.key] = + applicationCoroutineScope + .launch { getLauncherShortcutIconForPeopleAvatar(entry) } + .apply { invokeOnCompletion { launcherPeopleAvatarIconJobs.remove(entry.key) } } + } else { + val shortcut = entry.ranking.conversationShortcutInfo + if (shortcut != null) { + ic = launcherApps.getShortcutIcon(shortcut) + } } - // Fall back to extract from message + // Try to extract from message if (ic == null) { val extras: Bundle = entry.sbn.notification.extras val messages = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 2745817d6d40..5e87a7aa9320 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -350,6 +350,9 @@ constructor( if (shadeExpansion > 0f || qsExpansion > 0f) { if (configurationBasedDimensions.useSplitShade) { emit(1f) + } else if (qsExpansion == 1f) { + // Ensure HUNs will be visible in QS shade (at least while unlocked) + emit(1f) } else { // Fade as QS shade expands emit(1f - qsExpansion) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index a155e94584e3..24be3db6231f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.AnimationStateHandler; +import com.android.systemui.statusbar.policy.AvalancheController; import com.android.systemui.statusbar.policy.BaseHeadsUpManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; @@ -124,9 +125,10 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, JavaAdapter javaAdapter, - ShadeInteractor shadeInteractor) { + ShadeInteractor shadeInteractor, + AvalancheController avalancheController) { super(context, logger, handler, globalSettings, systemClock, executor, - accessibilityManagerWrapper, uiEventLogger); + accessibilityManagerWrapper, uiEventLogger, avalancheController); Resources resources = mContext.getResources(); mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time); statusBarStateController.addCallback(mStatusBarStateListener); @@ -279,7 +281,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) { headsUpEntry.mRemoteInputActive = remoteInputActive; if (remoteInputActive) { - headsUpEntry.removeAutoRemovalCallbacks("setRemoteInputActive(true)"); + headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)"); } else { headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)"); } @@ -482,7 +484,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp this.mExpanded = expanded; if (expanded) { - removeAutoRemovalCallbacks("setExpanded(true)"); + cancelAutoRemovalCallbacks("setExpanded(true)"); } else { updateEntry(false /* updatePostTime */, "setExpanded(false)"); } @@ -495,7 +497,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp mGutsShownPinned = gutsShownPinned; if (gutsShownPinned) { - removeAutoRemovalCallbacks("setGutsShownPinned(true)"); + cancelAutoRemovalCallbacks("setGutsShownPinned(true)"); } else { updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 8e9c0384987d..5b142590d149 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -44,6 +44,7 @@ import com.android.systemui.res.R; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeViewController; +import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -87,6 +88,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu private final NotificationMediaManager mMediaManager; private final NotificationGutsManager mGutsManager; private final ShadeViewController mNotificationPanel; + private final PanelExpansionInteractor mPanelExpansionInteractor; private final HeadsUpManager mHeadsUpManager; private final AboveShelfObserver mAboveShelfObserver; private final DozeScrimController mDozeScrimController; @@ -108,6 +110,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu StatusBarNotificationPresenter( Context context, ShadeViewController panel, + PanelExpansionInteractor panelExpansionInteractor, QuickSettingsController quickSettingsController, HeadsUpManager headsUp, NotificationShadeWindowView statusBarWindow, @@ -134,6 +137,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu mActivityStarter = activityStarter; mKeyguardStateController = keyguardStateController; mNotificationPanel = panel; + mPanelExpansionInteractor = panelExpansionInteractor; mQsController = quickSettingsController; mHeadsUpManager = headsUp; mDynamicPrivacyController = dynamicPrivacyController; @@ -202,7 +206,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu @Override public boolean isCollapsing() { - return mNotificationPanel.isCollapsing() + return mPanelExpansionInteractor.isCollapsing() || mNotificationShadeWindowController.isLaunchingActivity(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt new file mode 100644 index 000000000000..6aaf5d60ac0b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -0,0 +1,285 @@ +/* + * 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.statusbar.policy + +import android.util.Log +import androidx.annotation.VisibleForTesting +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun +import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry +import javax.inject.Inject + +/* + * Control when heads up notifications show during an avalanche where notifications arrive in fast + * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager + */ +@SysUISingleton +class AvalancheController @Inject constructor() { + + private val tag = "AvalancheController" + private val debug = false + + // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD + @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null + + // List of runnables to run for the HUN showing right now + private var headsUpEntryShowingRunnableList: MutableList<Runnable> = ArrayList() + + // HeadsUpEntry waiting to show + // Use sortable list instead of priority queue for debugging + private val nextList: MutableList<HeadsUpEntry> = ArrayList() + + // Map of HeadsUpEntry waiting to show, and runnables to run when it shows. + // Use HashMap instead of SortedMap for faster lookup, and also because the ordering + // provided by HeadsUpEntry.compareTo is not consistent over time or with HeadsUpEntry.equals + @VisibleForTesting var nextMap: MutableMap<HeadsUpEntry, MutableList<Runnable>> = HashMap() + + // Map of Runnable to label for debugging only + private val debugRunnableLabelMap: MutableMap<Runnable, String> = HashMap() + + // HeadsUpEntry we did not show at all because they are not the top priority hun in their batch + // For debugging only + @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet() + + /** + * Run or delay Runnable for given HeadsUpEntry + */ + fun update(entry: HeadsUpEntry, runnable: Runnable, label: String) { + if (!NotificationThrottleHun.isEnabled) { + runnable.run() + return + } + val fn = "[$label] => AvalancheController.update ${getKey(entry)}" + + if (debug) { + debugRunnableLabelMap[runnable] = label + } + + if (isShowing(entry)) { + log {"$fn => [update showing]" } + runnable.run() + } else if (entry in nextMap) { + log { "$fn => [update next]" } + nextMap[entry]?.add(runnable) + } else if (headsUpEntryShowing == null) { + log { "$fn => [showNow]" } + showNow(entry, arrayListOf(runnable)) + } else { + // Clean up invalid state when entry is in list but not map and vice versa + if (entry in nextMap) nextMap.remove(entry) + if (entry in nextList) nextList.remove(entry) + + addToNext(entry, runnable) + + // Shorten headsUpEntryShowing display time + val nextIndex = nextList.indexOf(entry) + val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1 + if (isOnlyNextEntry) { + // HeadsUpEntry.updateEntry recursively calls AvalancheController#update + // and goes to the isShowing case above + headsUpEntryShowing!!.updateEntry(false, "avalanche duration update") + } + } + logState("after $fn") + } + + @VisibleForTesting + fun addToNext(entry: HeadsUpEntry, runnable: Runnable) { + nextMap[entry] = arrayListOf(runnable) + nextList.add(entry) + } + + /** + * Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete + * all Runnables associated with that entry. + */ + fun delete(entry: HeadsUpEntry, runnable: Runnable, label: String) { + if (!NotificationThrottleHun.isEnabled) { + runnable.run() + return + } + val fn = "[$label] => AvalancheController.delete " + getKey(entry) + + if (entry in nextMap) { + log { "$fn => [remove from next]" } + if (entry in nextMap) nextMap.remove(entry) + if (entry in nextList) nextList.remove(entry) + } else if (entry in debugDropSet) { + log { "$fn => [remove from dropset]" } + debugDropSet.remove(entry) + } else if (isShowing(entry)) { + log { "$fn => [remove showing ${getKey(entry)}]" } + runnable.run() + showNext() + } else { + log { "$fn => [removing untracked ${getKey(entry)}]" } + } + logState("after $fn") + } + + /** + * Returns true if given HeadsUpEntry is the last one tracked by AvalancheController. Used by + * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration during active + * avalanche. + */ + fun shortenDuration(entry: HeadsUpEntry): Boolean { + if (!NotificationThrottleHun.isEnabled) { + // Use default display duration, like we always did before AvalancheController existed + return false + } + val showingList: MutableList<HeadsUpEntry> = mutableListOf() + headsUpEntryShowing?.let { showingList.add(it) } + val allEntryList = showingList + nextList + + // Shorten duration if not last entry + return allEntryList.indexOf(entry) != allEntryList.size - 1 + } + + /** + * Return true if entry is waiting to show. + */ + fun isWaiting(key: String): Boolean { + if (!NotificationThrottleHun.isEnabled) { + return false + } + for (entry in nextMap.keys) { + if (entry.mEntry?.key.equals(key)) { + return true + } + } + return false + } + + /** + * Return list of keys for huns waiting + */ + fun getWaitingKeys(): MutableList<String> { + if (!NotificationThrottleHun.isEnabled) { + return mutableListOf() + } + val keyList = mutableListOf<String>() + for (entry in nextMap.keys) { + entry.mEntry?.let { keyList.add(entry.mEntry!!.key) } + } + return keyList + } + + private fun isShowing(entry: HeadsUpEntry): Boolean { + return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key + } + + private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) { + log { "show " + getKey(entry) + " backlog size: " + runnableList.size } + + headsUpEntryShowing = entry + + runnableList.forEach { + if (it in debugRunnableLabelMap) { + log { "run runnable from: ${debugRunnableLabelMap[it]}" } + } + it.run() + } + } + + private fun showNext() { + log { "showNext" } + headsUpEntryShowing = null + + if (nextList.isEmpty()) { + log { "no more to show!" } + return + } + + // Only show first (top priority) entry in next batch + nextList.sort() + headsUpEntryShowing = nextList[0] + headsUpEntryShowingRunnableList = nextMap[headsUpEntryShowing]!! + + // Remove runnable labels for dropped huns + val listToDrop = nextList.subList(1, nextList.size) + if (debug) { + // Clear runnable labels + for (e in listToDrop) { + val runnableList = nextMap[e]!! + for (r in runnableList) { + debugRunnableLabelMap.remove(r) + } + } + debugDropSet.addAll(listToDrop) + } + + clearNext() + showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList) + } + + fun clearNext() { + nextList.clear() + nextMap.clear() + } + + // Methods below are for logging only ========================================================== + + private inline fun log(s: () -> String) { + if (debug) { + Log.d(tag, s()) + } + } + + // TODO(b/315362456) expose as dumpable for bugreports + private fun logState(reason: String) { + log { "state $reason" } + log { "showing: " + getKey(headsUpEntryShowing) } + log { "next list: $nextListStr map: $nextMapStr" } + log { "drop: $dropSetStr" } + } + + private val dropSetStr: String + get() { + val queue = ArrayList<String>() + for (entry in debugDropSet) { + queue.add(getKey(entry)) + } + return java.lang.String.join(" ", queue) + } + + private val nextListStr: String + get() { + val queue = ArrayList<String>() + for (entry in nextList) { + queue.add(getKey(entry)) + } + return java.lang.String.join(" ", queue) + } + + private val nextMapStr: String + get() { + val queue = ArrayList<String>() + for (entry in nextMap.keys) { + queue.add(getKey(entry)) + } + return java.lang.String.join(" ", queue) + } + + fun getKey(entry: HeadsUpEntry?): String { + if (entry == null) { + return "null" + } + if (entry.mEntry == null) { + return entry.toString() + } + return entry.mEntry!!.key + } +} 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 530e49c83802..05cc73edd892 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -45,6 +45,8 @@ import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Stream; /** @@ -68,6 +70,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { private final AccessibilityManagerWrapper mAccessibilityMgr; private final UiEventLogger mUiEventLogger; + private final AvalancheController mAvalancheController; protected final SystemClock mSystemClock; protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>(); @@ -100,13 +103,15 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { SystemClock systemClock, @Main DelayableExecutor executor, AccessibilityManagerWrapper accessibilityManagerWrapper, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + AvalancheController avalancheController) { mLogger = logger; mExecutor = executor; mSystemClock = systemClock; mContext = context; mAccessibilityMgr = accessibilityManagerWrapper; mUiEventLogger = uiEventLogger; + mAvalancheController = avalancheController; Resources resources = context.getResources(); mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); mStickyForSomeTimeAutoDismissTime = resources.getInteger( @@ -157,18 +162,26 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ @Override public void showNotification(@NonNull NotificationEntry entry) { - mLogger.logShowNotification(entry); - - // Add new entry and begin managing it HeadsUpEntry headsUpEntry = createHeadsUpEntry(); + + // Attach NotificationEntry for AvalancheController to log key and + // record mPostTime for AvalancheController sorting 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(); + Runnable runnable = () -> { + // TODO(b/315362456) log outside runnable too + mLogger.logShowNotification(entry); + + // Add new entry and begin managing it + mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry); + onEntryAdded(headsUpEntry); + entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + entry.setIsHeadsUpEntry(true); + + updateNotificationInternal(entry.getKey(), true /* shouldHeadsUpAgain */); + entry.setInterruption(); + }; + mAvalancheController.update(headsUpEntry, runnable, "showNotification"); } /** @@ -181,6 +194,11 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { @Override public boolean removeNotification(@NonNull String key, boolean releaseImmediately) { mLogger.logRemoveNotification(key, releaseImmediately); + + if (mAvalancheController.isWaiting(key)) { + removeEntry(key); + return true; + } HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); if (headsUpEntry == null) { return true; @@ -203,6 +221,14 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) { HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); + Runnable runnable = () -> { + updateNotificationInternal(key, shouldHeadsUpAgain); + }; + mAvalancheController.update(headsUpEntry, runnable, "updateNotification"); + } + + private void updateNotificationInternal(@NonNull String key, boolean shouldHeadsUpAgain) { + HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null); if (headsUpEntry == null) { // the entry was released before this update (i.e by a listener) This can happen @@ -231,12 +257,16 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { for (String key : keysToRemove) { removeEntry(key); } + for (String key : mAvalancheController.getWaitingKeys()) { + removeEntry(key); + } } /** * Returns the entry if it is managed by this manager. * @param key key of notification * @return the entry + * TODO(b/315362456) See if caller needs to check AvalancheController waiting entries */ @Nullable public NotificationEntry getEntry(@NonNull String key) { @@ -251,6 +281,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { @NonNull @Override public Stream<NotificationEntry> getAllEntries() { + // TODO(b/315362456) See if callers need to check AvalancheController return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry); } @@ -267,7 +298,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * @return true if the notification is managed by this manager */ public boolean isHeadsUpEntry(@NonNull String key) { - return mHeadsUpEntryMap.containsKey(key); + return mHeadsUpEntryMap.containsKey(key) || mAvalancheController.isWaiting(key); } /** @@ -331,7 +362,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * Manager-specific logic that should occur when an entry is added. * @param headsUpEntry entry added */ - protected void onEntryAdded(HeadsUpEntry headsUpEntry) { + void onEntryAdded(HeadsUpEntry headsUpEntry) { NotificationEntry entry = headsUpEntry.mEntry; entry.setHeadsUp(true); @@ -349,20 +380,24 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ protected final void removeEntry(@NonNull String key) { HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); - if (headsUpEntry == null) { - return; - } - NotificationEntry entry = headsUpEntry.mEntry; - // If the notification is animating, we will remove it at the end of the animation. - if (entry != null && entry.isExpandAnimationRunning()) { - return; - } - entry.demoteStickyHun(); - mHeadsUpEntryMap.remove(key); - onEntryRemoved(headsUpEntry); - entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - headsUpEntry.reset(); + Runnable runnable = () -> { + if (headsUpEntry == null) { + return; + } + NotificationEntry entry = headsUpEntry.mEntry; + + // If the notification is animating, we will remove it at the end of the animation. + if (entry != null && entry.isExpandAnimationRunning()) { + return; + } + entry.demoteStickyHun(); + mHeadsUpEntryMap.remove(key); + onEntryRemoved(headsUpEntry); + entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + headsUpEntry.reset(); + }; + mAvalancheController.delete(headsUpEntry, runnable, "removeEntry"); } /** @@ -380,7 +415,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } } - protected void updatePinnedMode() { + private void updatePinnedMode() { boolean hasPinnedNotification = hasPinnedNotificationInternal(); if (hasPinnedNotification == mHasPinnedNotification) { return; @@ -416,7 +451,9 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * Snoozes all current Heads Up Notifications. */ public void snooze() { - for (String key : mHeadsUpEntryMap.keySet()) { + List<String> keySet = new ArrayList<>(mHeadsUpEntryMap.keySet()); + keySet.addAll(mAvalancheController.getWaitingKeys()); + for (String key : keySet) { HeadsUpEntry entry = getHeadsUpEntry(key); String packageName = entry.mEntry.getSbn().getPackageName(); String snoozeKey = snoozeKey(packageName, mUser); @@ -432,6 +469,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { @Nullable protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) { + // TODO(b/315362456) See if callers need to check AvalancheController return (HeadsUpEntry) mHeadsUpEntryMap.get(key); } @@ -515,18 +553,22 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ public void unpinAll(boolean userUnPinned) { for (String key : mHeadsUpEntryMap.keySet()) { - HeadsUpEntry entry = getHeadsUpEntry(key); - setEntryPinned(entry, false /* isPinned */); - // maybe it got un sticky - entry.updateEntry(false /* updatePostTime */, "unpinAll"); - - // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay - // on the screen. - if (userUnPinned && entry.mEntry != null) { - if (entry.mEntry.mustStayOnScreen()) { - entry.mEntry.setHeadsUpIsVisible(); + HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); + + Runnable runnable = () -> { + setEntryPinned(headsUpEntry, false /* isPinned */); + // maybe it got un sticky + headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll"); + + // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay + // on the screen. + if (userUnPinned && headsUpEntry.mEntry != null) { + if (headsUpEntry.mEntry.mustStayOnScreen()) { + headsUpEntry.mEntry.setHeadsUpIsVisible(); + } } - } + }; + mAvalancheController.delete(headsUpEntry, runnable, "unpinAll"); } } @@ -606,6 +648,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ @Override public boolean isSticky(String key) { + // TODO(b/315362456) See if callers need to check AvalancheController HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); if (headsUpEntry != null) { return headsUpEntry.isSticky(); @@ -633,9 +676,10 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { /** * This represents a notification and how long it is in a heads up mode. It also manages its - * lifecycle automatically when created. + * lifecycle automatically when created. This class is public because it is exposed by methods + * of AvalancheController that take it as param. */ - protected class HeadsUpEntry implements Comparable<HeadsUpEntry> { + public class HeadsUpEntry implements Comparable<HeadsUpEntry> { public boolean mRemoteInputActive; public boolean mUserActionMayIndirectlyRemove; @@ -672,27 +716,41 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } /** + * An interface that returns the amount of time left this HUN should show. + */ + interface FinishTimeUpdater { + long updateAndGetTimeRemaining(); + } + + /** * Updates an entry's removal time. * @param updatePostTime whether or not to refresh the post time */ public void updateEntry(boolean updatePostTime, @Nullable String reason) { - mLogger.logUpdateEntry(mEntry, updatePostTime, reason); + Runnable runnable = () -> { + mLogger.logUpdateEntry(mEntry, updatePostTime, reason); - final long now = mSystemClock.elapsedRealtime(); - mEarliestRemovalTime = now + mMinimumDisplayTime; + final long now = mSystemClock.elapsedRealtime(); + mEarliestRemovalTime = now + mMinimumDisplayTime; - if (updatePostTime) { - mPostTime = Math.max(mPostTime, now); - } + if (updatePostTime) { + mPostTime = Math.max(mPostTime, now); + } + }; + mAvalancheController.update(this, runnable, "updateEntry (updatePostTime)"); if (isSticky()) { - removeAutoRemovalCallbacks("updateEntry (sticky)"); + cancelAutoRemovalCallbacks("updateEntry (sticky)"); return; } - final long finishTime = calculateFinishTime(); - final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime); - scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)"); + FinishTimeUpdater finishTimeCalculator = () -> { + final long finishTime = calculateFinishTime(); + final long now = mSystemClock.elapsedRealtime(); + final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime); + return timeLeft; + }; + scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)"); } /** @@ -758,12 +816,31 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } } + @Override + public int hashCode() { + if (mEntry == null) return super.hashCode(); + int result = mEntry.getKey().hashCode(); + result = 31 * result; + return result; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || !(o instanceof HeadsUpEntry)) return false; + HeadsUpEntry otherHeadsUpEntry = (HeadsUpEntry) o; + if (mEntry != null && otherHeadsUpEntry.mEntry != null) { + return mEntry.getKey().equals(otherHeadsUpEntry.mEntry.getKey()); + } + return false; + } + public void setExpanded(boolean expanded) { this.mExpanded = expanded; } public void reset() { - removeAutoRemovalCallbacks("reset()"); + cancelAutoRemovalCallbacks("reset()"); mEntry = null; mRemoveRunnable = null; mExpanded = false; @@ -773,37 +850,48 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { /** * Clear any pending removal runnables. */ - public void removeAutoRemovalCallbacks(@Nullable String reason) { - final boolean removed = removeAutoRemovalCallbackInternal(); + public void cancelAutoRemovalCallbacks(@Nullable String reason) { + Runnable runnable = () -> { + final boolean removed = cancelAutoRemovalCallbackInternal(); - if (removed) { - mLogger.logAutoRemoveCanceled(mEntry, reason); - } + if (removed) { + mLogger.logAutoRemoveCanceled(mEntry, reason); + } + }; + mAvalancheController.update(this, runnable, + reason + " removeAutoRemovalCallbacks"); } - public void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) { - if (mRemoveRunnable == null) { - Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set"); - return; - } + public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator, + @NonNull String reason) { - final boolean removed = removeAutoRemovalCallbackInternal(); + Runnable runnable = () -> { + long delayMs = finishTimeCalculator.updateAndGetTimeRemaining(); - if (removed) { - mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason); - } else { - mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason); - } + if (mRemoveRunnable == null) { + Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set"); + return; + } - mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable, - delayMillis); + final boolean deletedExistingRemovalRunnable = cancelAutoRemovalCallbackInternal(); + mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable, + delayMs); + + if (deletedExistingRemovalRunnable) { + mLogger.logAutoRemoveRescheduled(mEntry, delayMs, reason); + } else { + mLogger.logAutoRemoveScheduled(mEntry, delayMs, reason); + } + }; + mAvalancheController.update(this, runnable, + reason + " scheduleAutoRemovalCallback"); } - public boolean removeAutoRemovalCallbackInternal() { + public boolean cancelAutoRemovalCallbackInternal() { final boolean scheduled = (mCancelRemoveRunnable != null); if (scheduled) { - mCancelRemoveRunnable.run(); + mCancelRemoveRunnable.run(); // Delete removal runnable from Executor queue mCancelRemoveRunnable = null; } @@ -815,8 +903,12 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ public void removeAsSoonAsPossible() { if (mRemoveRunnable != null) { - final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime(); - scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible"); + + FinishTimeUpdater finishTimeCalculator = () -> { + final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime(); + return timeLeft; + }; + scheduleAutoRemovalCallback(finishTimeCalculator, "removeAsSoonAsPossible"); } } @@ -834,9 +926,15 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * {@link SystemClock#elapsedRealtime()} */ protected long calculateFinishTime() { - final long duration = getRecommendedHeadsUpTimeoutMs( - isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime); - + int requestedTimeOutMs; + if (isStickyForSomeTime()) { + requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime; + } else if (mAvalancheController.shortenDuration(this)) { + requestedTimeOutMs = 1000; + } else { + requestedTimeOutMs = mAutoDismissTime; + } + final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs); return mPostTime + duration; } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt index cabe831c5964..d10554f9c254 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt @@ -32,7 +32,7 @@ import kotlinx.coroutines.plus private const val LIMIT_BACKGROUND_DISPATCHER_THREADS = true -/** Providers for various SystemIU specific coroutines-related constructs. */ +/** Providers for various SystemUI-specific coroutines-related constructs. */ @Module class SysUICoroutinesModule { @Provides diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index de795a744129..f76957f85009 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -308,20 +308,6 @@ public class MenuViewLayerTest extends SysuiTestCase { } @Test - @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) - public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() { - mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100)); - final PointF beforePosition = mMenuView.getMenuPosition(); - - dispatchShowingImeInsets(); - - final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight(); - assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x); - assertThat(menuBottom).isLessThan(beforePosition.y); - } - - @Test - @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() { mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100)); final PointF beforePosition = mMenuView.getMenuPosition(); @@ -337,19 +323,6 @@ public class MenuViewLayerTest extends SysuiTestCase { } @Test - @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) - public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() { - mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200)); - final PointF beforePosition = mMenuView.getMenuPosition(); - - dispatchHidingImeInsets(); - - assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x); - assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y); - } - - @Test - @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() { mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200)); final PointF beforePosition = mMenuView.getMenuPosition(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 10b86ea9fd31..2b4e9ec4a017 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -20,7 +20,6 @@ import android.content.pm.PackageManager import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricManager -import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT import android.hardware.biometrics.PromptInfo import android.hardware.biometrics.PromptVerticalListContentView import android.hardware.face.FaceSensorPropertiesInternal @@ -386,7 +385,6 @@ open class AuthContainerViewTest : SysuiTestCase() { @Test fun testShowCredentialUI_withDescription() { - mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) val container = initializeFingerprintContainer( authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL ) @@ -397,6 +395,7 @@ open class AuthContainerViewTest : SysuiTestCase() { } @Test + @Ignore("b/302735104") fun testShowCredentialUI_withCustomBp() { val container = initializeFingerprintContainer( authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt index 7b972d3707af..81d4e8302c3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt @@ -17,9 +17,11 @@ package com.android.systemui.biometrics.data.repository import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT import android.hardware.biometrics.PromptInfo import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.shared.model.PromptKind @@ -135,6 +137,8 @@ class PromptRepositoryImplTest : SysuiTestCase() { @Test fun showBpWithoutIconForCredential_withCustomBp() = testScope.runTest { + mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP) + mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) for (case in listOf( PromptKind.Biometric(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 140849b8e257..7db4ca966890 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -35,6 +35,7 @@ import android.view.MotionEvent import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.Flags.FLAG_BP_TALKBACK +import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.UdfpsUtils @@ -1256,6 +1257,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa fun descriptionOverriddenByContentView() = runGenericTest(contentView = promptContentView, description = "test description") { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val contentView by collectLastValue(viewModel.contentView) val description by collectLastValue(viewModel.description) @@ -1267,6 +1269,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa fun descriptionWithoutContentView() = runGenericTest(description = "test description") { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val contentView by collectLastValue(viewModel.contentView) val description by collectLastValue(viewModel.description) @@ -1278,6 +1281,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa fun logoIsNullIfPackageNameNotFound() = runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat(logo).isNull() } @@ -1285,6 +1289,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Test fun defaultLogoIfNoLogoSet() = runGenericTest { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat(logo).isEqualTo(defaultLogoIcon) } @@ -1293,6 +1298,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa fun logoResSetByApp() = runGenericTest(logoRes = logoResFromApp) { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat(logo).isEqualTo(logoFromApp) } @@ -1301,6 +1307,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa fun logoBitmapSetByApp() = runGenericTest(logoBitmap = logoBitmapFromApp) { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp) } @@ -1309,6 +1316,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa fun logoDescriptionIsEmptyIfPackageNameNotFound() = runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logoDescription by collectLastValue(viewModel.logoDescription) assertThat(logoDescription).isEqualTo("") } @@ -1316,6 +1324,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Test fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logoDescription by collectLastValue(viewModel.logoDescription) assertThat(logoDescription).isEqualTo(defaultLogoDescription) } @@ -1324,10 +1333,22 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa fun logoDescriptionSetByApp() = runGenericTest(logoDescription = logoDescriptionFromApp) { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logoDescription by collectLastValue(viewModel.logoDescription) assertThat(logoDescription).isEqualTo(logoDescriptionFromApp) } + @Test + fun iconViewLoaded() = runGenericTest { + val isIconViewLoaded by collectLastValue(viewModel.isIconViewLoaded) + // TODO(b/328677869): Add test for noIcon logic. + assertThat(isIconViewLoaded).isFalse() + + viewModel.setIsIconViewLoaded(true) + + assertThat(isIconViewLoaded).isTrue() + } + /** Asserts that the selected buttons are visible now. */ private suspend fun TestScope.assertButtonsVisible( tryAgain: Boolean = false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java index 3f13033217b3..45d20dcd9d11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java @@ -188,6 +188,20 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test + public void testRegisterSensor_OccludingActivity() { + when(mKeyguardStateController.isOccluded()).thenReturn(true); + + ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor = + ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); + verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture()); + + mFalsingCollector.onScreenTurningOn(); + reset(mProximitySensor); + stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE); + verify(mProximitySensor).register(any(ThresholdSensor.Listener.class)); + } + + @Test public void testPassThroughGesture() { MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 31746a2a46a3..b38d5e326b97 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -99,6 +99,7 @@ import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.ShadeViewController; +import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.CommandQueue; @@ -580,6 +581,7 @@ public class NavigationBarTest extends SysuiTestCase { () -> Optional.of(mCentralSurfaces), mKeyguardStateController, mock(ShadeViewController.class), + mock(PanelExpansionInteractor.class), mock(NotificationRemoteInputManager.class), mock(NotificationShadeDepthController.class), mHandler, diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt index ab90b9be3c1e..25ba09a0ce90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt @@ -25,6 +25,7 @@ import com.android.settingslib.RestrictedLockUtils import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin +import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.BrightnessMirrorController import com.android.systemui.util.mockito.any @@ -66,6 +67,8 @@ class BrightnessSliderControllerTest : SysuiTestCase() { private lateinit var listener: ToggleSlider.Listener @Mock private lateinit var vibratorHelper: VibratorHelper + @Mock + private lateinit var activityStarter: ActivityStarter @Captor private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener> @@ -91,6 +94,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { mFalsingManager, uiEventLogger, SeekableSliderHapticPlugin(vibratorHelper, systemClock), + activityStarter, ) mController.init() mController.setOnChangedListener(listener) @@ -120,7 +124,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { @Test fun testEnforceAdminRelayed() { mController.setEnforcedAdmin(enforcedAdmin) - verify(brightnessSliderView).setEnforcedAdmin(enforcedAdmin) + verify(brightnessSliderView).setAdminBlocker(notNull()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 617b25d97eee..88b239a77433 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -481,8 +481,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // AND status bar doesn't want it whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) .thenReturn(false) - // AND shade is not fully expanded - whenever(notificationPanelViewController.isFullyExpanded()).thenReturn(false) + // AND shade is not fully expanded (mock is false by default) // AND the lock icon does NOT want the touch whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false) // AND quick settings controller DOES want it 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 b548117684ad..e90a3ac8bfdc 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 @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; @@ -157,52 +158,52 @@ public final class MediaCoordinatorTest extends SysuiTestCase { } @Test - @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS) + @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS) public void inflateMediaNotificationIconsMediaEnabled_old() throws InflationException { finishSetupWithMediaFeatureFlagEnabled(true); mListener.onEntryInit(mMediaEntry); mListener.onEntryAdded(mMediaEntry); verify(mIconManager, never()).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); mFilter.shouldFilterOut(mMediaEntry, 0); verify(mIconManager, times(1)).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); mFilter.shouldFilterOut(mMediaEntry, 0); verify(mIconManager, times(1)).createIcons(eq(mMediaEntry)); - verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry)); + verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false)); mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL); mListener.onEntryCleanUp(mMediaEntry); mListener.onEntryInit(mMediaEntry); verify(mIconManager, times(1)).createIcons(eq(mMediaEntry)); - verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry)); + verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false)); mFilter.shouldFilterOut(mMediaEntry, 0); verify(mIconManager, times(2)).createIcons(eq(mMediaEntry)); - verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry)); + verify(mIconManager, times(1)).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false)); } @Test - @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS) + @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_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)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); clearInvocations(mIconManager); mFilter.shouldFilterOut(mMediaEntry, 0); verify(mIconManager, never()).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); mListener.onEntryUpdated(mMediaEntry); verify(mIconManager, never()).createIcons(eq(mMediaEntry)); - verify(mIconManager).updateIcons(eq(mMediaEntry)); + verify(mIconManager).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false)); mListener.onEntryRemoved(mMediaEntry, NotificationListenerService.REASON_CANCEL); mListener.onEntryCleanUp(mMediaEntry); @@ -211,40 +212,40 @@ public final class MediaCoordinatorTest extends SysuiTestCase { mListener.onEntryInit(mMediaEntry); mListener.onEntryAdded(mMediaEntry); verify(mIconManager).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); } @Test - @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS) + @DisableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS) public void inflationException_old() throws InflationException { finishSetupWithMediaFeatureFlagEnabled(true); mListener.onEntryInit(mMediaEntry); mListener.onEntryAdded(mMediaEntry); verify(mIconManager, never()).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); doThrow(InflationException.class).when(mIconManager).createIcons(eq(mMediaEntry)); mFilter.shouldFilterOut(mMediaEntry, 0); verify(mIconManager, times(1)).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); mFilter.shouldFilterOut(mMediaEntry, 0); verify(mIconManager, times(1)).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), /* usingCache = */ eq(false)); mListener.onEntryUpdated(mMediaEntry); verify(mIconManager, times(1)).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); doNothing().when(mIconManager).createIcons(eq(mMediaEntry)); mFilter.shouldFilterOut(mMediaEntry, 0); verify(mIconManager, times(2)).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); } @Test - @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_MEDIA_ICONS) + @EnableFlags(Flags.FLAG_NOTIFICATIONS_BACKGROUND_ICONS) public void inflationException_new() throws InflationException { finishSetupWithMediaFeatureFlagEnabled(true); @@ -253,19 +254,19 @@ public final class MediaCoordinatorTest extends SysuiTestCase { mListener.onEntryInit(mMediaEntry); mListener.onEntryAdded(mMediaEntry); verify(mIconManager).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); clearInvocations(mIconManager); mListener.onEntryUpdated(mMediaEntry); verify(mIconManager).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); clearInvocations(mIconManager); doNothing().when(mIconManager).createIcons(eq(mMediaEntry)); mListener.onEntryUpdated(mMediaEntry); verify(mIconManager).createIcons(eq(mMediaEntry)); - verify(mIconManager, never()).updateIcons(eq(mMediaEntry)); + verify(mIconManager, never()).updateIcons(eq(mMediaEntry), anyBoolean()); } private void finishSetupWithMediaFeatureFlagEnabled(boolean mediaFeatureFlagEnabled) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt index a12806b9cc99..4ac9dc2be161 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.statusbar.notification.icon import android.app.ActivityManager @@ -38,6 +40,10 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -69,6 +75,11 @@ class IconManagerTest : SysuiTestCase() { @Mock private lateinit var notifCollection: CommonNotifCollection @Mock private lateinit var launcherApps: LauncherApps + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + private val mainContext = testScope.coroutineContext + private val bgContext = testScope.backgroundScope.coroutineContext + private val iconBuilder = IconBuilder(context) private lateinit var iconManager: IconManager @@ -85,7 +96,15 @@ class IconManagerTest : SysuiTestCase() { `when`(shortcut.icon).thenReturn(shortcutIc) `when`(launcherApps.getShortcutIcon(shortcut)).thenReturn(shortcutIc) - iconManager = IconManager(notifCollection, launcherApps, iconBuilder) + iconManager = + IconManager( + notifCollection, + launcherApps, + iconBuilder, + testScope, + bgContext, + mainContext, + ) } @Test @@ -94,6 +113,7 @@ class IconManagerTest : SysuiTestCase() { notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) } + testScope.runCurrent() assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc) } @@ -103,6 +123,7 @@ class IconManagerTest : SysuiTestCase() { notificationEntry(hasShortcut = false, hasMessageSenderIcon = true, hasLargeIcon = true) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) } + testScope.runCurrent() assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(messageIc) } @@ -116,6 +137,7 @@ class IconManagerTest : SysuiTestCase() { ) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) } + testScope.runCurrent() assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(largeIc) } @@ -129,6 +151,7 @@ class IconManagerTest : SysuiTestCase() { ) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) } + testScope.runCurrent() assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc) } @@ -143,6 +166,7 @@ class IconManagerTest : SysuiTestCase() { ) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) } + testScope.runCurrent() assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc) } @@ -161,6 +185,7 @@ class IconManagerTest : SysuiTestCase() { entry?.setSensitive(true, true) entry?.channel?.isImportantConversation = true entry?.let { iconManager.createIcons(it) } + testScope.runCurrent() assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc) assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc) assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc) @@ -175,6 +200,7 @@ class IconManagerTest : SysuiTestCase() { entry?.let { iconManager.createIcons(it) } // Updating the icons after creation shouldn't break anything entry?.let { iconManager.updateIcons(it) } + testScope.runCurrent() assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc) assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc) assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc) @@ -187,9 +213,11 @@ class IconManagerTest : SysuiTestCase() { entry?.channel?.isImportantConversation = true entry?.setSensitive(true, true) entry?.let { iconManager.createIcons(it) } + testScope.runCurrent() assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc) entry?.setSensitive(false, false) entry?.let { iconManager.updateIcons(it) } + testScope.runCurrent() assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(shortcutIc) } 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 718f99841292..3b78b7e492f5 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 @@ -53,6 +53,7 @@ import androidx.annotation.NonNull; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; +import com.android.keyguard.TestScopeProvider; import com.android.systemui.TestableDependency; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.flags.FakeFeatureFlags; @@ -102,6 +103,9 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import kotlin.coroutines.CoroutineContext; +import kotlinx.coroutines.test.TestScope; + /** * A helper class to create {@link ExpandableNotificationRow} (for both individual and group * notifications). @@ -140,6 +144,10 @@ public class NotificationTestHelper { private final FakeFeatureFlags mFeatureFlags; private final SystemClock mSystemClock; private final RowInflaterTaskLogger mRowInflaterTaskLogger; + private final TestScope mTestScope = TestScopeProvider.getTestScope(); + private final CoroutineContext mBgCoroutineContext = + mTestScope.getBackgroundScope().getCoroutineContext(); + private final CoroutineContext mMainCoroutineContext = mTestScope.getCoroutineContext(); public NotificationTestHelper( Context context, @@ -169,7 +177,10 @@ public class NotificationTestHelper { mIconManager = new IconManager( mock(CommonNotifCollection.class), mock(LauncherApps.class), - new IconBuilder(mContext)); + new IconBuilder(mContext), + mTestScope, + mBgCoroutineContext, + mMainCoroutineContext); NotificationContentInflater contentBinder = new NotificationContentInflater( mock(NotifRemoteViewCache.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index b0404a055a68..a8c5fc357c7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -50,6 +50,7 @@ import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeViewController; +import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -296,6 +297,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mStatusBarNotificationPresenter = new StatusBarNotificationPresenter( mContext, shadeViewController, + mock(PanelExpansionInteractor.class), mock(QuickSettingsController.class), mock(HeadsUpManager.class), notificationShadeWindowView, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java index 342855357fd2..80016046e9cf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java @@ -30,6 +30,7 @@ import java.io.IOException; * * To use: * - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase + * - Use `atest -d` to prevent files being cleaned up * - Watch the logcat with tag MEMORY to see the path to the .ahprof file * - adb pull /path/to/something.ahprof * - Download ahat from https://sites.google.com/corp/google.com/ahat/home diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt index c3af437dafdf..2e2cf9a61a8c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt @@ -78,6 +78,7 @@ class FakePromptRepository : PromptRepository { val hasCredentialViewShown = kind.value !is PromptKind.Biometric val showBpForCredential = Flags.customBiometricPrompt() && + com.android.systemui.Flags.constraintBp() && !Utils.isBiometricAllowed(promptInfo) && Utils.isDeviceCredentialAllowed(promptInfo) && promptInfo.contentView != null diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt index 0b1385865d63..b34681ac0bdc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt @@ -2,6 +2,7 @@ package com.android.systemui.kosmos import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.Kosmos.Fixture +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -9,3 +10,7 @@ var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() } var Kosmos.testScope by Fixture { TestScope(testDispatcher) } var Kosmos.applicationCoroutineScope by Fixture { testScope.backgroundScope } var Kosmos.testCase: SysuiTestCase by Fixture() +var Kosmos.backgroundCoroutineContext: CoroutineContext by Fixture { + testScope.backgroundScope.coroutineContext +} +var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt index 2a4dd3a43b88..09c8f87c99cc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorKosmos.kt @@ -23,6 +23,8 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor val Kosmos.panelExpansionInteractor by Fixture { panelExpansionInteractorImpl } val Kosmos.panelExpansionInteractorImpl by Fixture { PanelExpansionInteractorImpl( - sceneInteractor = sceneInteractor, + sceneInteractor, + shadeInteractor, + shadeAnimationInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt index d2dd200faa07..6d24e2aec089 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade.domain.interactor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.data.repository.shadeAnimationRepository @@ -24,5 +25,9 @@ var Kosmos.shadeAnimationInteractor: ShadeAnimationInteractor by Kosmos.Fixture { ShadeAnimationInteractorEmptyImpl(shadeAnimationRepository) } var Kosmos.shadeAnimationInteractorSceneContainerImpl: ShadeAnimationInteractorSceneContainerImpl by Kosmos.Fixture { - ShadeAnimationInteractorSceneContainerImpl(shadeAnimationRepository, sceneInteractor) + ShadeAnimationInteractorSceneContainerImpl( + testScope.backgroundScope, + shadeAnimationRepository, + sceneInteractor + ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt index d3a8e0c5970c..0950f04aeea8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt @@ -18,7 +18,19 @@ package com.android.systemui.statusbar.notification.icon import android.content.pm.launcherApps import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.backgroundCoroutineContext +import com.android.systemui.kosmos.mainCoroutineContext import com.android.systemui.statusbar.notification.collection.notifcollection.commonNotifCollection val Kosmos.iconManager by - Kosmos.Fixture { IconManager(commonNotifCollection, launcherApps, iconBuilder) } + Kosmos.Fixture { + IconManager( + commonNotifCollection, + launcherApps, + iconBuilder, + applicationCoroutineScope, + backgroundCoroutineContext, + mainCoroutineContext, + ) + } diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index 323917564bf2..004f37c16757 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -528,7 +528,7 @@ public class CameraExtensionsProxyService extends Service { */ public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension( int extensionType) { - if (Flags.concertMode()) { + if (Flags.concertModeApi()) { if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) { // Basic extensions are deprecated starting with extension version 1.5 return new Pair<>(new PreviewExtenderImpl() { @@ -713,7 +713,7 @@ public class CameraExtensionsProxyService extends Service { * @hide */ public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) { - if (Flags.concertMode()) { + if (Flags.concertModeApi()) { if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) { if (EFV_SUPPORTED) { return new EyesFreeVideographyAdvancedExtenderImpl(); diff --git a/ravenwood/README.md b/ravenwood/README.md index 9c4fda7a50a6..8cafb433736f 100644 --- a/ravenwood/README.md +++ b/ravenwood/README.md @@ -1,9 +1,11 @@ # Ravenwood -Ravenwood is an officially-supported lightweight unit testing environment for Android platform code that runs on the host. +Ravenwood is a lightweight unit testing environment for Android platform code that runs on the host. Ravenwood’s focus on Android platform use-cases, improved maintainability, and device consistency distinguishes it from Robolectric, which remains a popular choice for app testing. +> **Note:** Active development of Ravenwood has been paused as of March 2024. Existing Ravenwood tests will continue running, but support has moved to a self-service model. + ## Background Executing tests on a typical Android device has substantial overhead, such as flashing the build, waiting for the boot to complete, and retrying tests that fail due to general flakiness. diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 1749ee333b8e..16fe0077d6ff 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -4025,8 +4025,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } } - throw new IllegalArgumentException( - providerComponent + " is not a valid AppWidget provider"); + // Either the provider does not exist or the caller does not have permission to access its + // previews. + return null; } @Override diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 130a7333959d..1334a95e244d 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -113,6 +113,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; +import com.android.modules.expresslog.Histogram; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; @@ -284,6 +285,11 @@ public class AccountManagerService private static AtomicReference<AccountManagerService> sThis = new AtomicReference<>(); private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{}; + private static Histogram sResponseLatency = new Histogram( + "app.value_high_authenticator_response_latency", + new Histogram.ScaledRangeOptions(20, 10000, 10000, 1.5f) + ); + /** * This should only be called by system code. One should only call this after the service * has started. @@ -4937,6 +4943,9 @@ public class AccountManagerService protected boolean mCanStartAccountManagerActivity = false; protected final UserAccounts mAccounts; + private int mAuthenticatorUid; + private long mBindingStartTime; + public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType, boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName, boolean authDetailsRequired) { @@ -4974,6 +4983,10 @@ public class AccountManagerService } IAccountManagerResponse getResponseAndClose() { + if (mAuthenticatorUid != 0 && mBindingStartTime > 0) { + sResponseLatency.logSampleWithUid(mAuthenticatorUid, + SystemClock.uptimeMillis() - mBindingStartTime); + } if (mResponse == null) { close(); return null; @@ -5353,7 +5366,8 @@ public class AccountManagerService mContext.unbindService(this); return false; } - + mAuthenticatorUid = authenticatorInfo.uid; + mBindingStartTime = SystemClock.uptimeMillis(); return true; } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 52988460606c..0012b3d86552 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -204,6 +204,7 @@ import android.os.TransactionTooLargeException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService; import android.service.voice.HotwordDetectionService; import android.service.voice.VisualQueryDetectionService; import android.service.wearable.WearableSensingService; @@ -4518,13 +4519,14 @@ public final class ActiveServices { } // TODO(b/265746493): Special case for HotwordDetectionService, - // VisualQueryDetectionService and WearableSensingService. + // VisualQueryDetectionService, WearableSensingService and OnDeviceSandboxedInferenceService // Need a cleaner way to append this seInfo. private String generateAdditionalSeInfoFromService(Intent service) { if (service != null && service.getAction() != null && (service.getAction().equals(HotwordDetectionService.SERVICE_INTERFACE) || service.getAction().equals(VisualQueryDetectionService.SERVICE_INTERFACE) - || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE))) { + || service.getAction().equals(WearableSensingService.SERVICE_INTERFACE) + || service.getAction().equals(OnDeviceSandboxedInferenceService.SERVICE_INTERFACE))) { return ":isolatedComputeApp"; } return ""; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5e6ff55f4e94..447dfd95e034 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9011,7 +9011,7 @@ public class ActivityManagerService extends IActivityManager.Stub // cleaning up the old proxies. VMRuntime.getRuntime().requestConcurrentGC(); } - }, BackgroundThread.getHandler()); + }, mHandler); t.traceEnd(); // setBinderProxies t.traceEnd(); // ActivityManagerStartApps diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 5521381e8908..0a6e9d3198cb 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -286,9 +286,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // when the flag is fused on. private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8; - // TODO: Use the trunk stable flag. - private static final boolean DEFER_FROZEN_OUTGOING_BCASTS = false; - private void enqueueUpdateRunningList() { mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST); mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST); @@ -766,7 +763,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // TODO: Apply delivery group policies and FLAG_REPLACE_PENDING to collapse the // outgoing broadcasts. // TODO: Add traces/logs for the enqueueing outgoing broadcasts logic. - if (DEFER_FROZEN_OUTGOING_BCASTS && isProcessFreezable(r.callerApp)) { + if (Flags.deferOutgoingBcasts() && isProcessFreezable(r.callerApp)) { final BroadcastProcessQueue queue = getOrCreateProcessQueue( r.callerApp.processName, r.callerApp.uid); if (queue.getOutgoingBroadcastCount() >= mConstants.MAX_FROZEN_OUTGOING_BROADCASTS) { diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 48bf9f4967bc..e915688b95b1 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -791,14 +791,12 @@ public class AuthService extends SystemService { private void registerAuthenticators() { BiometricHandlerProvider handlerProvider = mInjector.getBiometricHandlerProvider(); - handlerProvider.getFingerprintHandler().post(() -> - registerFingerprintSensors(mInjector.getFingerprintAidlInstances(), - mInjector.getFingerprintConfiguration(getContext()), getContext(), - mInjector.getFingerprintService())); - handlerProvider.getFaceHandler().post(() -> - registerFaceSensors(mInjector.getFaceAidlInstances(), - mInjector.getFaceConfiguration(getContext()), getContext(), - mInjector.getFaceService())); + registerFingerprintSensors(mInjector.getFingerprintAidlInstances(), + mInjector.getFingerprintConfiguration(getContext()), getContext(), + mInjector.getFingerprintService(), handlerProvider); + registerFaceSensors(mInjector.getFaceAidlInstances(), + mInjector.getFaceConfiguration(getContext()), getContext(), + mInjector.getFaceService(), handlerProvider); registerIrisSensors(mInjector.getIrisConfiguration(getContext())); } @@ -854,30 +852,38 @@ public class AuthService extends SystemService { */ private static void registerFaceSensors(final String[] faceAidlInstances, final String[] hidlConfigStrings, final Context context, - final IFaceService faceService) { - final FaceSensorConfigurations mFaceSensorConfigurations = - new FaceSensorConfigurations(hidlConfigStrings != null - && hidlConfigStrings.length > 0); - - if (hidlConfigStrings != null && hidlConfigStrings.length > 0) { - mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context); + final IFaceService faceService, final BiometricHandlerProvider handlerProvider) { + if ((hidlConfigStrings == null || hidlConfigStrings.length == 0) + && (faceAidlInstances == null || faceAidlInstances.length == 0)) { + Slog.d(TAG, "No face sensors."); + return; } - if (faceAidlInstances != null && faceAidlInstances.length > 0) { - mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances, - name -> IFace.Stub.asInterface(Binder.allowBlocking( - ServiceManager.waitForDeclaredService(name)))); - } + handlerProvider.getFaceHandler().post(() -> { + final FaceSensorConfigurations mFaceSensorConfigurations = + new FaceSensorConfigurations(hidlConfigStrings != null + && hidlConfigStrings.length > 0); - if (faceService != null) { - try { - faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations); - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException when registering face authenticators", e); + if (hidlConfigStrings != null && hidlConfigStrings.length > 0) { + mFaceSensorConfigurations.addHidlConfigs(hidlConfigStrings, context); } - } else if (mFaceSensorConfigurations.hasSensorConfigurations()) { - Slog.e(TAG, "Face configuration exists, but FaceService is null."); - } + + if (faceAidlInstances != null && faceAidlInstances.length > 0) { + mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances, + name -> IFace.Stub.asInterface(Binder.allowBlocking( + ServiceManager.waitForDeclaredService(name)))); + } + + if (faceService != null) { + try { + faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when registering face authenticators", e); + } + } else if (mFaceSensorConfigurations.hasSensorConfigurations()) { + Slog.e(TAG, "Face configuration exists, but FaceService is null."); + } + }); } /** @@ -885,30 +891,40 @@ public class AuthService extends SystemService { */ private static void registerFingerprintSensors(final String[] fingerprintAidlInstances, final String[] hidlConfigStrings, final Context context, - final IFingerprintService fingerprintService) { - final FingerprintSensorConfigurations mFingerprintSensorConfigurations = - new FingerprintSensorConfigurations(!(hidlConfigStrings != null - && hidlConfigStrings.length > 0)); - - if (hidlConfigStrings != null && hidlConfigStrings.length > 0) { - mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context); + final IFingerprintService fingerprintService, + final BiometricHandlerProvider handlerProvider) { + if ((hidlConfigStrings == null || hidlConfigStrings.length == 0) + && (fingerprintAidlInstances == null || fingerprintAidlInstances.length == 0)) { + Slog.d(TAG, "No fingerprint sensors."); + return; } - if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) { - mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances, - name -> IFingerprint.Stub.asInterface(Binder.allowBlocking( - ServiceManager.waitForDeclaredService(name)))); - } + handlerProvider.getFingerprintHandler().post(() -> { + final FingerprintSensorConfigurations mFingerprintSensorConfigurations = + new FingerprintSensorConfigurations(!(hidlConfigStrings != null + && hidlConfigStrings.length > 0)); - if (fingerprintService != null) { - try { - fingerprintService.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations); - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e); + if (hidlConfigStrings != null && hidlConfigStrings.length > 0) { + mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, context); } - } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) { - Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null."); - } + + if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) { + mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances, + name -> IFingerprint.Stub.asInterface(Binder.allowBlocking( + ServiceManager.waitForDeclaredService(name)))); + } + + if (fingerprintService != null) { + try { + fingerprintService.registerAuthenticatorsLegacy( + mFingerprintSensorConfigurations); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e); + } + } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) { + Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null."); + } + }); } /** diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java index a923daaa5a51..e57886127e63 100644 --- a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java +++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java @@ -16,6 +16,9 @@ package com.android.server.biometrics; +import static android.os.Process.THREAD_PRIORITY_DEFAULT; +import static android.os.Process.THREAD_PRIORITY_DISPLAY; + import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -27,9 +30,9 @@ public class BiometricHandlerProvider { private static final BiometricHandlerProvider sBiometricHandlerProvider = new BiometricHandlerProvider(); - private final Handler mBiometricsCallbackHandler; - private final Handler mFingerprintHandler; - private final Handler mFaceHandler; + private Handler mBiometricsCallbackHandler; + private Handler mFingerprintHandler; + private Handler mFaceHandler; /** * @return an instance of {@link BiometricHandlerProvider} which contains the three @@ -39,16 +42,16 @@ public class BiometricHandlerProvider { return sBiometricHandlerProvider; } - private BiometricHandlerProvider() { - mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler"); - mFingerprintHandler = getNewHandler("FingerprintHandler"); - mFaceHandler = getNewHandler("FaceHandler"); - } + private BiometricHandlerProvider() {} /** * @return the handler to process all biometric callback operations */ public synchronized Handler getBiometricCallbackHandler() { + if (mBiometricsCallbackHandler == null) { + mBiometricsCallbackHandler = getNewHandler("BiometricsCallbackHandler", + THREAD_PRIORITY_DISPLAY); + } return mBiometricsCallbackHandler; } @@ -56,6 +59,9 @@ public class BiometricHandlerProvider { * @return the handler to process all face related biometric operations */ public synchronized Handler getFaceHandler() { + if (mFaceHandler == null) { + mFaceHandler = getNewHandler("FaceHandler", THREAD_PRIORITY_DEFAULT); + } return mFaceHandler; } @@ -63,12 +69,15 @@ public class BiometricHandlerProvider { * @return the handler to process all fingerprint related biometric operations */ public synchronized Handler getFingerprintHandler() { + if (mFingerprintHandler == null) { + mFingerprintHandler = getNewHandler("FingerprintHandler", THREAD_PRIORITY_DEFAULT); + } return mFingerprintHandler; } - private Handler getNewHandler(String tag) { + private Handler getNewHandler(String tag, int priority) { if (Flags.deHidl()) { - HandlerThread handlerThread = new HandlerThread(tag); + HandlerThread handlerThread = new HandlerThread(tag, priority); handlerThread.start(); return new Handler(handlerThread.getLooper()); } diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java new file mode 100644 index 000000000000..133c79f81bb5 --- /dev/null +++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryHelper.java @@ -0,0 +1,129 @@ +/* + * 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.server.crashrecovery; + +import android.annotation.AnyThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.VersionedPackage; +import android.net.ConnectivityModuleConnector; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.server.PackageWatchdog; +import com.android.server.pm.ApexManager; + +import java.util.Collections; +import java.util.List; + +/** + * Provides helper methods for the CrashRecovery APEX + * + * @hide + */ +public final class CrashRecoveryHelper { + private static final String TAG = "CrashRecoveryHelper"; + + private final ApexManager mApexManager; + private final Context mContext; + private final ConnectivityModuleConnector mConnectivityModuleConnector; + + + /** @hide */ + public CrashRecoveryHelper(@NonNull Context context) { + mContext = context; + mApexManager = ApexManager.getInstance(); + mConnectivityModuleConnector = ConnectivityModuleConnector.getInstance(); + } + + /** + * Returns true if the package name is the name of a module. + * If the package is an APK inside an APEX then it will use the parent's APEX package name + * do determine if it is a module or not. + * @hide + */ + @AnyThread + public boolean isModule(@NonNull String packageName) { + String apexPackageName = + mApexManager.getActiveApexPackageNameContainingPackage(packageName); + if (apexPackageName != null) { + packageName = apexPackageName; + } + + PackageManager pm = mContext.getPackageManager(); + try { + return pm.getModuleInfo(packageName, 0) != null; + } catch (PackageManager.NameNotFoundException ignore) { + return false; + } + } + + /** + * Register health listeners for explicit package failures. + * Currently only registering for Connectivity Module health. + * @hide + */ + public void registerConnectivityModuleHealthListener(@NonNull int failureReason) { + // register listener for ConnectivityModule + mConnectivityModuleConnector.registerHealthListener( + packageName -> { + final VersionedPackage pkg = getVersionedPackage(packageName); + if (pkg == null) { + Slog.wtf(TAG, "NetworkStack failed but could not find its package"); + return; + } + final List<VersionedPackage> pkgList = Collections.singletonList(pkg); + PackageWatchdog.getInstance(mContext).onPackageFailure(pkgList, failureReason); + }); + } + + @Nullable + private VersionedPackage getVersionedPackage(String packageName) { + final PackageManager pm = mContext.getPackageManager(); + if (pm == null || TextUtils.isEmpty(packageName)) { + return null; + } + try { + final long versionCode = getPackageInfo(packageName).getLongVersionCode(); + return new VersionedPackage(packageName, versionCode); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } + + /** + * Gets PackageInfo for the given package. Matches any user and apex. + * + * @throws PackageManager.NameNotFoundException if no such package is installed. + */ + private PackageInfo getPackageInfo(String packageName) + throws PackageManager.NameNotFoundException { + PackageManager pm = mContext.getPackageManager(); + try { + // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX + // flag, so make two separate attempts to get the package info. + // We don't need both flags at the same time because we assume + // apex files are always installed for all users. + return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER); + } catch (PackageManager.NameNotFoundException e) { + return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX); + } + } +} diff --git a/services/core/java/com/android/server/crashrecovery/OWNERS b/services/core/java/com/android/server/crashrecovery/OWNERS new file mode 100644 index 000000000000..daa02111f71f --- /dev/null +++ b/services/core/java/com/android/server/crashrecovery/OWNERS @@ -0,0 +1,3 @@ +ancr@google.com +harshitmahajan@google.com +robertogil@google.com diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java index c7b60da2fc51..dd6433d98553 100644 --- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java +++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java @@ -19,11 +19,13 @@ package com.android.server.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.content.Context; import android.content.pm.UserInfo; import android.os.Handler; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.inputmethod.DirectBootAwareness; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; @@ -67,7 +69,7 @@ final class AdditionalSubtypeMapRepository { AdditionalSubtypeUtils.save(map, inputMethodMap, userId); } - static void initialize(@NonNull Handler handler) { + static void initialize(@NonNull Handler handler, @NonNull Context context) { final UserManagerInternal userManagerInternal = LocalServices.getService(UserManagerInternal.class); handler.post(() -> { @@ -79,8 +81,16 @@ final class AdditionalSubtypeMapRepository { handler.post(() -> { synchronized (ImfLock.class) { if (!sPerUserMap.contains(userId)) { - sPerUserMap.put(userId, - AdditionalSubtypeUtils.load(userId)); + final AdditionalSubtypeMap additionalSubtypeMap = + AdditionalSubtypeUtils.load(userId); + sPerUserMap.put(userId, additionalSubtypeMap); + final InputMethodSettings settings = + InputMethodManagerService + .queryInputMethodServicesInternal(context, + userId, + additionalSubtypeMap, + DirectBootAwareness.AUTO); + InputMethodSettingsRepository.put(userId, settings); } } }); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index fef56610b406..a0e910dd7e9c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -284,6 +284,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final Context mContext; final Resources mRes; private final Handler mHandler; + + /** + * TODO(b/329163064): Remove this field. + */ @NonNull @MultiUserUnawareField private InputMethodSettings mSettings; @@ -867,7 +871,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!mSystemReady) { return; } - buildInputMethodListLocked(true); + for (int userId : mUserManagerInternal.getUserIds()) { + final InputMethodSettings settings = queryInputMethodServicesInternal( + mContext, + userId, + AdditionalSubtypeMapRepository.get(userId), + DirectBootAwareness.AUTO); + InputMethodSettingsRepository.put(userId, settings); + if (userId == mSettings.getUserId()) { + mSettings = settings; + } + } + postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */); // If the locale is changed, needs to reset the default ime resetDefaultImeLocked(mContext); updateFromSettingsLocked(true); @@ -1063,13 +1078,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final boolean isCurrentUser = (userId == mSettings.getUserId()); final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); - final InputMethodSettings settings; - if (isCurrentUser) { - settings = mSettings; - } else { - settings = queryInputMethodServicesInternal(mContext, userId, - additionalSubtypeMap, DirectBootAwareness.AUTO); - } + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); InputMethodInfo curIm = null; String curInputMethodId = settings.getSelectedInputMethod(); @@ -1113,13 +1122,20 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap, settings.getMethodMap()); } - - if (!isCurrentUser - || !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) { + if (isCurrentUser + && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) { return; } - buildInputMethodListLocked(false /* resetDefaultEnabledIme */); + final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, + userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO); + InputMethodSettingsRepository.put(userId, newSettings); + if (!isCurrentUser) { + return; + } + mSettings = queryInputMethodServicesInternal(mContext, userId, + newAdditionalSubtypeMap, DirectBootAwareness.AUTO); + postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); boolean changed = false; @@ -1271,17 +1287,20 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub void onUnlockUser(@UserIdInt int userId) { synchronized (ImfLock.class) { - final int currentUserId = mSettings.getUserId(); if (DEBUG) { - Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId); + Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + + mSettings.getUserId()); } - if (userId != currentUserId) { + if (!mSystemReady) { return; } - mSettings = InputMethodSettings.createEmptyMap(userId); - if (mSystemReady) { + final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, + userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); + InputMethodSettingsRepository.put(userId, newSettings); + if (mSettings.getUserId() == userId) { + mSettings = newSettings; // We need to rebuild IMEs. - buildInputMethodListLocked(false /* resetDefaultEnabledIme */); + postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); updateInputMethodsFromSettingsLocked(true /* enabledChanged */); } } @@ -1347,12 +1366,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mShowOngoingImeSwitcherForPhones = false; - AdditionalSubtypeMapRepository.initialize(mHandler); + // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked + InputMethodSettingsRepository.initialize(mHandler, mContext); + AdditionalSubtypeMapRepository.initialize(mHandler, mContext); final int userId = mActivityManagerInternal.getCurrentUserId(); - // mSettings should be created before buildInputMethodListLocked - mSettings = InputMethodSettings.createEmptyMap(userId); + mSettings = InputMethodSettingsRepository.get(userId); mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(context, @@ -1515,7 +1535,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // The mSystemReady flag is set during boot phase, // and user switch would not happen at that time. resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER); - buildInputMethodListLocked(initialUserSwitch); + + final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, + newUserId, AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO); + InputMethodSettingsRepository.put(newUserId, newSettings); + mSettings = newSettings; + postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */); if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) { // This is the first time of the user switch and // set the current ime to the proper one. @@ -1596,7 +1621,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final String defaultImiId = mSettings.getSelectedInputMethod(); final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); - buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */); + final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, + currentUserId, AdditionalSubtypeMapRepository.get(currentUserId), + DirectBootAwareness.AUTO); + InputMethodSettingsRepository.put(currentUserId, newSettings); + mSettings = newSettings; + postInputMethodSettingUpdatedLocked( + !imeSelectedOnBoot /* resetDefaultEnabledIme */); updateFromSettingsLocked(true); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, currentUserId), @@ -1703,9 +1734,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub && (!connectionless || mBindingController.supportsConnectionlessStylusHandwriting()); } - //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList. - //TODO(b/210039666): use cache. - final InputMethodSettings settings = queryMethodMapForUserLocked(userId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final InputMethodInfo imi = settings.getMethodMap().get( settings.getSelectedInputMethod()); return imi != null && imi.supportsStylusHandwriting() @@ -1729,9 +1758,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId, @DirectBootAwareness int directBootAwareness, int callingUid) { final InputMethodSettings settings; - if (userId == mSettings.getUserId() - && directBootAwareness == DirectBootAwareness.AUTO) { - settings = mSettings; + if (directBootAwareness == DirectBootAwareness.AUTO) { + settings = InputMethodSettingsRepository.get(userId); } else { final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); @@ -1755,7 +1783,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub methodList = mSettings.getEnabledInputMethodList(); settings = mSettings; } else { - settings = queryMethodMapForUserLocked(userId); + settings = InputMethodSettingsRepository.get(userId); methodList = settings.getEnabledInputMethodList(); } // filter caller's access to input methods @@ -1815,22 +1843,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) { - if (userId == mSettings.getUserId()) { - final InputMethodInfo imi; - String selectedMethodId = getSelectedMethodIdLocked(); - if (imiId == null && selectedMethodId != null) { - imi = mSettings.getMethodMap().get(selectedMethodId); - } else { - imi = mSettings.getMethodMap().get(imiId); - } - if (imi == null || !canCallerAccessInputMethod( - imi.getPackageName(), callingUid, userId, mSettings)) { - return Collections.emptyList(); - } - return mSettings.getEnabledInputMethodSubtypeList( - imi, allowsImplicitlyEnabledSubtypes); - } - final InputMethodSettings settings = queryMethodMapForUserLocked(userId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final InputMethodInfo imi = settings.getMethodMap().get(imiId); if (imi == null) { return Collections.emptyList(); @@ -3996,8 +4009,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return mSettings.getLastInputMethodSubtype(); } - final InputMethodSettings settings = queryMethodMapForUserLocked(userId); - return settings.getLastInputMethodSubtype(); + return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype(); } } @@ -4029,19 +4041,21 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); final boolean isCurrentUser = (mSettings.getUserId() == userId); - final InputMethodSettings settings = isCurrentUser - ? mSettings - : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - DirectBootAwareness.AUTO); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap( imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid); if (additionalSubtypeMap != newAdditionalSubtypeMap) { AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap, settings.getMethodMap()); + final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, + userId, AdditionalSubtypeMapRepository.get(userId), + DirectBootAwareness.AUTO); + InputMethodSettingsRepository.put(userId, newSettings); if (isCurrentUser) { final long ident = Binder.clearCallingIdentity(); try { - buildInputMethodListLocked(false /* resetDefaultEnabledIme */); + mSettings = newSettings; + postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); } finally { Binder.restoreCallingIdentity(ident); } @@ -4071,8 +4085,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub try { synchronized (ImfLock.class) { final boolean currentUser = (mSettings.getUserId() == userId); - final InputMethodSettings settings = currentUser - ? mSettings : queryMethodMapForUserLocked(userId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) { return; } @@ -4969,7 +4982,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - void buildInputMethodListLocked(boolean resetDefaultEnabledIme) { + void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme) { if (DEBUG) { Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme + " \n ------ caller=" + Debug.getCallers(10)); @@ -4981,10 +4994,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mMethodMapUpdateCount++; mMyPackageMonitor.clearKnownImePackageNamesLocked(); - mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(), - AdditionalSubtypeMapRepository.get(mSettings.getUserId()), - DirectBootAwareness.AUTO); - // Construct the set of possible IME packages for onPackageChanged() to avoid false // negatives when the package state remains to be the same but only the component state is // changed. @@ -5255,8 +5264,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return getCurrentInputMethodSubtypeLocked(); } - final InputMethodSettings settings = queryMethodMapForUserLocked(userId); - return settings.getCurrentInputMethodSubtypeForNonCurrentUsers(); + return InputMethodSettingsRepository.get(userId) + .getCurrentInputMethodSubtypeForNonCurrentUsers(); } } @@ -5318,27 +5327,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub */ @GuardedBy("ImfLock.class") private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) { - final InputMethodSettings settings; - if (userId == mSettings.getUserId()) { - settings = mSettings; - } else { - final AdditionalSubtypeMap additionalSubtypeMap = - AdditionalSubtypeMapRepository.get(userId); - settings = queryInputMethodServicesInternal(mContext, userId, - additionalSubtypeMap, DirectBootAwareness.AUTO); - } + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); return settings.getMethodMap().get(settings.getSelectedInputMethod()); } @GuardedBy("ImfLock.class") - private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) { - final AdditionalSubtypeMap additionalSubtypeMap = - AdditionalSubtypeMapRepository.get(userId); - return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - DirectBootAwareness.AUTO); - } - - @GuardedBy("ImfLock.class") private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) { if (userId == mSettings.getUserId()) { if (!mSettings.getMethodMap().containsKey(imeId) @@ -5349,7 +5342,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); return true; } - final InputMethodSettings settings = queryMethodMapForUserLocked(userId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (!settings.getMethodMap().containsKey(imeId) || !settings.getEnabledInputMethodList().contains( settings.getMethodMap().get(imeId))) { @@ -5489,7 +5482,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub setInputMethodEnabledLocked(imeId, enabled); return true; } - final InputMethodSettings settings = queryMethodMapForUserLocked(userId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (!settings.getMethodMap().containsKey(imeId)) { return false; // IME is not found. } @@ -6243,7 +6236,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled); } } else { - final InputMethodSettings settings = queryMethodMapForUserLocked(userId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (enabled) { if (!settings.getMethodMap().containsKey(imeId)) { failedToEnableUnknownIme = true; @@ -6377,10 +6370,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub nextIme = mSettings.getSelectedInputMethod(); nextEnabledImes = mSettings.getEnabledInputMethodList(); } else { - final AdditionalSubtypeMap additionalSubtypeMap = - AdditionalSubtypeMapRepository.get(userId); - final InputMethodSettings settings = queryInputMethodServicesInternal( - mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO); + final InputMethodSettings settings = + InputMethodSettingsRepository.get(userId); nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList()); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java new file mode 100644 index 000000000000..60b9a4cfe840 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java @@ -0,0 +1,86 @@ +/* + * 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.server.inputmethod; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.Handler; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.inputmethod.DirectBootAwareness; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; + +final class InputMethodSettingsRepository { + @GuardedBy("ImfLock.class") + @NonNull + private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>(); + + /** + * Not intended to be instantiated. + */ + private InputMethodSettingsRepository() { + } + + @NonNull + @GuardedBy("ImfLock.class") + static InputMethodSettings get(@UserIdInt int userId) { + final InputMethodSettings obj = sPerUserMap.get(userId); + if (obj != null) { + return obj; + } + return InputMethodSettings.createEmptyMap(userId); + } + + @GuardedBy("ImfLock.class") + static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) { + sPerUserMap.put(userId, obj); + } + + static void initialize(@NonNull Handler handler, @NonNull Context context) { + final UserManagerInternal userManagerInternal = + LocalServices.getService(UserManagerInternal.class); + handler.post(() -> { + userManagerInternal.addUserLifecycleListener( + new UserManagerInternal.UserLifecycleListener() { + @Override + public void onUserRemoved(UserInfo user) { + final int userId = user.id; + handler.post(() -> { + synchronized (ImfLock.class) { + sPerUserMap.remove(userId); + } + }); + } + }); + synchronized (ImfLock.class) { + for (int userId : userManagerInternal.getUserIds()) { + final InputMethodSettings settings = + InputMethodManagerService.queryInputMethodServicesInternal( + context, + userId, + AdditionalSubtypeMapRepository.get(userId), + DirectBootAwareness.AUTO); + sPerUserMap.put(userId, settings); + } + } + }); + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c38fbda4f5fd..c1a4571eeb20 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -338,6 +338,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.TriPredicate; @@ -1825,6 +1826,12 @@ public class NotificationManagerService extends SystemService { } } + protected void logSensitiveAdjustmentReceived(boolean hasPosted, + boolean hasSensitiveContent, int lifespanMs) { + FrameworkStatsLog.write(FrameworkStatsLog.SENSITIVE_NOTIFICATION_REDACTION, hasPosted, + hasSensitiveContent, lifespanMs); + } + @GuardedBy("mNotificationLock") void clearSoundLocked() { mSoundNotificationKey = null; @@ -6384,7 +6391,7 @@ public class NotificationManagerService extends SystemService { if (Objects.equals(adjustment.getKey(), r.getKey()) && Objects.equals(adjustment.getUser(), r.getUserId()) && mAssistants.isSameUser(token, r.getUserId())) { - applyAdjustment(r, adjustment); + applyAdjustmentLocked(r, adjustment, false); r.applyAdjustments(); // importance is checked at the beginning of the // PostNotificationRunnable, before the signal extractors are run, so @@ -6394,7 +6401,7 @@ public class NotificationManagerService extends SystemService { } } if (!foundEnqueued) { - applyAdjustmentFromAssistant(token, adjustment); + applyAdjustmentsFromAssistant(token, List.of(adjustment)); } } } finally { @@ -6422,7 +6429,7 @@ public class NotificationManagerService extends SystemService { for (Adjustment adjustment : adjustments) { NotificationRecord r = mNotificationsByKey.get(adjustment.getKey()); if (r != null && mAssistants.isSameUser(token, r.getUserId())) { - applyAdjustment(r, adjustment); + applyAdjustmentLocked(r, adjustment, true); // If the assistant has blocked the notification, cancel it // This will trigger a sort, so we don't have to explicitly ask for // one here. @@ -6706,7 +6713,9 @@ public class NotificationManagerService extends SystemService { } } - private void applyAdjustment(NotificationRecord r, Adjustment adjustment) { + @GuardedBy("mNotificationLock") + private void applyAdjustmentLocked(NotificationRecord r, Adjustment adjustment, + boolean isPosted) { if (r == null) { return; } @@ -6723,6 +6732,11 @@ public class NotificationManagerService extends SystemService { adjustments.remove(removeKey); } r.addAdjustment(adjustment); + if (adjustment.getSignals().containsKey(Adjustment.KEY_SENSITIVE_CONTENT)) { + logSensitiveAdjustmentReceived(isPosted, + adjustment.getSignals().getBoolean(Adjustment.KEY_SENSITIVE_CONTENT), + r.getLifespanMs(System.currentTimeMillis())); + } } } diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java index 40f0ef9a8541..81f11b52dcb7 100644 --- a/core/java/android/app/ondeviceintelligence/Content.aidl +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2024, The Android Open Source Project +/* + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,8 @@ * limitations under the License. */ -package android.app.ondeviceintelligence; +package com.android.server.ondeviceintelligence; -/** - * @hide - */ -parcelable Content; +public interface OnDeviceIntelligenceManagerInternal { + String getRemoteServicePackageName(); +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java index 71800efae292..28682e3d916f 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java @@ -16,12 +16,11 @@ package com.android.server.ondeviceintelligence; -import static android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED; - import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.app.AppGlobals; -import android.app.ondeviceintelligence.Content; import android.app.ondeviceintelligence.DownloadCallback; import android.app.ondeviceintelligence.Feature; import android.app.ondeviceintelligence.IDownloadCallback; @@ -33,25 +32,32 @@ import android.app.ondeviceintelligence.IProcessingSignal; import android.app.ondeviceintelligence.IResponseCallback; import android.app.ondeviceintelligence.IStreamingResponseCallback; import android.app.ondeviceintelligence.ITokenInfoCallback; -import android.app.ondeviceintelligence.OnDeviceIntelligenceManager; +import android.app.ondeviceintelligence.OnDeviceIntelligenceException; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Binder; +import android.content.res.Resources; import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; import android.os.ICancellationSignal; +import android.os.Looper; +import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; import android.os.UserHandle; import android.provider.DeviceConfig; import android.service.ondeviceintelligence.IOnDeviceIntelligenceService; import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService; +import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback; import android.service.ondeviceintelligence.IRemoteProcessingService; import android.service.ondeviceintelligence.IRemoteStorageService; -import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback; import android.service.ondeviceintelligence.OnDeviceIntelligenceService; import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService; import android.text.TextUtils; @@ -62,8 +68,10 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; import com.android.server.SystemService; +import java.io.FileDescriptor; import java.util.Objects; import java.util.Set; @@ -84,6 +92,9 @@ public class OnDeviceIntelligenceManagerService extends SystemService { private static final String TAG = OnDeviceIntelligenceManagerService.class.getSimpleName(); private static final String KEY_SERVICE_ENABLED = "service_enabled"; + /** Handler message to {@link #resetTemporaryServices()} */ + private static final int MSG_RESET_TEMPORARY_SERVICE = 0; + /** Default value in absence of {@link DeviceConfig} override. */ private static final boolean DEFAULT_SERVICE_ENABLED = true; private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence"; @@ -96,19 +107,30 @@ public class OnDeviceIntelligenceManagerService extends SystemService { private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService; volatile boolean mIsServiceEnabled; + @GuardedBy("mLock") + private String[] mTemporaryServiceNames; + + /** + * Handler used to reset the temporary service names. + */ + @GuardedBy("mLock") + private Handler mTemporaryHandler; + public OnDeviceIntelligenceManagerService(Context context) { super(context); mContext = context; + mTemporaryServiceNames = new String[0]; } @Override public void onStart() { publishBinderService( - Context.ON_DEVICE_INTELLIGENCE_SERVICE, new OnDeviceIntelligenceManagerInternal(), + Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(), /* allowIsolated = */true); + LocalServices.addService(OnDeviceIntelligenceManagerInternal.class, + OnDeviceIntelligenceManagerService.this::getRemoteConfiguredPackageName); } - @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { @@ -133,195 +155,211 @@ public class OnDeviceIntelligenceManagerService extends SystemService { KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); } - private final class OnDeviceIntelligenceManagerInternal extends - IOnDeviceIntelligenceManager.Stub { - @Override - public void getVersion(RemoteCallback remoteCallback) throws RemoteException { - Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion"); - Objects.requireNonNull(remoteCallback); - mContext.enforceCallingOrSelfPermission( - Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); - if (!mIsServiceEnabled) { - Slog.w(TAG, "Service not available"); - remoteCallback.sendResult(null); - return; + private IBinder getOnDeviceIntelligenceManagerService() { + return new IOnDeviceIntelligenceManager.Stub() { + @Override + public String getRemoteServicePackageName() { + return OnDeviceIntelligenceManagerService.this.getRemoteConfiguredPackageName(); } - ensureRemoteIntelligenceServiceInitialized(); - mRemoteOnDeviceIntelligenceService.post( - service -> service.getVersion(remoteCallback)); - } - @Override - public void getFeature(int id, IFeatureCallback featureCallback) throws RemoteException { - Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures"); - Objects.requireNonNull(featureCallback); - mContext.enforceCallingOrSelfPermission( - Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); - if (!mIsServiceEnabled) { - Slog.w(TAG, "Service not available"); - featureCallback.onFailure( - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, - "OnDeviceIntelligenceManagerService is unavailable", - new PersistableBundle()); - return; + @Override + public void getVersion(RemoteCallback remoteCallback) throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion"); + Objects.requireNonNull(remoteCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + remoteCallback.sendResult(null); + return; + } + ensureRemoteIntelligenceServiceInitialized(); + mRemoteOnDeviceIntelligenceService.run( + service -> service.getVersion(remoteCallback)); } - ensureRemoteIntelligenceServiceInitialized(); - mRemoteOnDeviceIntelligenceService.post( - service -> service.getFeature(Binder.getCallingUid(), id, featureCallback)); - } - @Override - public void listFeatures(IListFeaturesCallback listFeaturesCallback) - throws RemoteException { - Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures"); - Objects.requireNonNull(listFeaturesCallback); - mContext.enforceCallingOrSelfPermission( - Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); - if (!mIsServiceEnabled) { - Slog.w(TAG, "Service not available"); - listFeaturesCallback.onFailure( - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, - "OnDeviceIntelligenceManagerService is unavailable", - new PersistableBundle()); - return; + @Override + public void getFeature(int id, IFeatureCallback featureCallback) + throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures"); + Objects.requireNonNull(featureCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + featureCallback.onFailure( + OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + PersistableBundle.EMPTY); + return; + } + ensureRemoteIntelligenceServiceInitialized(); + mRemoteOnDeviceIntelligenceService.run( + service -> service.getFeature(Binder.getCallingUid(), id, featureCallback)); } - ensureRemoteIntelligenceServiceInitialized(); - mRemoteOnDeviceIntelligenceService.post( - service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback)); - } - @Override - public void getFeatureDetails(Feature feature, - IFeatureDetailsCallback featureDetailsCallback) - throws RemoteException { - Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus"); - Objects.requireNonNull(feature); - Objects.requireNonNull(featureDetailsCallback); - mContext.enforceCallingOrSelfPermission( - Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); - if (!mIsServiceEnabled) { - Slog.w(TAG, "Service not available"); - featureDetailsCallback.onFailure( - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, - "OnDeviceIntelligenceManagerService is unavailable", - new PersistableBundle()); - return; + @Override + public void listFeatures(IListFeaturesCallback listFeaturesCallback) + throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures"); + Objects.requireNonNull(listFeaturesCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + listFeaturesCallback.onFailure( + OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + PersistableBundle.EMPTY); + return; + } + ensureRemoteIntelligenceServiceInitialized(); + mRemoteOnDeviceIntelligenceService.run( + service -> service.listFeatures(Binder.getCallingUid(), + listFeaturesCallback)); } - ensureRemoteIntelligenceServiceInitialized(); - mRemoteOnDeviceIntelligenceService.post( - service -> service.getFeatureDetails(Binder.getCallingUid(), feature, - featureDetailsCallback)); - } - @Override - public void requestFeatureDownload(Feature feature, ICancellationSignal cancellationSignal, - IDownloadCallback downloadCallback) throws RemoteException { - Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload"); - Objects.requireNonNull(feature); - Objects.requireNonNull(downloadCallback); - mContext.enforceCallingOrSelfPermission( - Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); - if (!mIsServiceEnabled) { - Slog.w(TAG, "Service not available"); - downloadCallback.onDownloadFailed( - DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE, - "OnDeviceIntelligenceManagerService is unavailable", - new PersistableBundle()); + @Override + public void getFeatureDetails(Feature feature, + IFeatureDetailsCallback featureDetailsCallback) + throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus"); + Objects.requireNonNull(feature); + Objects.requireNonNull(featureDetailsCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + featureDetailsCallback.onFailure( + OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + PersistableBundle.EMPTY); + return; + } + ensureRemoteIntelligenceServiceInitialized(); + mRemoteOnDeviceIntelligenceService.run( + service -> service.getFeatureDetails(Binder.getCallingUid(), feature, + featureDetailsCallback)); + } + + @Override + public void requestFeatureDownload(Feature feature, + ICancellationSignal cancellationSignal, + IDownloadCallback downloadCallback) throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload"); + Objects.requireNonNull(feature); + Objects.requireNonNull(downloadCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + downloadCallback.onDownloadFailed( + DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + PersistableBundle.EMPTY); + } + ensureRemoteIntelligenceServiceInitialized(); + mRemoteOnDeviceIntelligenceService.run( + service -> service.requestFeatureDownload(Binder.getCallingUid(), feature, + cancellationSignal, + downloadCallback)); } - ensureRemoteIntelligenceServiceInitialized(); - mRemoteOnDeviceIntelligenceService.post( - service -> service.requestFeatureDownload(Binder.getCallingUid(), feature, - cancellationSignal, - downloadCallback)); - } - @Override - public void requestTokenInfo(Feature feature, - Content request, ICancellationSignal cancellationSignal, - ITokenInfoCallback tokenInfoCallback) throws RemoteException { - Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing"); - Objects.requireNonNull(feature); - Objects.requireNonNull(request); - Objects.requireNonNull(tokenInfoCallback); - - mContext.enforceCallingOrSelfPermission( - Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); - if (!mIsServiceEnabled) { - Slog.w(TAG, "Service not available"); - tokenInfoCallback.onFailure( - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, - "OnDeviceIntelligenceManagerService is unavailable", - new PersistableBundle()); + @Override + public void requestTokenInfo(Feature feature, + Bundle request, ICancellationSignal cancellationSignal, + ITokenInfoCallback tokenInfoCallback) throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing"); + Objects.requireNonNull(feature); + Objects.requireNonNull(request); + Objects.requireNonNull(tokenInfoCallback); + + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + tokenInfoCallback.onFailure( + OnDeviceIntelligenceException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + PersistableBundle.EMPTY); + } + ensureRemoteInferenceServiceInitialized(); + mRemoteInferenceService.run( + service -> service.requestTokenInfo(Binder.getCallingUid(), feature, + request, + cancellationSignal, + tokenInfoCallback)); } - ensureRemoteInferenceServiceInitialized(); - mRemoteInferenceService.post( - service -> service.requestTokenInfo(Binder.getCallingUid(), feature, request, - cancellationSignal, - tokenInfoCallback)); - } - @Override - public void processRequest(Feature feature, - Content request, - int requestType, - ICancellationSignal cancellationSignal, - IProcessingSignal processingSignal, - IResponseCallback responseCallback) - throws RemoteException { - Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest"); - Objects.requireNonNull(feature); - Objects.requireNonNull(responseCallback); - mContext.enforceCallingOrSelfPermission( - Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); - if (!mIsServiceEnabled) { - Slog.w(TAG, "Service not available"); - responseCallback.onFailure( - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE, - "OnDeviceIntelligenceManagerService is unavailable", - new PersistableBundle()); + @Override + public void processRequest(Feature feature, + Bundle request, + int requestType, + ICancellationSignal cancellationSignal, + IProcessingSignal processingSignal, + IResponseCallback responseCallback) + throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest"); + Objects.requireNonNull(feature); + Objects.requireNonNull(responseCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + responseCallback.onFailure( + OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + PersistableBundle.EMPTY); + } + ensureRemoteInferenceServiceInitialized(); + mRemoteInferenceService.run( + service -> service.processRequest(Binder.getCallingUid(), feature, request, + requestType, + cancellationSignal, processingSignal, + responseCallback)); } - ensureRemoteInferenceServiceInitialized(); - mRemoteInferenceService.post( - service -> service.processRequest(Binder.getCallingUid(), feature, request, - requestType, - cancellationSignal, processingSignal, - responseCallback)); - } - @Override - public void processRequestStreaming(Feature feature, - Content request, - int requestType, - ICancellationSignal cancellationSignal, - IProcessingSignal processingSignal, - IStreamingResponseCallback streamingCallback) throws RemoteException { - Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming"); - Objects.requireNonNull(feature); - Objects.requireNonNull(streamingCallback); - mContext.enforceCallingOrSelfPermission( - Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); - if (!mIsServiceEnabled) { - Slog.w(TAG, "Service not available"); - streamingCallback.onFailure( - OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE, - "OnDeviceIntelligenceManagerService is unavailable", - new PersistableBundle()); + @Override + public void processRequestStreaming(Feature feature, + Bundle request, + int requestType, + ICancellationSignal cancellationSignal, + IProcessingSignal processingSignal, + IStreamingResponseCallback streamingCallback) throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming"); + Objects.requireNonNull(feature); + Objects.requireNonNull(streamingCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + streamingCallback.onFailure( + OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + PersistableBundle.EMPTY); + } + ensureRemoteInferenceServiceInitialized(); + mRemoteInferenceService.run( + service -> service.processRequestStreaming(Binder.getCallingUid(), feature, + request, requestType, + cancellationSignal, processingSignal, + streamingCallback)); } - ensureRemoteInferenceServiceInitialized(); - mRemoteInferenceService.post( - service -> service.processRequestStreaming(Binder.getCallingUid(), feature, - request, requestType, - cancellationSignal, processingSignal, - streamingCallback)); - } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + new OnDeviceIntelligenceShellCommand(OnDeviceIntelligenceManagerService.this).exec( + this, in, out, err, args, callback, resultReceiver); + } + }; } private void ensureRemoteIntelligenceServiceInitialized() throws RemoteException { synchronized (mLock) { if (mRemoteOnDeviceIntelligenceService == null) { - String serviceName = mContext.getResources().getString( - R.string.config_defaultOnDeviceIntelligenceService); + String serviceName = getServiceNames()[0]; validateService(serviceName, false); mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext, ComponentName.unflattenFromString(serviceName), @@ -352,13 +390,13 @@ public class OnDeviceIntelligenceManagerService extends SystemService { IProcessingUpdateStatusCallback callback) { try { ensureRemoteInferenceServiceInitialized(); - mRemoteInferenceService.post( + mRemoteInferenceService.run( service -> service.updateProcessingState( processingState, callback)); } catch (RemoteException unused) { try { callback.onFailure( - PROCESSING_UPDATE_STATUS_CONNECTION_FAILED, + OnDeviceIntelligenceException.PROCESSING_UPDATE_STATUS_CONNECTION_FAILED, "Received failure invoking the remote processing service."); } catch (RemoteException ex) { Slog.w(TAG, "Failed to send failure status.", ex); @@ -371,8 +409,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { private void ensureRemoteInferenceServiceInitialized() throws RemoteException { synchronized (mLock) { if (mRemoteInferenceService == null) { - String serviceName = mContext.getResources().getString( - R.string.config_defaultOnDeviceSandboxedInferenceService); + String serviceName = getServiceNames()[1]; validateService(serviceName, true); mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext, ComponentName.unflattenFromString(serviceName), @@ -384,7 +421,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { @NonNull IOnDeviceSandboxedInferenceService service) { try { ensureRemoteIntelligenceServiceInitialized(); - mRemoteOnDeviceIntelligenceService.post( + mRemoteOnDeviceIntelligenceService.run( intelligenceService -> intelligenceService.notifyInferenceServiceConnected()); service.registerRemoteStorageService( getIRemoteStorageService()); @@ -404,7 +441,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { public void getReadOnlyFileDescriptor( String filePath, AndroidFuture<ParcelFileDescriptor> future) { - mRemoteOnDeviceIntelligenceService.post( + mRemoteOnDeviceIntelligenceService.run( service -> service.getReadOnlyFileDescriptor( filePath, future)); } @@ -413,7 +450,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { public void getReadOnlyFeatureFileDescriptorMap( Feature feature, RemoteCallback remoteCallback) { - mRemoteOnDeviceIntelligenceService.post( + mRemoteOnDeviceIntelligenceService.run( service -> service.getReadOnlyFeatureFileDescriptorMap( feature, remoteCallback)); } @@ -469,4 +506,92 @@ public class OnDeviceIntelligenceManagerService extends SystemService { return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0 && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0; } + + @Nullable + public String getRemoteConfiguredPackageName() { + try { + String[] serviceNames = getServiceNames(); + ComponentName componentName = ComponentName.unflattenFromString(serviceNames[1]); + if (componentName != null) { + return componentName.getPackageName(); + } + } catch (Resources.NotFoundException e) { + Slog.e(TAG, "Could not find resource", e); + } + + return null; + } + + + protected String[] getServiceNames() throws Resources.NotFoundException { + // TODO 329240495 : Consider a small class with explicit field names for the two services + synchronized (mLock) { + if (mTemporaryServiceNames != null && mTemporaryServiceNames.length == 2) { + return mTemporaryServiceNames; + } + } + return new String[]{mContext.getResources().getString( + R.string.config_defaultOnDeviceIntelligenceService), + mContext.getResources().getString( + R.string.config_defaultOnDeviceSandboxedInferenceService)}; + } + + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) { + Objects.requireNonNull(componentNames); + enforceShellOnly(Binder.getCallingUid(), "setTemporaryServices"); + mContext.enforceCallingPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + synchronized (mLock) { + mTemporaryServiceNames = componentNames; + + if (mTemporaryHandler == null) { + mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_RESET_TEMPORARY_SERVICE) { + synchronized (mLock) { + resetTemporaryServices(); + } + } else { + Slog.wtf(TAG, "invalid handler msg: " + msg); + } + } + }; + } else { + mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE); + } + + if (durationMs != -1) { + mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs); + } + } + } + + public void resetTemporaryServices() { + synchronized (mLock) { + if (mTemporaryHandler != null) { + mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE); + mTemporaryHandler = null; + } + + mRemoteInferenceService = null; + mRemoteOnDeviceIntelligenceService = null; + mTemporaryServiceNames = new String[0]; + } + } + + /** + * Throws if the caller is not of a shell (or root) UID. + * + * @param callingUid pass Binder.callingUid(). + */ + public static void enforceShellOnly(int callingUid, String message) { + if (callingUid == android.os.Process.SHELL_UID + || callingUid == android.os.Process.ROOT_UID) { + return; // okay + } + + throw new SecurityException(message + ": Only shell user can call it"); + } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java new file mode 100644 index 000000000000..a76d8a31405d --- /dev/null +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.ondeviceintelligence; + +import android.annotation.NonNull; +import android.os.ShellCommand; + +import java.io.PrintWriter; +import java.util.Objects; + +final class OnDeviceIntelligenceShellCommand extends ShellCommand { + private static final String TAG = OnDeviceIntelligenceShellCommand.class.getSimpleName(); + + @NonNull + private final OnDeviceIntelligenceManagerService mService; + + OnDeviceIntelligenceShellCommand(@NonNull OnDeviceIntelligenceManagerService service) { + mService = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + + switch (cmd) { + case "set-temporary-services": + return setTemporaryServices(); + case "get-services": + return getConfiguredServices(); + default: + return handleDefaultCommands(cmd); + } + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("OnDeviceIntelligenceShellCommand commands: "); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(); + pw.println( + " set-temporary-services [IntelligenceServiceComponentName] " + + "[InferenceServiceComponentName] [DURATION]"); + pw.println(" Temporarily (for DURATION ms) changes the service implementations."); + pw.println(" To reset, call without any arguments."); + + pw.println(" get-services To get the names of services that are currently being used."); + } + + private int setTemporaryServices() { + final PrintWriter out = getOutPrintWriter(); + final String intelligenceServiceName = getNextArg(); + final String inferenceServiceName = getNextArg(); + if (getRemainingArgsCount() == 0 && intelligenceServiceName == null + && inferenceServiceName == null) { + mService.resetTemporaryServices(); + out.println("OnDeviceIntelligenceManagerService temporary reset. "); + return 0; + } + + Objects.requireNonNull(intelligenceServiceName); + Objects.requireNonNull(inferenceServiceName); + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setTemporaryServices( + new String[]{intelligenceServiceName, inferenceServiceName}, duration); + out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName + + " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName + + " for " + duration + "ms"); + return 0; + } + + private int getConfiguredServices() { + final PrintWriter out = getOutPrintWriter(); + String[] services = mService.getServiceNames(); + out.println("OnDeviceIntelligenceService set to : " + services[0] + + " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]); + return 0; + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/permission/OWNERS b/services/core/java/com/android/server/permission/OWNERS new file mode 100644 index 000000000000..fb6099cf7e5a --- /dev/null +++ b/services/core/java/com/android/server/permission/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 137825 + +include platform/frameworks/base:/core/java/android/permission/OWNERS diff --git a/services/core/java/com/android/server/permission/PermissionManagerLocal.java b/services/core/java/com/android/server/permission/PermissionManagerLocal.java new file mode 100644 index 000000000000..7251e6ee62de --- /dev/null +++ b/services/core/java/com/android/server/permission/PermissionManagerLocal.java @@ -0,0 +1,46 @@ +/* + * 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.server.permission; + +import android.annotation.TestApi; +import com.android.internal.annotations.Keep; + +/** + * In-process API for server side permission related infrastructure. + * + * @hide + */ +@Keep +@TestApi +public interface PermissionManagerLocal { + + /** + * Get whether signature permission allowlist is enforced even on debuggable builds. + * + * @return whether the signature permission allowlist is force enforced + */ + @TestApi + boolean isSignaturePermissionAllowlistForceEnforced(); + + /** + * Set whether signature permission allowlist is enforced even on debuggable builds. + * + * @param forceEnforced whether the signature permission allowlist is force enforced + */ + @TestApi + void setSignaturePermissionAllowlistForceEnforced(boolean forceEnforced); +} diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java index 6266ef325e53..4ed6e808d23f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerLocal.java +++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java @@ -20,6 +20,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.content.pm.SigningDetails; import android.os.Binder; import android.os.UserHandle; @@ -124,6 +126,48 @@ public interface PackageManagerLocal { FilteredSnapshot withFilteredSnapshot(int callingUid, @NonNull UserHandle user); /** + * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will + * behave as if they are signed by the {@code newSigningDetails}. + * <p> + * This is only available on {@link android.os.Build#isDebuggable debuggable} builds. + * + * @param oldSigningDetails the original signing detail of the package + * @param newSigningDetails the new signing detail that will replace the original one + * @throws SecurityException if the build is not debuggable + * + * @hide + */ + @TestApi + void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails, + @NonNull SigningDetails newSigningDetails); + + /** + * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by + * the old signing details. + * <p> + * This is only available on {@link android.os.Build#isDebuggable debuggable} builds. + * + * @param oldSigningDetails the original signing detail of the package + * @throws SecurityException if the build is not debuggable + * + * @hide + */ + @TestApi + void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails); + + /** + * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}. + * <p> + * This is only available on {@link android.os.Build#isDebuggable debuggable} builds. + * + * @throws SecurityException if the build is not debuggable + * + * @hide + */ + @TestApi + void clearOverrideSigningDetails(); + + /** * @hide */ @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java index 8d05450147e2..55afb17614af 100644 --- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java +++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java @@ -20,9 +20,12 @@ import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.content.pm.SigningDetails; import android.os.Binder; +import android.os.Build; import android.os.UserHandle; import android.util.ArrayMap; +import android.util.apk.ApkSignatureVerifier; import com.android.server.pm.Computer; import com.android.server.pm.PackageManagerLocal; @@ -72,6 +75,31 @@ public class PackageManagerLocalImpl implements PackageManagerLocal { mService.snapshotComputer(false /*allowLiveComputer*/), null); } + @Override + public void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails, + @NonNull SigningDetails newSigningDetails) { + if (!Build.isDebuggable()) { + throw new SecurityException("This test API is only available on debuggable builds"); + } + ApkSignatureVerifier.addOverrideSigningDetails(oldSigningDetails, newSigningDetails); + } + + @Override + public void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails) { + if (!Build.isDebuggable()) { + throw new SecurityException("This test API is only available on debuggable builds"); + } + ApkSignatureVerifier.removeOverrideSigningDetails(oldSigningDetails); + } + + @Override + public void clearOverrideSigningDetails() { + if (!Build.isDebuggable()) { + throw new SecurityException("This test API is only available on debuggable builds"); + } + ApkSignatureVerifier.clearOverrideSigningDetails(); + } + private abstract static class BaseSnapshotImpl implements AutoCloseable { private boolean mClosed; diff --git a/services/core/java/com/android/server/rollback/OWNERS b/services/core/java/com/android/server/rollback/OWNERS index daa02111f71f..8337fd2453df 100644 --- a/services/core/java/com/android/server/rollback/OWNERS +++ b/services/core/java/com/android/server/rollback/OWNERS @@ -1,3 +1 @@ -ancr@google.com -harshitmahajan@google.com -robertogil@google.com +include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0069cdd1e4e8..6fa6957f2949 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7786,8 +7786,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override void prepareSurfaces() { - final boolean show = isVisible() || isAnimating(PARENTS, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS + final boolean show = (isVisible() + // Ensure that the activity content is hidden when the decor surface is boosted to + // prevent UI redressing attack. + && !getTask().isDecorSurfaceBoosted()) + || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS | ANIMATION_TYPE_PREDICT_BACK); if (mSurfaceControl != null) { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 071f40342461..f7baa7914f86 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -18,10 +18,10 @@ package com.android.server.wm; import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ActivityOptions.BackgroundActivityStartMode; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; -import static android.app.ActivityOptions.BackgroundActivityStartMode; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; @@ -37,12 +37,11 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW; import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY; import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel; +import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS; import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck; import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator; import static com.android.window.flags.Flags.balRequireOptInSameUid; -import static com.android.window.flags.Flags.balShowToasts; import static com.android.window.flags.Flags.balShowToastsBlocked; -import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS; import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.util.Objects.requireNonNull; @@ -752,7 +751,6 @@ public class BackgroundActivityStartController { Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked" + " if the PI creator upgrades target_sdk to 35+! " + " (missing opt in by PI creator)!" + state.dump()); - showBalRiskToast(); return allowBasedOnCaller(state); } } @@ -762,7 +760,6 @@ public class BackgroundActivityStartController { Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked" + " if the PI sender upgrades target_sdk to 34+! " + " (missing opt in by PI sender)!" + state.dump()); - showBalRiskToast(); return allowBasedOnRealCaller(state); } } @@ -793,7 +790,11 @@ public class BackgroundActivityStartController { private BalVerdict abortLaunch(BalState state) { Slog.wtf(TAG, "Background activity launch blocked! " + state.dump()); - showBalBlockedToast(); + if (balShowToastsBlocked() + && (state.mResultForCaller.allows() || state.mResultForRealCaller.allows())) { + // only show a toast if either caller or real caller could launch if they opted in + showToast("BAL blocked. go/debug-bal"); + } return statsLog(BalVerdict.BLOCK, state); } @@ -1192,18 +1193,6 @@ public class BackgroundActivityStartController { return true; } - private void showBalBlockedToast() { - if (balShowToastsBlocked()) { - showToast("BAL blocked. go/debug-bal"); - } - } - - private void showBalRiskToast() { - if (balShowToasts()) { - showToast("BAL allowed in compat mode. go/debug-bal"); - } - } - @VisibleForTesting void showToast(String toastText) { UiThread.getHandler().post(() -> Toast.makeText(mService.mContext, toastText, Toast.LENGTH_LONG).show()); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index d87e21c0ac92..55dc30cc37d5 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3741,7 +3741,9 @@ class Task extends TaskFragment { wc.assignChildLayers(t); if (!wc.needsZBoost()) { // Place the decor surface under any untrusted content. - if (mDecorSurfaceContainer != null && !decorSurfacePlaced + if (mDecorSurfaceContainer != null + && !mDecorSurfaceContainer.mIsBoosted + && !decorSurfacePlaced && shouldPlaceDecorSurfaceBelowContainer(wc)) { mDecorSurfaceContainer.assignLayer(t, layer++); decorSurfacePlaced = true; @@ -3760,7 +3762,9 @@ class Task extends TaskFragment { } // Place the decor surface just above the owner TaskFragment. - if (mDecorSurfaceContainer != null && !decorSurfacePlaced + if (mDecorSurfaceContainer != null + && !mDecorSurfaceContainer.mIsBoosted + && !decorSurfacePlaced && wc == mDecorSurfaceContainer.mOwnerTaskFragment) { mDecorSurfaceContainer.assignLayer(t, layer++); decorSurfacePlaced = true; @@ -3768,10 +3772,10 @@ class Task extends TaskFragment { } } - // If not placed yet, the decor surface should be on top of all non-boosted children. - if (mDecorSurfaceContainer != null && !decorSurfacePlaced) { + // Boost the decor surface above other non-boosted windows if requested. The cover surface + // will ensure that the content of the windows below are invisible. + if (mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted) { mDecorSurfaceContainer.assignLayer(t, layer++); - decorSurfacePlaced = true; } for (int j = 0; j < mChildren.size(); ++j) { @@ -3796,6 +3800,24 @@ class Task extends TaskFragment { return !isOwnActivity && !isTrustedTaskFragment; } + void setDecorSurfaceBoosted( + @NonNull TaskFragment ownerTaskFragment, + boolean isBoosted, + @Nullable SurfaceControl.Transaction clientTransaction) { + if (mDecorSurfaceContainer == null + || mDecorSurfaceContainer.mOwnerTaskFragment != ownerTaskFragment) { + return; + } + mDecorSurfaceContainer.setBoosted(isBoosted, clientTransaction); + // scheduleAnimation() is called inside assignChildLayers(), which ensures that child + // surface visibility is updated with prepareSurfaces() + assignChildLayers(); + } + + boolean isDecorSurfaceBoosted() { + return mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted; + } + boolean isTaskId(int taskId) { return mTaskId == taskId; } @@ -6796,14 +6818,35 @@ class Task extends TaskFragment { } /** - * A decor surface that is requested by a {@code TaskFragmentOrganizer} which will be placed - * below children windows except for own Activities and TaskFragment in fully trusted mode. + * A class managing the decor surface. + * + * A decor surface is requested by a {@link TaskFragmentOrganizer} and is placed below children + * windows in the Task except for own Activities and TaskFragments in fully trusted mode. The + * decor surface is created and shared with the client app with + * {@link android.window.TaskFragmentOperation#OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE} and + * be removed with + * {@link android.window.TaskFragmentOperation#OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE}. + * + * When boosted with + * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED}, the decor + * surface is placed above all non-boosted windows in the Task, but all the content below it + * will be hidden to prevent UI redressing attacks. This can be used by the draggable + * divider between {@link TaskFragment}s where veils are drawn on the decor surface while + * dragging to indicate new bounds. */ @VisibleForTesting class DecorSurfaceContainer { + + // The container surface is the parent of the decor surface. The container surface + // should NEVER be shared with the client. It is used to ensure that the decor surface has + // a z-order in the Task that is managed by WM core and cannot be updated by the client + // process. @VisibleForTesting @NonNull final SurfaceControl mContainerSurface; + // The decor surface is shared with the client process owning the + // {@link TaskFragmentOrganizer}. It can be used to draw the divider between TaskFragments + // or other decorations. @VisibleForTesting @NonNull final SurfaceControl mDecorSurface; @@ -6812,12 +6855,18 @@ class Task extends TaskFragment { @VisibleForTesting @NonNull TaskFragment mOwnerTaskFragment; + private boolean mIsBoosted; + + // The surface transactions that will be applied when the layer is reassigned. + @NonNull private final List<SurfaceControl.Transaction> mPendingClientTransactions = + new ArrayList<>(); + private DecorSurfaceContainer(@NonNull TaskFragment initialOwner) { mOwnerTaskFragment = initialOwner; mContainerSurface = makeSurface().setContainerLayer() .setParent(mSurfaceControl) .setName(mSurfaceControl + " - decor surface container") - .setEffectLayer() + .setContainerLayer() .setHidden(false) .setCallsite("Task.DecorSurfaceContainer") .build(); @@ -6830,14 +6879,28 @@ class Task extends TaskFragment { .build(); } + private void setBoosted( + boolean isBoosted, @Nullable SurfaceControl.Transaction clientTransaction) { + mIsBoosted = isBoosted; + // The client transaction will be applied together with the next assignLayer. + if (clientTransaction != null) { + mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction); + } + } + private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) { t.setLayer(mContainerSurface, layer); t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible()); + for (int i = 0; i < mPendingClientTransactions.size(); i++) { + t.merge(mPendingClientTransactions.get(i)); + } + mPendingClientTransactions.clear(); } private void release() { - mDecorSurface.release(); - mContainerSurface.release(); + getSyncTransaction() + .remove(mDecorSurface) + .remove(mContainerSurface); } } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index a63e106beb55..7e6f5ac7497e 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -34,6 +34,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED; import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION; import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH; @@ -124,6 +125,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal; +import com.android.window.flags.Flags; import java.util.ArrayList; import java.util.HashMap; @@ -1557,13 +1559,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: { - final Task task = taskFragment.getTask(); - task.moveOrCreateDecorSurfaceFor(taskFragment); + taskFragment.getTask().moveOrCreateDecorSurfaceFor(taskFragment); break; } case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: { - final Task task = taskFragment.getTask(); - task.removeDecorSurface(); + taskFragment.getTask().removeDecorSurface(); break; } case OP_TYPE_SET_DIM_ON_TASK: { @@ -1577,6 +1577,23 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub operation.isMoveToBottomIfClearWhenLaunch()); break; } + case OP_TYPE_SET_DECOR_SURFACE_BOOSTED: { + if (Flags.activityEmbeddingInteractiveDividerFlag()) { + final SurfaceControl.Transaction clientTransaction = + operation.getSurfaceTransaction(); + if (clientTransaction != null) { + // Sanitize the client transaction. sanitize() silently removes invalid + // operations and does not throw or provide signal about whether there are + // any invalid operations. + clientTransaction.sanitize(caller.mPid, caller.mUid); + } + taskFragment.getTask().setDecorSurfaceBoosted( + taskFragment, + operation.getBooleanValue() /* isBoosted */, + clientTransaction); + } + break; + } } return effects; } @@ -1616,19 +1633,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } - // TODO (b/293654166) remove the decor surface checks once we clear security reviews - if ((opType == OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE - || opType == OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE) - && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { - final Throwable exception = new SecurityException( - "Only a system organizer can perform OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE" - + " or OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE." - ); - sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, - opType, exception); - return false; - } - if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH) && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { final Throwable exception = new SecurityException( diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index c778398342dc..610fcb5962c8 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -502,7 +502,7 @@ void NativeInputManager::dump(std::string& dump) { toString(mLocked.pointerGesturesEnabled)); dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches)); dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n", - mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled", + mLocked.pointerCaptureRequest.isEnable() ? "Enabled" : "Disabled", mLocked.pointerCaptureRequest.seq); if (auto pc = mLocked.legacyPointerController.lock(); pc) { dump += pc->dump(); @@ -1717,7 +1717,7 @@ void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) return; } - ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling"); + ALOGV("%s pointer capture.", request.isEnable() ? "Enabling" : "Disabling"); mLocked.pointerCaptureRequest = request; } // release lock diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 7c669f1aeb35..c6189ed405a1 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -161,6 +161,7 @@ import com.android.server.net.watchlist.NetworkWatchlistService; import com.android.server.notification.NotificationManagerService; import com.android.server.oemlock.OemLockService; import com.android.server.om.OverlayManagerService; +import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService; import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.NativeTombstoneManagerService; @@ -1965,6 +1966,7 @@ public final class SystemServer implements Dumpable { startSystemCaptionsManagerService(context, t); startTextToSpeechManagerService(context, t); startWearableSensingService(t); + startOnDeviceIntelligenceService(t); if (deviceHasConfigString( context, R.string.config_defaultAmbientContextDetectionService)) { @@ -2166,16 +2168,14 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); - if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) { - t.traceBegin("StartVcnManagementService"); - try { - vcnManagement = VcnManagementService.create(context); - ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement); - } catch (Throwable e) { - reportWtf("starting VCN Management Service", e); - } - t.traceEnd(); + t.traceBegin("StartVcnManagementService"); + try { + vcnManagement = VcnManagementService.create(context); + ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement); + } catch (Throwable e) { + reportWtf("starting VCN Management Service", e); } + t.traceEnd(); t.traceBegin("StartSystemUpdateManagerService"); try { @@ -3337,6 +3337,12 @@ public final class SystemServer implements Dumpable { t.traceEnd(); // startOtherServices } + private void startOnDeviceIntelligenceService(TimingsTraceAndSlog t) { + t.traceBegin("startOnDeviceIntelligenceManagerService"); + mSystemServiceManager.startService(OnDeviceIntelligenceManagerService.class); + t.traceEnd(); + } + /** * Starts system services defined in apexes. * diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt index acaec211440d..fd2e8c8fc9e7 100644 --- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt +++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt @@ -27,9 +27,11 @@ import com.android.server.LocalServices import com.android.server.SystemConfig import com.android.server.SystemService import com.android.server.appop.AppOpsCheckingServiceInterface +import com.android.server.permission.PermissionManagerLocal import com.android.server.permission.access.appop.AppOpService import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports +import com.android.server.permission.access.permission.PermissionManagerLocalImpl import com.android.server.permission.access.permission.PermissionService import com.android.server.pm.KnownPackages import com.android.server.pm.PackageManagerLocal @@ -63,6 +65,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { LocalServices.addService(AppOpsCheckingServiceInterface::class.java, appOpService) LocalServices.addService(PermissionManagerServiceInterface::class.java, permissionService) + + LocalManagerRegistry.addManager( + PermissionManagerLocal::class.java, + PermissionManagerLocalImpl(this) + ) } fun initialize() { diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 67df67fdf6c1..af8ce31205bf 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -63,6 +63,12 @@ class AppIdPermissionPolicy : SchemePolicy() { private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>() + /** + * Test-only switch to enforce signature permission allowlist even on debuggable builds. + */ + @Volatile + var isSignaturePermissionAllowlistForceEnforced = false + override val subjectScheme: String get() = UidUri.SCHEME @@ -1274,7 +1280,7 @@ class AppIdPermissionPolicy : SchemePolicy() { SigningDetails.CertCapabilities.PERMISSION ) if (!Flags.signaturePermissionAllowlistEnabled()) { - return hasCommonSigner; + return hasCommonSigner } if (!hasCommonSigner) { return false @@ -1308,7 +1314,7 @@ class AppIdPermissionPolicy : SchemePolicy() { " ${packageState.packageName} (${packageState.path}) not in" + " signature permission allowlist" ) - if (!Build.isDebuggable()) { + if (!Build.isDebuggable() || isSignaturePermissionAllowlistForceEnforced) { return false } } diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt new file mode 100644 index 000000000000..ad2d70bbe147 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionManagerLocalImpl.kt @@ -0,0 +1,40 @@ +/* + * 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.server.permission.access.permission + +import android.os.Build +import com.android.server.permission.PermissionManagerLocal +import com.android.server.permission.access.AccessCheckingService +import com.android.server.permission.access.PermissionUri +import com.android.server.permission.access.UidUri + +class PermissionManagerLocalImpl( + private val service: AccessCheckingService +) : PermissionManagerLocal { + private val policy = + service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy + + override fun isSignaturePermissionAllowlistForceEnforced(): Boolean { + check(Build.isDebuggable()) + return policy.isSignaturePermissionAllowlistForceEnforced + } + + override fun setSignaturePermissionAllowlistForceEnforced(forceEnforced: Boolean) { + check(Build.isDebuggable()) + policy.isSignaturePermissionAllowlistForceEnforced = forceEnforced + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java index bd20ae26821e..ce281daf41c4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java @@ -56,6 +56,7 @@ import com.android.server.wm.ActivityTaskManagerService; import org.junit.Rule; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.io.File; @@ -160,6 +161,7 @@ public abstract class BaseBroadcastQueueTest { realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal()); + realAms.mOomAdjuster.mCachedAppOptimizer = Mockito.mock(CachedAppOptimizer.class); realAms.mOomAdjuster = spy(realAms.mOomAdjuster); ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt())); realAms.mPackageManagerInt = mPackageManagerInt; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 66ab8076a217..1172685c4466 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -86,7 +86,6 @@ import androidx.test.platform.app.InstrumentationRegistry; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.InOrder; @@ -2335,8 +2334,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { .isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue)); } - @Ignore @Test + @RequiresFlagsEnabled(Flags.FLAG_DEFER_OUTGOING_BCASTS) public void testDeferOutgoingBroadcasts() throws Exception { final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); setProcessFreezable(callerApp, true /* pendingFreeze */, false /* frozen */); @@ -2350,6 +2349,8 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { makeRegisteredReceiver(receiverGreenApp), makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW)))); + // Verify that we invoke the call to freeze the caller app. + verify(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncImmediateLSP(callerApp); waitForIdle(); verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick); diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS index daa02111f71f..8337fd2453df 100644 --- a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS @@ -1,3 +1 @@ -ancr@google.com -harshitmahajan@google.com -robertogil@google.com +include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java index dc26e6e2374c..f6dc2f0f05b2 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java @@ -18,21 +18,23 @@ package com.android.server.accessibility; import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT; import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId; +import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.windowId; import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges; -import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.a11yWindowId; +import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.EventWindowIdMatcher.eventWindowId; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -350,6 +352,88 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest { } @Test + public void onWindowsChanged_shouldNotReportNonTouchableWindow() { + final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0); + when(window.isTouchable()).thenReturn(false); + final int windowId = mA11yWindowManager.findWindowIdLocked( + USER_SYSTEM_ID, window.getWindowInfo().token); + + onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + + final List<AccessibilityWindowInfo> a11yWindows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + assertThat(a11yWindows, not(hasItem(windowId(windowId)))); + } + + @Test + public void onWindowsChanged_shouldReportFocusedNonTouchableWindow() { + final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get( + DEFAULT_FOCUSED_INDEX); + when(window.isTouchable()).thenReturn(false); + final int windowId = mA11yWindowManager.findWindowIdLocked( + USER_SYSTEM_ID, window.getWindowInfo().token); + + onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + + final List<AccessibilityWindowInfo> a11yWindows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + assertThat(a11yWindows, hasItem(windowId(windowId))); + } + + @Test + public void onWindowsChanged_trustedFocusedNonTouchableWindow_shouldNotHideWindowsBelow() { + // Make the focused trusted un-touchable window fullscreen. + final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get( + DEFAULT_FOCUSED_INDEX); + setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)); + when(window.isTouchable()).thenReturn(false); + when(window.isTrustedOverlay()).thenReturn(true); + + onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + + final List<AccessibilityWindowInfo> a11yWindows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS)); + } + + @Test + public void onWindowsChanged_accessibilityOverlay_shouldNotHideWindowsBelow() { + // Make the a11y overlay window fullscreen. + final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0); + setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)); + when(window.getType()).thenReturn(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY); + + onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + + final List<AccessibilityWindowInfo> a11yWindows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS)); + } + + @Test + public void onWindowsChanged_shouldReportFocusedWindowEvenIfOccluded() { + // Make the front window fullscreen. + final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0); + setRegionForMockAccessibilityWindow(frontWindow, + new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)); + final int frontWindowId = mA11yWindowManager.findWindowIdLocked( + USER_SYSTEM_ID, frontWindow.getWindowInfo().token); + + final AccessibilityWindow focusedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get( + DEFAULT_FOCUSED_INDEX); + final int focusedWindowId = mA11yWindowManager.findWindowIdLocked( + USER_SYSTEM_ID, focusedWindow.getWindowInfo().token); + + onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES); + + final List<AccessibilityWindowInfo> a11yWindows = + mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY); + assertThat(a11yWindows, hasSize(2)); + assertThat(a11yWindows.get(0), windowId(frontWindowId)); + assertThat(a11yWindows.get(1), windowId(focusedWindowId)); + } + + @Test public void onWindowsChangedAndForceSend_shouldUpdateWindows() { assertNotEquals("new title", toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY) @@ -631,11 +715,11 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest { .sendAccessibilityEventForCurrentUserLocked(captor.capture()); assertThat(captor.getAllValues().get(0), allOf(displayId(Display.DEFAULT_DISPLAY), - a11yWindowId(currentActiveWindowId), + eventWindowId(currentActiveWindowId), a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE))); assertThat(captor.getAllValues().get(1), allOf(displayId(Display.DEFAULT_DISPLAY), - a11yWindowId(eventWindowId), + eventWindowId(eventWindowId), a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE))); } @@ -661,7 +745,7 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest { .sendAccessibilityEventForCurrentUserLocked(captor.capture()); assertThat(captor.getAllValues().get(0), allOf(displayId(Display.DEFAULT_DISPLAY), - a11yWindowId(eventWindowId), + eventWindowId(eventWindowId), a11yWindowChanges( AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED))); } @@ -710,12 +794,12 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest { .sendAccessibilityEventForCurrentUserLocked(captor.capture()); assertThat(captor.getAllValues().get(0), allOf(displayId(initialDisplayId), - a11yWindowId(initialWindowId), + eventWindowId(initialWindowId), a11yWindowChanges( AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED))); assertThat(captor.getAllValues().get(1), allOf(displayId(eventDisplayId), - a11yWindowId(eventWindowId), + eventWindowId(eventWindowId), a11yWindowChanges( AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED))); } @@ -771,11 +855,11 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest { .sendAccessibilityEventForCurrentUserLocked(captor.capture()); assertThat(captor.getAllValues().get(0), allOf(displayId(Display.DEFAULT_DISPLAY), - a11yWindowId(eventWindowId), + eventWindowId(eventWindowId), a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE))); assertThat(captor.getAllValues().get(1), allOf(displayId(Display.DEFAULT_DISPLAY), - a11yWindowId(currentActiveWindowId), + eventWindowId(currentActiveWindowId), a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE))); } @@ -979,7 +1063,7 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest { .sendAccessibilityEventForCurrentUserLocked(captor.capture()); assertThat(captor.getAllValues().get(0), allOf(displayId(Display.DEFAULT_DISPLAY), - a11yWindowId(windowId), + eventWindowId(windowId), a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED))); } @@ -1001,7 +1085,7 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest { .sendAccessibilityEventForCurrentUserLocked(captor.capture()); assertThat(captor.getAllValues().get(0), allOf(displayId(Display.DEFAULT_DISPLAY), - a11yWindowId(windowId), + eventWindowId(windowId), a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED))); } @@ -1019,7 +1103,7 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest { .sendAccessibilityEventForCurrentUserLocked(captor.capture()); assertThat(captor.getAllValues().get(0), allOf(displayId(Display.DEFAULT_DISPLAY), - a11yWindowId(windowId), + eventWindowId(windowId), a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE))); } @@ -1173,8 +1257,6 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest { private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) { final WindowInfo windowInfo = WindowInfo.obtain(); - // TODO(b/325341171): add tests with various kinds of windows such as - // changing window types, touchable or not, trusted or not, etc. windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION; windowInfo.token = windowToken.asBinder(); @@ -1235,16 +1317,16 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest { } } - static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> { + static class EventWindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> { private int mWindowId; - WindowIdMatcher(int windowId) { + EventWindowIdMatcher(int windowId) { super(); mWindowId = windowId; } - static WindowIdMatcher a11yWindowId(int windowId) { - return new WindowIdMatcher(windowId); + static EventWindowIdMatcher eventWindowId(int windowId) { + return new EventWindowIdMatcher(windowId); } @Override @@ -1280,4 +1362,27 @@ public class AccessibilityWindowManagerWithAccessibilityWindowTest { description.appendText("Matching to window changes " + mWindowChanges); } } + + static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityWindowInfo> { + private final int mWindowId; + + WindowIdMatcher(int windowId) { + super(); + mWindowId = windowId; + } + + static WindowIdMatcher windowId(int windowId) { + return new WindowIdMatcher(windowId); + } + + @Override + protected boolean matchesSafely(AccessibilityWindowInfo window) { + return window.getId() == mWindowId; + } + + @Override + public void describeTo(Description description) { + description.appendText("Matching to windowId " + mWindowId); + } + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 26cda65309ad..f29d215afb7d 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -6223,6 +6223,52 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testSensitiveAdjustmentsLogged() throws Exception { + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); + + // Set up notifications that will be adjusted + final NotificationRecord r1 = spy(generateNotificationRecord( + mTestNotificationChannel, 1, null, true)); + when(r1.getLifespanMs(anyLong())).thenReturn(1); + + r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId()); + mService.addEnqueuedNotification(r1); + + // Test an adjustment for an enqueued notification + Bundle signals = new Bundle(); + signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, true); + Adjustment adjustment1 = new Adjustment( + r1.getSbn().getPackageName(), r1.getKey(), signals, "", + r1.getUser().getIdentifier()); + mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment1); + assertTrue(mService.checkLastSensitiveLog(false, true, 1)); + + // Set up notifications that will be adjusted + final NotificationRecord r2 = spy(generateNotificationRecord( + mTestNotificationChannel, 1, null, true)); + when(r2.getLifespanMs(anyLong())).thenReturn(2); + + r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId()); + mService.addNotification(r2); + Adjustment adjustment2 = new Adjustment( + r2.getSbn().getPackageName(), r2.getKey(), signals, "", + r2.getUser().getIdentifier()); + mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment2); + assertTrue(mService.checkLastSensitiveLog(true, true, 2)); + + signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, false); + Adjustment adjustment3 = new Adjustment( + r2.getSbn().getPackageName(), r2.getKey(), signals, "", + r2.getUser().getIdentifier()); + mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment3); + assertTrue(mService.checkLastSensitiveLog(true, false, 2)); + } + + @Test public void testAdjustmentToImportanceNone_cancelsNotification() throws Exception { NotificationManagerService.WorkerHandler handler = mock( NotificationManagerService.WorkerHandler.class); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java index 6976ec3b0465..07d25dfd814e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java @@ -45,6 +45,13 @@ public class TestableNotificationManagerService extends NotificationManagerServi ComponentPermissionChecker permissionChecker; + private static class SensitiveLog { + public boolean hasPosted; + public boolean hasSensitiveContent; + public long lifetime; + } + public SensitiveLog lastSensitiveLog = null; + TestableNotificationManagerService(Context context, NotificationRecordLogger logger, InstanceIdSequence notificationInstanceIdSequence) { super(context, logger, notificationInstanceIdSequence); @@ -167,6 +174,15 @@ public class TestableNotificationManagerService extends NotificationManagerServi return permissionChecker.check(permission, uid, owningUid, exported); } + @Override + protected void logSensitiveAdjustmentReceived(boolean hasPosted, boolean hasSensitiveContent, + int lifetimeMs) { + lastSensitiveLog = new SensitiveLog(); + lastSensitiveLog.hasPosted = hasPosted; + lastSensitiveLog.hasSensitiveContent = hasSensitiveContent; + lastSensitiveLog.lifetime = lifetimeMs; + } + public class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker { private int mGetStrongAuthForUserReturnValue = 0; StrongAuthTrackerFake(Context context) { @@ -183,6 +199,15 @@ public class TestableNotificationManagerService extends NotificationManagerServi } } + public boolean checkLastSensitiveLog(boolean hasPosted, boolean hasSensitive, int lifetime) { + if (lastSensitiveLog == null) { + return false; + } + return hasPosted == lastSensitiveLog.hasPosted + && hasSensitive == lastSensitiveLog.hasSensitiveContent + && lifetime == lastSensitiveLog.lifetime; + } + public interface ComponentPermissionChecker { int check(String permission, int uid, int owningUid, boolean exported); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 961fdfb14bf3..f506e9fbbb14 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -1823,6 +1823,66 @@ public class TaskTests extends WindowTestsBase { } @Test + public void testAssignChildLayers_boostedDecorSurfacePlacement() { + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); + final ActivityRecord unembeddedActivity = task.getTopMostActivity(); + + final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer); + final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer); + final SurfaceControl.Transaction t = task.getSyncTransaction(); + final SurfaceControl.Transaction clientTransaction = mock(SurfaceControl.Transaction.class); + + doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded(); + spyOn(unembeddedActivity); + spyOn(fragment1); + spyOn(fragment2); + + doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid); + doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(false).when(fragment2).isAllowedToBeEmbeddedInTrustedMode(); + doReturn(true).when(fragment1).isVisible(); + + task.moveOrCreateDecorSurfaceFor(fragment1); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should be placed above all the windows when boosted and the cover + // surface should show. + task.setDecorSurfaceBoosted(fragment1, true /* isBoosted */, clientTransaction); + + verify(unembeddedActivity).assignLayer(t, 0); + verify(fragment1).assignLayer(t, 1); + verify(fragment2).assignLayer(t, 2); + verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 3); + + verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true); + verify(t).merge(clientTransaction); + + clearInvocations(t); + clearInvocations(unembeddedActivity); + clearInvocations(fragment1); + clearInvocations(fragment2); + + // The decor surface should be placed just above the owner TaskFragment and the cover + // surface should hide. + task.moveOrCreateDecorSurfaceFor(fragment1); + task.setDecorSurfaceBoosted(fragment1, false /* isBoosted */, clientTransaction); + + verify(unembeddedActivity).assignLayer(t, 0); + verify(fragment1).assignLayer(t, 1); + verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2); + verify(fragment2).assignLayer(t, 3); + + verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true); + verify(t).merge(clientTransaction); + + } + + @Test public void testMoveTaskFragmentsToBottomIfNeeded() { final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt index 8faf22440828..9f14b136861f 100644 --- a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt +++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt @@ -17,28 +17,25 @@ package com.android.input.screenshot import androidx.test.platform.app.InstrumentationRegistry -import platform.test.screenshot.GoldenImagePathManager +import platform.test.screenshot.GoldenPathManager import platform.test.screenshot.PathConfig -/** A [GoldenImagePathManager] that should be used for all Input screenshot tests. */ -class InputGoldenImagePathManager( - pathConfig: PathConfig, - assetsPathRelativeToBuildRoot: String -) : - GoldenImagePathManager( - appContext = InstrumentationRegistry.getInstrumentation().context, - assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot, - deviceLocalPath = - InstrumentationRegistry.getInstrumentation() - .targetContext - .filesDir - .absolutePath - .toString() + "/input_screenshots", - pathConfig = pathConfig, - ) { +/** A [GoldenPathManager] that should be used for all Input screenshot tests. */ +class InputGoldenPathManager(pathConfig: PathConfig, assetsPathRelativeToBuildRoot: String) : + GoldenPathManager( + appContext = InstrumentationRegistry.getInstrumentation().context, + assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot, + deviceLocalPath = + InstrumentationRegistry.getInstrumentation() + .targetContext + .filesDir + .absolutePath + .toString() + "/input_screenshots", + pathConfig = pathConfig, + ) { override fun toString(): String { // This string is appended to all actual/expected screenshots on the device, so make sure // it is a static value. - return "InputGoldenImagePathManager" + return "InputGoldenPathManager" } -}
\ No newline at end of file +} diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt index 75dab41d3609..2f408964fd8c 100644 --- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt +++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt @@ -44,7 +44,7 @@ class InputScreenshotTestRule( private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) private val screenshotRule = ScreenshotTestRule( - InputGoldenImagePathManager( + InputGoldenPathManager( getEmulatedDevicePathConfig(emulationSpec), assetsPathRelativeToBuildRoot ) |