diff options
315 files changed, 7718 insertions, 2425 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index e0b919a8dc4a..173cf6c5f46b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4482,7 +4482,7 @@ package android.app { method @CallSuper public void onActionModeStarted(android.view.ActionMode); method public void onActivityReenter(int, android.content.Intent); method protected void onActivityResult(int, int, android.content.Intent); - method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @NonNull android.content.Intent, @NonNull android.app.ComponentCaller); + method @FlaggedApi("android.security.content_uri_permission_apis") public void onActivityResult(int, int, @Nullable android.content.Intent, @NonNull android.app.ComponentCaller); method @Deprecated public void onAttachFragment(android.app.Fragment); method public void onAttachedToWindow(); method @Deprecated public void onBackPressed(); @@ -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 5645119b2f6d..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 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 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 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 @@ -4912,7 +4901,6 @@ package android.hardware.camera2.extension { method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat(); method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize(); method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface(); - method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int); method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setDynamicRangeProfile(long); } @@ -4923,6 +4911,7 @@ package android.hardware.camera2.extension { @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration { ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest); + method @FlaggedApi("com.android.internal.camera.flags.extension_10_bit") public void setColorSpace(int); } @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration { @@ -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/api/test-current.txt b/core/api/test-current.txt index 892567c6a587..bc45a76d861b 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -42,6 +42,7 @@ package android { field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE"; field public static final String READ_WRITE_SYNC_DISABLED_MODE_CONFIG = "android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG"; field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; + field @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") public static final String RECORD_SENSITIVE_CONTENT = "android.permission.RECORD_SENSITIVE_CONTENT"; field public static final String REMAP_MODIFIER_KEYS = "android.permission.REMAP_MODIFIER_KEYS"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String REQUEST_UNIQUE_ID_ATTESTATION = "android.permission.REQUEST_UNIQUE_ID_ATTESTATION"; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index afbefca0cefe..1cc2d25fb76d 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -7473,7 +7473,7 @@ public class Activity extends ContextThemeWrapper * intent. */ @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) - public void onActivityResult(int requestCode, int resultCode, @NonNull Intent data, + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data, @NonNull ComponentCaller caller) { onActivityResult(requestCode, resultCode, data); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index af56cb4d55b2..ed0c9338d612 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -272,10 +272,17 @@ class ContextImpl extends Context { @UnsupportedAppUsage private Context mOuterContext; + + private final Object mThemeLock = new Object(); + @UnsupportedAppUsage + @GuardedBy("mThemeLock") private int mThemeResource = 0; + @UnsupportedAppUsage + @GuardedBy("mThemeLock") private Resources.Theme mTheme = null; + @UnsupportedAppUsage private PackageManager mPackageManager; private Context mReceiverRestrictedContext = null; @@ -288,7 +295,6 @@ class ContextImpl extends Context { private ContentCaptureOptions mContentCaptureOptions = null; - private final Object mSync = new Object(); /** * Indicates this {@link Context} can not handle UI components properly and is not associated * with a {@link Display} instance. @@ -340,21 +346,18 @@ class ContextImpl extends Context { */ private boolean mOwnsToken = false; - @GuardedBy("mSync") - private File mDatabasesDir; - @GuardedBy("mSync") - @UnsupportedAppUsage - private File mPreferencesDir; - @GuardedBy("mSync") - private File mFilesDir; - @GuardedBy("mSync") - private File mCratesDir; - @GuardedBy("mSync") - private File mNoBackupFilesDir; - @GuardedBy("mSync") - private File mCacheDir; - @GuardedBy("mSync") - private File mCodeCacheDir; + private final Object mDirsLock = new Object(); + private volatile File mDatabasesDir; + @UnsupportedAppUsage private volatile File mPreferencesDir; + private volatile File mFilesDir; + private volatile File mCratesDir; + private volatile File mNoBackupFilesDir; + private volatile File[] mExternalFilesDirs; + private volatile File[] mObbDirs; + private volatile File mCacheDir; + private volatile File mCodeCacheDir; + private volatile File[] mExternalCacheDirs; + private volatile File[] mExternalMediaDirs; // The system service cache for the system services that are cached per-ContextImpl. @UnsupportedAppUsage @@ -458,7 +461,7 @@ class ContextImpl extends Context { @Override public void setTheme(int resId) { - synchronized (mSync) { + synchronized (mThemeLock) { if (mThemeResource != resId) { mThemeResource = resId; initializeTheme(); @@ -468,14 +471,14 @@ class ContextImpl extends Context { @Override public int getThemeResId() { - synchronized (mSync) { + synchronized (mThemeLock) { return mThemeResource; } } @Override public Resources.Theme getTheme() { - synchronized (mSync) { + synchronized (mThemeLock) { if (mTheme != null) { return mTheme; } @@ -488,6 +491,7 @@ class ContextImpl extends Context { } } + @GuardedBy("mThemeLock") private void initializeTheme() { if (mTheme == null) { mTheme = mResources.newTheme(); @@ -597,12 +601,18 @@ class ContextImpl extends Context { if (sp == null) { checkMode(mode); if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) { - if (isCredentialProtectedStorage() - && !getSystemService(UserManager.class) - .isUserUnlockingOrUnlocked(UserHandle.myUserId())) { - throw new IllegalStateException("SharedPreferences in credential encrypted " - + "storage are not available until after user (id " - + UserHandle.myUserId() + ") is unlocked"); + if (isCredentialProtectedStorage()) { + final UserManager um = getSystemService(UserManager.class); + if (um == null) { + throw new IllegalStateException("SharedPreferences cannot be accessed " + + "if UserManager is not available. " + + "(e.g. from inside an isolated process)"); + } + if (!um.isUserUnlockingOrUnlocked(UserHandle.myUserId())) { + throw new IllegalStateException("SharedPreferences in " + + "credential encrypted storage are not available until after " + + "user (id " + UserHandle.myUserId() + ") is unlocked"); + } } } sp = new SharedPreferencesImpl(file, mode); @@ -731,12 +741,18 @@ class ContextImpl extends Context { @UnsupportedAppUsage private File getPreferencesDir() { - synchronized (mSync) { - if (mPreferencesDir == null) { - mPreferencesDir = new File(getDataDir(), "shared_prefs"); + File localPreferencesDir = mPreferencesDir; + if (localPreferencesDir == null) { + synchronized (mDirsLock) { + localPreferencesDir = mPreferencesDir; + if (localPreferencesDir == null) { + localPreferencesDir = new File(getDataDir(), "shared_prefs"); + ensurePrivateDirExists(localPreferencesDir); + mPreferencesDir = localPreferencesDir; + } } - return ensurePrivateDirExists(mPreferencesDir); } + return localPreferencesDir; } @Override @@ -778,16 +794,16 @@ class ContextImpl extends Context { /** * Common-path handling of app data dir creation */ - private static File ensurePrivateDirExists(File file) { - return ensurePrivateDirExists(file, 0771, -1, null); + private static void ensurePrivateDirExists(File file) { + ensurePrivateDirExists(file, 0771, -1, null); } - private static File ensurePrivateCacheDirExists(File file, String xattr) { + private static void ensurePrivateCacheDirExists(File file, String xattr) { final int gid = UserHandle.getCacheAppGid(Process.myUid()); - return ensurePrivateDirExists(file, 02771, gid, xattr); + ensurePrivateDirExists(file, 02771, gid, xattr); } - private static File ensurePrivateDirExists(File file, int mode, int gid, String xattr) { + private static void ensurePrivateDirExists(File file, int mode, int gid, String xattr) { if (!file.exists()) { final String path = file.getAbsolutePath(); try { @@ -815,17 +831,22 @@ class ContextImpl extends Context { } } } - return file; } @Override public File getFilesDir() { - synchronized (mSync) { - if (mFilesDir == null) { - mFilesDir = new File(getDataDir(), "files"); + File localFilesDir = mFilesDir; + if (localFilesDir == null) { + localFilesDir = mFilesDir; + synchronized (mDirsLock) { + if (localFilesDir == null) { + localFilesDir = new File(getDataDir(), "files"); + ensurePrivateDirExists(localFilesDir); + mFilesDir = localFilesDir; + } } - return ensurePrivateDirExists(mFilesDir); } + return localFilesDir; } @Override @@ -835,25 +856,37 @@ class ContextImpl extends Context { final Path absoluteNormalizedCratePath = cratesRootPath.resolve(crateId) .toAbsolutePath().normalize(); - synchronized (mSync) { - if (mCratesDir == null) { - mCratesDir = cratesRootPath.toFile(); + File localCratesDir = mCratesDir; + if (localCratesDir == null) { + synchronized (mDirsLock) { + localCratesDir = mCratesDir; + if (localCratesDir == null) { + localCratesDir = cratesRootPath.toFile(); + ensurePrivateDirExists(localCratesDir); + mCratesDir = localCratesDir; + } } - ensurePrivateDirExists(mCratesDir); } - File cratedDir = absoluteNormalizedCratePath.toFile(); - return ensurePrivateDirExists(cratedDir); + File crateDir = absoluteNormalizedCratePath.toFile(); + ensurePrivateDirExists(crateDir); + return crateDir; } @Override public File getNoBackupFilesDir() { - synchronized (mSync) { - if (mNoBackupFilesDir == null) { - mNoBackupFilesDir = new File(getDataDir(), "no_backup"); + File localNoBackupFilesDir = mNoBackupFilesDir; + if (localNoBackupFilesDir == null) { + synchronized (mDirsLock) { + localNoBackupFilesDir = mNoBackupFilesDir; + if (localNoBackupFilesDir == null) { + localNoBackupFilesDir = new File(getDataDir(), "no_backup"); + ensurePrivateDirExists(localNoBackupFilesDir); + mNoBackupFilesDir = localNoBackupFilesDir; + } } - return ensurePrivateDirExists(mNoBackupFilesDir); } + return localNoBackupFilesDir; } @Override @@ -865,13 +898,24 @@ class ContextImpl extends Context { @Override public File[] getExternalFilesDirs(String type) { - synchronized (mSync) { - File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName()); - if (type != null) { - dirs = Environment.buildPaths(dirs, type); + File[] localExternalFilesDirs = mExternalFilesDirs; + if (localExternalFilesDirs == null) { + synchronized (mDirsLock) { + localExternalFilesDirs = mExternalFilesDirs; + if (localExternalFilesDirs == null) { + localExternalFilesDirs = + Environment.buildExternalStorageAppFilesDirs(getPackageName()); + if (type != null) { + localExternalFilesDirs = + Environment.buildPaths(localExternalFilesDirs, type); + } + localExternalFilesDirs = ensureExternalDirsExistOrFilter( + localExternalFilesDirs, true /* tryCreateInProcess */); + mExternalFilesDirs = localExternalFilesDirs; + } } - return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */); } + return localExternalFilesDirs; } @Override @@ -883,30 +927,51 @@ class ContextImpl extends Context { @Override public File[] getObbDirs() { - synchronized (mSync) { - File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName()); - return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */); + File[] localObbDirs = mObbDirs; + if (mObbDirs == null) { + synchronized (mDirsLock) { + localObbDirs = mObbDirs; + if (localObbDirs == null) { + localObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName()); + localObbDirs = ensureExternalDirsExistOrFilter( + localObbDirs, true /* tryCreateInProcess */); + mObbDirs = localObbDirs; + } + } } + return localObbDirs; } @Override public File getCacheDir() { - synchronized (mSync) { - if (mCacheDir == null) { - mCacheDir = new File(getDataDir(), "cache"); + File localCacheDir = mCacheDir; + if (localCacheDir == null) { + synchronized (mDirsLock) { + localCacheDir = mCacheDir; + if (localCacheDir == null) { + localCacheDir = new File(getDataDir(), "cache"); + ensurePrivateCacheDirExists(localCacheDir, XATTR_INODE_CACHE); + mCacheDir = localCacheDir; + } } - return ensurePrivateCacheDirExists(mCacheDir, XATTR_INODE_CACHE); } + return localCacheDir; } @Override public File getCodeCacheDir() { - synchronized (mSync) { - if (mCodeCacheDir == null) { - mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir()); + File localCodeCacheDir = mCodeCacheDir; + if (localCodeCacheDir == null) { + synchronized (mDirsLock) { + localCodeCacheDir = mCodeCacheDir; + if (localCodeCacheDir == null) { + localCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir()); + ensurePrivateCacheDirExists(localCodeCacheDir, XATTR_INODE_CODE_CACHE); + mCodeCacheDir = localCodeCacheDir; + } } - return ensurePrivateCacheDirExists(mCodeCacheDir, XATTR_INODE_CODE_CACHE); } + return localCodeCacheDir; } /** @@ -927,21 +992,37 @@ class ContextImpl extends Context { @Override public File[] getExternalCacheDirs() { - synchronized (mSync) { - File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName()); - // We don't try to create cache directories in-process, because they need special - // setup for accurate quota tracking. This ensures the cache dirs are always - // created through StorageManagerService. - return ensureExternalDirsExistOrFilter(dirs, false /* tryCreateInProcess */); + File[] localExternalCacheDirs = mExternalCacheDirs; + if (localExternalCacheDirs == null) { + synchronized (mDirsLock) { + localExternalCacheDirs = mExternalCacheDirs; + if (localExternalCacheDirs == null) { + localExternalCacheDirs = + Environment.buildExternalStorageAppCacheDirs(getPackageName()); + localExternalCacheDirs = ensureExternalDirsExistOrFilter( + localExternalCacheDirs, false /* tryCreateInProcess */); + mExternalCacheDirs = localExternalCacheDirs; + } + } } + return localExternalCacheDirs; } @Override public File[] getExternalMediaDirs() { - synchronized (mSync) { - File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName()); - return ensureExternalDirsExistOrFilter(dirs, true /* tryCreateInProcess */); + File[] localExternalMediaDirs = mExternalMediaDirs; + if (localExternalMediaDirs == null) { + synchronized (mDirsLock) { + localExternalMediaDirs = mExternalMediaDirs; + if (localExternalMediaDirs == null) { + localExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName()); + localExternalMediaDirs = ensureExternalDirsExistOrFilter( + localExternalMediaDirs, true /* tryCreateInProcess */); + mExternalMediaDirs = localExternalMediaDirs; + } + } } + return localExternalMediaDirs; } /** @@ -1040,16 +1121,22 @@ class ContextImpl extends Context { } private File getDatabasesDir() { - synchronized (mSync) { - if (mDatabasesDir == null) { - if ("android".equals(getPackageName())) { - mDatabasesDir = new File("/data/system"); - } else { - mDatabasesDir = new File(getDataDir(), "databases"); + File localDatabasesDir = mDatabasesDir; + if (localDatabasesDir == null) { + synchronized (mDirsLock) { + localDatabasesDir = mDatabasesDir; + if (localDatabasesDir == null) { + if ("android".equals(getPackageName())) { + localDatabasesDir = new File("/data/system"); + } else { + localDatabasesDir = new File(getDataDir(), "databases"); + } + ensurePrivateDirExists(localDatabasesDir); + mDatabasesDir = localDatabasesDir; } } - return ensurePrivateDirExists(mDatabasesDir); } + return localDatabasesDir; } @Override diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java index 4ce983f6019b..3e7d66563c5e 100644 --- a/core/java/android/app/GrammaticalInflectionManager.java +++ b/core/java/android/app/GrammaticalInflectionManager.java @@ -125,7 +125,10 @@ public class GrammaticalInflectionManager { /** * Get the current grammatical gender of privileged application from the encrypted file. * - * @return the value of grammatical gender + * @return the value of system grammatical gender only if the calling app has the permission, + * otherwise throwing an exception. + * + * @throws SecurityException if the caller does not have the required permission. * * @see Configuration#getGrammaticalGender */ diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 79cb09d5baea..cd4c0bc4524d 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -7531,6 +7531,9 @@ public class Notification implements Parcelable /** * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is * attached to. + * <p> + * Note: Calling build() multiple times returns the same Notification instance, + * so reusing a builder to create multiple Notifications is discouraged. * * @return the fully constructed Notification. */ 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/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index 986205a346f7..9ef8b38666c6 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -189,10 +189,13 @@ public final class DeviceAdminInfo implements Parcelable { @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED) public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2; + /** + * @hide + */ @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED, HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER}) @Retention(RetentionPolicy.SOURCE) - private @interface HeadlessDeviceOwnerMode {} + public @interface HeadlessDeviceOwnerMode {} /** @hide */ public static class PolicyInfo { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 620bbaf4bbf5..cb4ed058af33 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -53,6 +53,7 @@ import static android.Manifest.permission.QUERY_ADMIN_POLICY; import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; +import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED; import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED; import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED; @@ -93,6 +94,7 @@ import android.app.Activity; import android.app.IServiceConnection; import android.app.KeyguardManager; import android.app.admin.SecurityLog.SecurityEvent; +import android.app.admin.flags.Flags; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; @@ -17526,4 +17528,25 @@ public class DevicePolicyManager { } return -1; } + + /** + * @return The headless device owner mode for the current set DO, returns + * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED} if no DO is set. + * + * @hide + */ + @DeviceAdminInfo.HeadlessDeviceOwnerMode + public int getHeadlessDeviceOwnerMode() { + if (!Flags.headlessDeviceOwnerProvisioningFixEnabled()) { + return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; + } + if (mService != null) { + try { + return mService.getHeadlessDeviceOwnerMode(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; + } }
\ No newline at end of file diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 3a7a891c7995..03d0b0f88bc0 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -625,4 +625,6 @@ interface IDevicePolicyManager { void setMaxPolicyStorageLimit(String packageName, int storageLimit); int getMaxPolicyStorageLimit(String packageName); + + int getHeadlessDeviceOwnerMode(String callerPackageName); } diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 19270199696e..c29ea6d95dcc 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -163,3 +163,13 @@ flag { description: "Enable UX changes for esim management" bug: "295301164" } + +flag { + name: "headless_device_owner_provisioning_fix_enabled" + namespace: "enterprise" + description: "Fix provisioning for single-user headless DO" + bug: "289515470" + metadata { + purpose: PURPOSE_BUGFIX + } +} 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 749f218b0e6a..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; /** @@ -180,6 +180,16 @@ public final class CameraExtensionCharacteristics { EXTENSION_HDR, EXTENSION_NIGHT}; + /** + * List of synthetic CameraCharacteristics keys that are supported in the extensions. + */ + private static final List<CameraCharacteristics.Key> + SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS = + Arrays.asList( + CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES, + CameraCharacteristics.REQUEST_AVAILABLE_COLOR_SPACE_PROFILES + ); + private final Context mContext; private final String mCameraId; private final Map<String, CameraCharacteristics> mCharacteristicsMap; @@ -549,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); } @@ -752,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); } @@ -874,11 +884,17 @@ public final class CameraExtensionCharacteristics { Class<CameraCharacteristics.Key<?>> keyTyped = (Class<CameraCharacteristics.Key<?>>) key; - // Do not include synthetic keys. Including synthetic keys leads to undefined - // behavior. This causes inclusion of capabilities that may not be supported in - // camera extensions. ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys, /*includeSynthetic*/ false)); + + // Add synthetic keys to the available key list if they are part of the supported + // synthetic camera characteristic key list + for (CameraCharacteristics.Key charKey : + SUPPORTED_SYNTHETIC_CAMERA_CHARACTERISTICS) { + if (chars.get(charKey) != null) { + ret.add(charKey); + } + } } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension for all available keys! Extension " @@ -990,6 +1006,7 @@ public final class CameraExtensionCharacteristics { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: case ImageFormat.JPEG_R: + case ImageFormat.YCBCR_P010: break; default: throw new IllegalArgumentException("Unsupported format: " + format); @@ -1021,8 +1038,9 @@ public final class CameraExtensionCharacteristics { return generateJpegSupportedSizes( extenders.second.getSupportedPostviewResolutions(sz), streamMap); - } else if (format == ImageFormat.JPEG_R) { - // Jpeg_R/UltraHDR is currently not supported in the basic extension case + } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) { + // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the basic + // extension case return new ArrayList<>(); } else { throw new IllegalArgumentException("Unsupported format: " + format); @@ -1118,16 +1136,16 @@ public final class CameraExtensionCharacteristics { * * <p>Device-specific extensions currently support at most three * multi-frame capture surface formats. ImageFormat.JPEG will be supported by all - * extensions while ImageFormat.YUV_420_888 and ImageFormat.JPEG_R may or may not be - * supported.</p> + * extensions while ImageFormat.YUV_420_888, ImageFormat.JPEG_R, or ImageFormat.YCBCR_P010 + * may or may not be supported.</p> * * @param extension the extension type * @param format device-specific extension output format * @return non-modifiable list of available sizes or an empty list if the format is not * supported. * @throws IllegalArgumentException in case of format different from ImageFormat.JPEG, - * ImageFormat.YUV_420_888, ImageFormat.JPEG_R; or - * unsupported extension. + * ImageFormat.YUV_420_888, ImageFormat.JPEG_R, + * ImageFormat.YCBCR_P010; or unsupported extension. */ public @NonNull List<Size> getExtensionSupportedSizes(@Extension int extension, int format) { @@ -1151,6 +1169,7 @@ public final class CameraExtensionCharacteristics { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: case ImageFormat.JPEG_R: + case ImageFormat.YCBCR_P010: break; default: throw new IllegalArgumentException("Unsupported format: " + format); @@ -1183,8 +1202,9 @@ public final class CameraExtensionCharacteristics { } else { return generateSupportedSizes(null, format, streamMap); } - } else if (format == ImageFormat.JPEG_R) { - // Jpeg_R/UltraHDR is currently not supported in the basic extension case + } else if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) { + // Jpeg_R/UltraHDR + YCBCR_P010 is currently not supported in the + // basic extension case return new ArrayList<>(); } else { throw new IllegalArgumentException("Unsupported format: " + format); @@ -1213,7 +1233,8 @@ public final class CameraExtensionCharacteristics { * @return the range of estimated minimal and maximal capture latency in milliseconds * or null if no capture latency info can be provided * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG}, - * {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R}; + * {@link ImageFormat#YUV_420_888}, {@link ImageFormat#JPEG_R} + * {@link ImageFormat#YCBCR_P010}; * or unsupported extension. */ public @Nullable Range<Long> getEstimatedCaptureLatencyRangeMillis(@Extension int extension, @@ -1222,6 +1243,7 @@ public final class CameraExtensionCharacteristics { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: case ImageFormat.JPEG_R: + case ImageFormat.YCBCR_P010: //No op break; default: @@ -1269,8 +1291,8 @@ public final class CameraExtensionCharacteristics { // specific and cannot be estimated accurately enough. return null; } - if (format == ImageFormat.JPEG_R) { - // JpegR/UltraHDR is not supported for basic extensions + if (format == ImageFormat.JPEG_R || format == ImageFormat.YCBCR_P010) { + // JpegR/UltraHDR + YCBCR_P010 is not supported for basic extensions return null; } @@ -1522,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/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java index 4895f38d7328..8fa09a802aa4 100644 --- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java +++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java @@ -61,7 +61,6 @@ public abstract class AdvancedExtender { private CameraUsageTracker mCameraUsageTracker; private static final String TAG = "AdvancedExtender"; - /** * Initialize a camera extension advanced extender instance. * @@ -263,6 +262,13 @@ public abstract class AdvancedExtender { * * <p>For example, an extension may limit the zoom ratio range. In this case, an OEM can return * a new zoom ratio range for the key {@link CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE}. + * + * <p> Currently, the only synthetic keys supported for override are + * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES} and + * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES}. To enable them, an OEM + * should override the respective native keys + * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP} and + * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP}. */ @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET) @NonNull diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl index 509bcb8e3d23..5567bed7f128 100644 --- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl +++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl @@ -27,6 +27,7 @@ parcelable CameraOutputConfig int imageFormat; int capacity; long usage; + long dynamicRangeProfile; const int TYPE_SURFACE = 0; const int TYPE_IMAGEREADER = 1; diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java index 53f56bc9f896..001b79499b1a 100644 --- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java +++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java @@ -133,15 +133,4 @@ public final class CameraOutputSurface { @DynamicRangeProfiles.Profile long dynamicRangeProfile) { mOutputSurface.dynamicRangeProfile = dynamicRangeProfile; } - - /** - * Set the color space. The default colorSpace - * will be - * {@link android.hardware.camera2.params.ColorSpaceProfiles.UNSPECIFIED} - * unless explicitly set using this method. - */ - @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT) - public void setColorSpace(int colorSpace) { - mOutputSurface.colorSpace = colorSpace; - } } diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl index 84ca2b63fbcf..9d46b559ce37 100644 --- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl +++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl @@ -25,4 +25,5 @@ parcelable CameraSessionConfig CameraMetadataNative sessionParameter; int sessionTemplateId; int sessionType; + int colorSpace = -1; } diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java index 96c88e660e10..84b7a7fc1349 100644 --- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java +++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.params.ColorSpaceProfiles; import android.os.IBinder; import com.android.internal.camera.flags.Flags; @@ -48,6 +49,7 @@ public class ExtensionConfiguration { private final int mSessionTemplateId; private final List<ExtensionOutputConfiguration> mOutputs; private final CaptureRequest mSessionParameters; + private int mColorSpace; /** * Initialize an extension configuration instance @@ -72,6 +74,18 @@ public class ExtensionConfiguration { mSessionTemplateId = sessionTemplateId; mOutputs = outputs; mSessionParameters = sessionParams; + mColorSpace = ColorSpaceProfiles.UNSPECIFIED; + } + + /** + * Set the color space using the ordinal value of a + * {@link android.graphics.ColorSpace.Named}. + * The default will be -1, indicating an unspecified ColorSpace, + * unless explicitly set using this method. + */ + @FlaggedApi(Flags.FLAG_EXTENSION_10_BIT) + public void setColorSpace(int colorSpace) { + mColorSpace = colorSpace; } @FlaggedApi(Flags.FLAG_CONCERT_MODE) @@ -84,6 +98,11 @@ public class ExtensionConfiguration { ret.sessionTemplateId = mSessionTemplateId; ret.sessionType = mSessionType; ret.outputConfigs = new ArrayList<>(mOutputs.size()); + if (Flags.extension10Bit()) { + ret.colorSpace = mColorSpace; + } else { + ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED; + } for (ExtensionOutputConfiguration outputConfig : mOutputs) { ret.outputConfigs.add(outputConfig.getOutputConfig()); } diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java index 9dc6d7bf94b3..3a67d6192f5e 100644 --- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java +++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java @@ -20,6 +20,7 @@ import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.hardware.camera2.params.DynamicRangeProfiles; import com.android.internal.camera.flags.Flags; @@ -79,6 +80,11 @@ public class ExtensionOutputConfiguration { config.outputId = new OutputConfigId(); config.outputId.id = mOutputConfigId; config.surfaceGroupId = mSurfaceGroupId; + if (Flags.extension10Bit()) { + config.dynamicRangeProfile = surface.getDynamicRangeProfile(); + } else { + config.dynamicRangeProfile = DynamicRangeProfiles.STANDARD; + } } @Nullable CameraOutputConfig getOutputConfig() { diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index a7d6caf9d9df..6d9b51cbd003 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.content.Context; +import android.graphics.ColorSpace; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.SyncFence; @@ -49,6 +50,7 @@ import android.hardware.camera2.extension.ParcelCaptureResult; import android.hardware.camera2.extension.ParcelImage; import android.hardware.camera2.extension.ParcelTotalCaptureResult; import android.hardware.camera2.extension.Request; +import android.hardware.camera2.params.ColorSpaceProfiles; import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.OutputConfiguration; @@ -62,6 +64,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; +import android.util.IntArray; import android.util.Log; import android.util.Size; import android.view.Surface; @@ -97,6 +100,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes private Surface mClientRepeatingRequestSurface; private Surface mClientCaptureSurface; private Surface mClientPostviewSurface; + private OutputConfiguration mClientRepeatingRequestOutputConfig; + private OutputConfiguration mClientCaptureOutputConfig; + private OutputConfiguration mClientPostviewOutputConfig; private CameraCaptureSession mCaptureSession = null; private ISessionProcessorImpl mSessionProcessor = null; private final InitializeSessionHandler mInitializeHandler; @@ -142,8 +148,19 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes for (OutputConfiguration c : config.getOutputConfigurations()) { if (c.getDynamicRangeProfile() != DynamicRangeProfiles.STANDARD) { - throw new IllegalArgumentException("Unsupported dynamic range profile: " + - c.getDynamicRangeProfile()); + if (Flags.extension10Bit() && Flags.cameraExtensionsCharacteristicsGet()) { + DynamicRangeProfiles dynamicProfiles = extensionChars.get( + config.getExtension(), + CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES); + if (dynamicProfiles == null || !dynamicProfiles.getSupportedProfiles() + .contains(c.getDynamicRangeProfile())) { + throw new IllegalArgumentException("Unsupported dynamic range profile: " + + c.getDynamicRangeProfile()); + } + } else { + throw new IllegalArgumentException("Unsupported dynamic range profile: " + + c.getDynamicRangeProfile()); + } } if (c.getStreamUseCase() != CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT) { @@ -157,12 +174,26 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes config.getExtension(), SurfaceTexture.class); Surface repeatingRequestSurface = CameraExtensionUtils.getRepeatingRequestSurface( config.getOutputConfigurations(), supportedPreviewSizes); + OutputConfiguration repeatingRequestOutputConfig = null; if (repeatingRequestSurface != null) { + for (OutputConfiguration outputConfig : config.getOutputConfigurations()) { + if (outputConfig.getSurface() == repeatingRequestSurface) { + repeatingRequestOutputConfig = outputConfig; + } + } suitableSurfaceCount++; } HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>(); - for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + + IntArray supportedCaptureOutputFormats = + new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length); + supportedCaptureOutputFormats.addAll( + CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS); + if (Flags.extension10Bit()) { + supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010); + } + for (int format : supportedCaptureOutputFormats.toArray()) { List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes( config.getExtension(), format); if (supportedSizes != null) { @@ -171,7 +202,13 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes } Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface( config.getOutputConfigurations(), supportedCaptureSizes); + OutputConfiguration burstCaptureOutputConfig = null; if (burstCaptureSurface != null) { + for (OutputConfiguration outputConfig : config.getOutputConfigurations()) { + if (outputConfig.getSurface() == burstCaptureSurface) { + burstCaptureOutputConfig = outputConfig; + } + } suitableSurfaceCount++; } @@ -180,13 +217,14 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes } Surface postviewSurface = null; + OutputConfiguration postviewOutputConfig = config.getPostviewOutputConfiguration(); if (burstCaptureSurface != null && config.getPostviewOutputConfiguration() != null) { CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo = CameraExtensionUtils.querySurface(burstCaptureSurface); Size burstCaptureSurfaceSize = new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight); HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>(); - for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + for (int format : supportedCaptureOutputFormats.toArray()) { List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes( config.getExtension(), burstCaptureSurfaceSize, format); if (supportedSizesPostview != null) { @@ -207,8 +245,8 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes extender.init(cameraId, characteristicsMapNative); CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(ctx, - extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface, - burstCaptureSurface, postviewSurface, config.getStateCallback(), + extender, cameraDevice, characteristicsMapNative, repeatingRequestOutputConfig, + burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(), config.getExecutor(), sessionId, token, config.getExtension()); ret.mStatsAggregator.setClientName(ctx.getOpPackageName()); @@ -223,8 +261,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @NonNull IAdvancedExtenderImpl extender, @NonNull CameraDeviceImpl cameraDevice, Map<String, CameraMetadataNative> characteristicsMap, - @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface, - @Nullable Surface postviewSurface, + @Nullable OutputConfiguration repeatingRequestOutputConfig, + @Nullable OutputConfiguration burstCaptureOutputConfig, + @Nullable OutputConfiguration postviewOutputConfig, @NonNull StateCallback callback, @NonNull Executor executor, int sessionId, @NonNull IBinder token, @@ -235,9 +274,18 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes mCharacteristicsMap = characteristicsMap; mCallbacks = callback; mExecutor = executor; - mClientRepeatingRequestSurface = repeatingRequestSurface; - mClientCaptureSurface = burstCaptureSurface; - mClientPostviewSurface = postviewSurface; + mClientRepeatingRequestOutputConfig = repeatingRequestOutputConfig; + mClientCaptureOutputConfig = burstCaptureOutputConfig; + mClientPostviewOutputConfig = postviewOutputConfig; + if (repeatingRequestOutputConfig != null) { + mClientRepeatingRequestSurface = repeatingRequestOutputConfig.getSurface(); + } + if (burstCaptureOutputConfig != null) { + mClientCaptureSurface = burstCaptureOutputConfig.getSurface(); + } + if (postviewOutputConfig != null) { + mClientPostviewSurface = postviewOutputConfig.getSurface(); + } mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); @@ -262,9 +310,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes return; } - OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestSurface); - OutputSurface captureSurface = initializeParcelable(mClientCaptureSurface); - OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface); + OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestOutputConfig); + OutputSurface captureSurface = initializeParcelable(mClientCaptureOutputConfig); + OutputSurface postviewSurface = initializeParcelable(mClientPostviewOutputConfig); mSessionProcessor = mAdvancedExtender.getSessionProcessor(); CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken, @@ -300,6 +348,23 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR); cameraOutput.setReadoutTimestampEnabled(false); cameraOutput.setPhysicalCameraId(output.physicalCameraId); + 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); } @@ -314,7 +379,16 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType, outputList, new CameraExtensionUtils.HandlerExecutor(mHandler), new SessionStateHandler()); - + 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())) { CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest( @@ -362,21 +436,38 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes return ret; } - private static OutputSurface initializeParcelable(Surface s) { + private static OutputSurface initializeParcelable(OutputConfiguration o) { OutputSurface ret = new OutputSurface(); - if (s != null) { + + if (o != null && o.getSurface() != null) { + Surface s = o.getSurface(); ret.surface = s; ret.size = new android.hardware.camera2.extension.Size(); Size surfaceSize = SurfaceUtils.getSurfaceSize(s); ret.size.width = surfaceSize.getWidth(); ret.size.height = surfaceSize.getHeight(); ret.imageFormat = SurfaceUtils.getSurfaceFormat(s); + + if (Flags.extension10Bit()) { + ret.dynamicRangeProfile = o.getDynamicRangeProfile(); + ColorSpace colorSpace = o.getColorSpace(); + if (colorSpace != null) { + ret.colorSpace = colorSpace.getId(); + } else { + ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED; + } + } else { + ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD; + ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED; + } } else { ret.surface = null; ret.size = new android.hardware.camera2.extension.Size(); ret.size.width = -1; ret.size.height = -1; ret.imageFormat = ImageFormat.UNKNOWN; + ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD; + ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED; } return ret; diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 725b4139bb95..5b32f33777fa 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -58,12 +58,15 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; +import android.util.IntArray; import android.util.Log; import android.util.LongSparseArray; import android.util.Pair; import android.util.Size; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; @@ -183,7 +186,14 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } HashMap<Integer, List<Size>> supportedCaptureSizes = new HashMap<>(); - for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + IntArray supportedCaptureOutputFormats = + new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length); + supportedCaptureOutputFormats.addAll( + CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS); + if (Flags.extension10Bit()) { + supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010); + } + for (int format : supportedCaptureOutputFormats.toArray()) { List<Size> supportedSizes = extensionChars.getExtensionSupportedSizes( config.getExtension(), format); if (supportedSizes != null) { @@ -207,7 +217,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { Size burstCaptureSurfaceSize = new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight); HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>(); - for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + for (int format : supportedCaptureOutputFormats.toArray()) { List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes( config.getExtension(), burstCaptureSurfaceSize, format); if (supportedSizesPostview != null) { diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java index a8066aa74f95..f0c6e2e4e123 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java @@ -29,10 +29,13 @@ import android.hardware.camera2.utils.SurfaceUtils; import android.media.Image; import android.media.ImageWriter; import android.os.Handler; +import android.util.IntArray; import android.util.Log; import android.util.Size; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -130,9 +133,16 @@ public final class CameraExtensionUtils { public static Surface getBurstCaptureSurface( @NonNull List<OutputConfiguration> outputConfigs, @NonNull HashMap<Integer, List<Size>> supportedCaptureSizes) { + IntArray supportedCaptureOutputFormats = + new IntArray(CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS.length); + supportedCaptureOutputFormats.addAll( + CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS); + if (Flags.extension10Bit()) { + supportedCaptureOutputFormats.add(ImageFormat.YCBCR_P010); + } for (OutputConfiguration config : outputConfigs) { SurfaceInfo surfaceInfo = querySurface(config.getSurface()); - for (int supportedFormat : SUPPORTED_CAPTURE_OUTPUT_FORMATS) { + for (int supportedFormat : supportedCaptureOutputFormats.toArray()) { if (surfaceInfo.mFormat == supportedFormat) { Size captureSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight); if (supportedCaptureSizes.containsKey(supportedFormat)) { diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java index 905d911248ca..76888f338615 100644 --- a/core/java/android/hardware/devicestate/DeviceState.java +++ b/core/java/android/hardware/devicestate/DeviceState.java @@ -543,11 +543,13 @@ public final class DeviceState { int identifier = source.readInt(); String name = source.readString8(); ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>(); - for (int i = 0; i < source.readInt(); i++) { + int systemPropertySize = source.readInt(); + for (int i = 0; i < systemPropertySize; i++) { systemProperties.add(source.readInt()); } ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>(); - for (int j = 0; j < source.readInt(); j++) { + int physicalPropertySize = source.readInt(); + for (int j = 0; j < physicalPropertySize; j++) { physicalProperties.add(source.readInt()); } return new DeviceState.Configuration(identifier, name, systemProperties, diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index 09e6b5de8a3c..c489c5808728 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -38,7 +38,6 @@ interface IDreamManager { boolean isDreaming(); @UnsupportedAppUsage boolean isDreamingOrInPreview(); - @UnsupportedAppUsage boolean canStartDreaming(boolean isScreenOn); void finishSelf(in IBinder token, boolean immediate); void startDozing(in IBinder token, int screenState, int screenBrightness); 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 1acfc1b3bb0a..644a7a925f81 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -233,6 +233,28 @@ public class AutofillFeatureFlags { public static final String DEVICE_CONFIG_INCLUDE_INVISIBLE_VIEW_GROUP_IN_ASSIST_STRUCTURE = "include_invisible_view_group_in_assist_structure"; + /** + * Bugfix flag, Autofill should ignore views resetting to empty states. + * + * See frameworks/base/services/autofill/bugfixes.aconfig#ignore_view_state_reset_to_empty + * for more information. + * + * @hide + */ + 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 // @@ -494,6 +516,22 @@ public class AutofillFeatureFlags { false); } + /** @hide */ + public static boolean shouldIgnoreViewStateResetToEmpty() { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_AUTOFILL, + DEVICE_CONFIG_IGNORE_VIEW_STATE_RESET_TO_EMPTY, + 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/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/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index ce74848705e4..82e613e18d41 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -77,4 +77,12 @@ flag { description: "Properties to allow apps and activities to opt-in to cover display rendering" bug: "312530526" is_fixed_read_only: true +} + +flag { + namespace: "windowing_sdk" + name: "enable_wm_extensions_for_all_flag" + description: "Whether to enable WM Extensions for all devices" + bug: "306666082" + is_fixed_read_only: true }
\ No newline at end of file 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/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index cbe070048811..d4dcec948e31 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -93,6 +93,9 @@ class ZygoteConnection { throw ex; } + if (peer.getUid() != Process.SYSTEM_UID) { + throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote."); + } isEof = false; } diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp index 54c4cd50a902..e0cc055a62a6 100644 --- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp +++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp @@ -354,6 +354,18 @@ jstring com_android_internal_os_ZygoteCommandBuffer_nativeNextArg(JNIEnv* env, j return result; } +static uid_t getSocketPeerUid(int socket, const std::function<void(const std::string&)>& fail_fn) { + struct ucred credentials; + socklen_t cred_size = sizeof credentials; + if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1 + || cred_size != sizeof credentials) { + fail_fn(CREATE_ERROR("Failed to get socket credentials, %s", + strerror(errno))); + } + + return credentials.uid; +} + // Read all lines from the current command into the buffer, and then reset the buffer, so // we will start reading again at the beginning of the command, starting with the argument // count. And we don't need access to the fd to do so. @@ -413,19 +425,12 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( fail_fn_z("Failed to retrieve session socket timeout"); } - struct ucred credentials; - socklen_t cred_size = sizeof credentials; - if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1 - || cred_size != sizeof credentials) { - fail_fn_1(CREATE_ERROR("ForkRepeatedly failed to get initial credentials, %s", - strerror(errno))); + uid_t peerUid = getSocketPeerUid(session_socket, fail_fn_1); + if (peerUid != static_cast<uid_t>(expected_uid)) { + return JNI_FALSE; } - bool first_time = true; do { - if (credentials.uid != static_cast<uid_t>(expected_uid)) { - return JNI_FALSE; - } n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n); n_buffer->reset(); int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds, @@ -453,6 +458,7 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( } } for (;;) { + bool valid_session_socket = true; // Clear buffer and get count from next command. n_buffer->clear(); // Poll isn't strictly necessary for now. But without it, disconnect is hard to detect. @@ -463,25 +469,50 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) { if (n_buffer->getCount(fail_fn_z) != 0) { break; - } // else disconnected; + } else { + // Session socket was disconnected + valid_session_socket = false; + close(session_socket); + } } else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) { fail_fn_z( CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res)); } - // We've now seen either a disconnect or connect request. - close(session_socket); - int new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr)); + int new_fd = -1; + do { + // We've now seen either a disconnect or connect request. + new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr)); + if (new_fd == -1) { + fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno))); + } + uid_t newPeerUid = getSocketPeerUid(new_fd, fail_fn_1); + if (newPeerUid != static_cast<uid_t>(expected_uid)) { + ALOGW("Dropping new connection with a mismatched uid %d\n", newPeerUid); + close(new_fd); + new_fd = -1; + } else { + // If we still have a valid session socket, close it now + if (valid_session_socket) { + close(session_socket); + } + valid_session_socket = true; + } + } while (!valid_session_socket); + + // At this point we either have a valid new connection (new_fd > 0), or + // an existing session socket we can poll on if (new_fd == -1) { - fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno))); + // The new connection wasn't valid, and we still have an old one; retry polling + continue; } if (new_fd != session_socket) { - // Move new_fd back to the old value, so that we don't have to change Java-level data - // structures to reflect a change. This implicitly closes the old one. - if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) { - fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s", - new_fd, session_socket, strerror(errno))); - } - close(new_fd); // On Linux, fd is closed even if EINTR is returned. + // Move new_fd back to the old value, so that we don't have to change Java-level data + // structures to reflect a change. This implicitly closes the old one. + if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) { + fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s", + new_fd, session_socket, strerror(errno))); + } + close(new_fd); // On Linux, fd is closed even if EINTR is returned. } // If we ever return, we effectively reuse the old Java ZygoteConnection. // None of its state needs to change. @@ -493,13 +524,6 @@ jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s", session_socket, strerror(errno))); } - if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) { - fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno))); - } - if (cred_size != sizeof credentials) { - fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d", - cred_size, static_cast<int>(sizeof credentials))); - } } first_time = false; } while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n)); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 1acdc75a600e..8ea742d19047 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6916,6 +6916,12 @@ <permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" android:protectionLevel="signature" /> + <!-- @hide @TestApi Allows an application to record sensitive content during media + projection. This is intended for on device screen recording system app. + @FlaggedApi("android.permission.flags.sensitive_notification_app_protection") --> + <permission android:name="android.permission.RECORD_SENSITIVE_CONTENT" + android:protectionLevel="signature"/> + <!-- @SystemApi Allows an application to read install sessions @hide This is not a third-party API (intended for system apps). --> <permission android:name="android.permission.READ_INSTALL_SESSIONS" 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/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java index 76772b7a528b..08977265667c 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateInfoTest.java @@ -16,6 +16,12 @@ package android.hardware.devicestate; +import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY; +import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY; +import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED; +import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN; +import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; @@ -33,6 +39,7 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.List; +import java.util.Set; /** * Unit tests for {@link DeviceStateInfo}. @@ -44,11 +51,25 @@ import java.util.List; public final class DeviceStateInfoTest { private static final DeviceState DEVICE_STATE_0 = new DeviceState( - new DeviceState.Configuration.Builder(0, "STATE_0").build()); + new DeviceState.Configuration.Builder(0, "STATE_0") + .setSystemProperties( + Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS, + PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)) + .setPhysicalProperties( + Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED)) + .build()); private static final DeviceState DEVICE_STATE_1 = new DeviceState( - new DeviceState.Configuration.Builder(1, "STATE_1").build()); + new DeviceState.Configuration.Builder(1, "STATE_1") + .setSystemProperties( + Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY)) + .setPhysicalProperties( + Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_HALF_OPEN)) + .build()); private static final DeviceState DEVICE_STATE_2 = new DeviceState( - new DeviceState.Configuration.Builder(2, "STATE_2").build()); + new DeviceState.Configuration.Builder(2, "STATE_2") + .setSystemProperties( + Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY)) + .build()); @Test public void create() { diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java index 68de21f76dc8..78d4324ebb1a 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java @@ -93,4 +93,22 @@ public final class DeviceStateTest { Assert.assertEquals(originalState, new DeviceState(stateConfiguration)); } + + @Test + public void writeToParcel_noPhysicalProperties() { + final DeviceState originalState = new DeviceState( + new DeviceState.Configuration.Builder(0, "TEST_STATE") + .setSystemProperties(Set.of(PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS, + PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST)) + .build()); + + final Parcel parcel = Parcel.obtain(); + originalState.getConfiguration().writeToParcel(parcel, 0 /* flags */); + parcel.setDataPosition(0); + + final DeviceState.Configuration stateConfiguration = + DeviceState.Configuration.CREATOR.createFromParcel(parcel); + + Assert.assertEquals(originalState, new DeviceState(stateConfiguration)); + } } 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/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index 539832e3cf3c..d44033c72302 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -523,8 +523,8 @@ class ActivityEmbeddingAnimationRunner { /** * Whether we should use jump cut for the change transition. * This normally happens when opening a new secondary with the existing primary using a - * different split layout. This can be complicated, like from horizontal to vertical split with - * new split pairs. + * different split layout (ratio or direction). This can be complicated, like from horizontal to + * vertical split with new split pairs. * Uses a jump cut animation to simplify. */ private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) { @@ -553,8 +553,8 @@ class ActivityEmbeddingAnimationRunner { } // Check if the transition contains both opening and closing windows. - boolean hasOpeningWindow = false; - boolean hasClosingWindow = false; + final List<TransitionInfo.Change> openChanges = new ArrayList<>(); + final List<TransitionInfo.Change> closeChanges = new ArrayList<>(); for (TransitionInfo.Change change : info.getChanges()) { if (changingChanges.contains(change)) { continue; @@ -564,10 +564,30 @@ class ActivityEmbeddingAnimationRunner { // No-op if it will be covered by the changing parent window. continue; } - hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode()); - hasClosingWindow |= TransitionUtil.isClosingType(change.getMode()); + if (TransitionUtil.isOpeningType(change.getMode())) { + openChanges.add(change); + } else if (TransitionUtil.isClosingType(change.getMode())) { + closeChanges.add(change); + } + } + if (openChanges.isEmpty() || closeChanges.isEmpty()) { + // Only skip if the transition contains both open and close. + return false; + } + if (changingChanges.size() != 1 || openChanges.size() != 1 || closeChanges.size() != 1) { + // Skip when there are too many windows involved. + return true; + } + final TransitionInfo.Change changingChange = changingChanges.get(0); + final TransitionInfo.Change openChange = openChanges.get(0); + final TransitionInfo.Change closeChange = closeChanges.get(0); + if (changingChange.getStartAbsBounds().equals(openChange.getEndAbsBounds()) + && changingChange.getEndAbsBounds().equals(closeChange.getStartAbsBounds())) { + // Don't skip if the transition is a simple shifting without split direction or ratio + // change. For example, A|B -> B|C. + return false; } - return hasOpeningWindow && hasClosingWindow; + return true; } /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */ 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/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 8b2ec0a35685..8d489e106ae1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -846,8 +846,10 @@ public abstract class WMShellBaseModule { static ShellController provideShellController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, + DisplayInsetsController displayInsetsController, @ShellMainThread ShellExecutor mainExecutor) { - return new ShellController(context, shellInit, shellCommandHandler, mainExecutor); + return new ShellController(context, shellInit, shellCommandHandler, + displayInsetsController, mainExecutor); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index fb3c35b6a1e3..04f0f44d2876 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -57,6 +57,8 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.dagger.back.ShellBackAnimationModule; import com.android.wm.shell.dagger.pip.PipModule; +import com.android.wm.shell.desktopmode.DesktopModeEventLogger; +import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; @@ -509,6 +511,7 @@ public abstract class WMShellModule { ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, DragToDesktopTransitionHandler dragToDesktopTransitionHandler, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, + DesktopModeLoggerTransitionObserver desktopModeLoggerTransitionObserver, LaunchAdjacentController launchAdjacentController, RecentsTransitionHandler recentsTransitionHandler, MultiInstanceHelper multiInstanceHelper, @@ -518,7 +521,8 @@ public abstract class WMShellModule { displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, dragAndDropController, transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, - dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController, + dragToDesktopTransitionHandler, desktopModeTaskRepository, + desktopModeLoggerTransitionObserver, launchAdjacentController, recentsTransitionHandler, multiInstanceHelper, mainExecutor); } @@ -562,6 +566,22 @@ public abstract class WMShellModule { return new DesktopModeTaskRepository(); } + @WMSingleton + @Provides + static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver( + ShellInit shellInit, + Transitions transitions, + DesktopModeEventLogger desktopModeEventLogger) { + return new DesktopModeLoggerTransitionObserver( + shellInit, transitions, desktopModeEventLogger); + } + + @WMSingleton + @Provides + static DesktopModeEventLogger provideDesktopModeEventLogger() { + return new DesktopModeEventLogger(); + } + // // Drag and drop // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt new file mode 100644 index 000000000000..a10c7c093c60 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -0,0 +1,349 @@ +/* + * 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.wm.shell.desktopmode + +import android.app.ActivityManager.RunningTaskInfo +import android.app.ActivityTaskManager.INVALID_TASK_ID +import android.app.TaskInfo +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.os.IBinder +import android.util.SparseArray +import android.view.SurfaceControl +import android.view.WindowManager +import android.window.TransitionInfo +import androidx.annotation.VisibleForTesting +import androidx.core.util.containsKey +import androidx.core.util.forEach +import androidx.core.util.isEmpty +import androidx.core.util.isNotEmpty +import androidx.core.util.plus +import androidx.core.util.putAll +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.shared.TransitionUtil +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.util.KtProtoLog + +/** + * A [Transitions.TransitionObserver] that observes transitions and the proposed changes to log + * appropriate desktop mode session log events. This observes transitions related to desktop mode + * and other transitions that originate both within and outside shell. + */ +class DesktopModeLoggerTransitionObserver( + shellInit: ShellInit, + private val transitions: Transitions, + private val desktopModeEventLogger: DesktopModeEventLogger +) : Transitions.TransitionObserver { + + private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) } + + init { + if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) { + shellInit.addInitCallback(this::onInit, this) + } + } + + // A sparse array of visible freeform tasks and taskInfos + private val visibleFreeformTaskInfos: SparseArray<TaskInfo> = SparseArray() + + // Caching the taskInfos to handle canceled recents animations, if we identify that the recents + // animation was cancelled, we restore these tasks to calculate the post-Transition state + private val tasksSavedForRecents: SparseArray<TaskInfo> = SparseArray() + + // The instanceId for the current logging session + private var loggerInstanceId: InstanceId? = null + + private val isSessionActive: Boolean + get() = loggerInstanceId != null + + private fun setSessionInactive() { + loggerInstanceId = null + } + + fun onInit() { + transitions.registerObserver(this) + } + + override fun onTransitionReady( + transition: IBinder, + info: TransitionInfo, + startTransaction: SurfaceControl.Transaction, + finishTransaction: SurfaceControl.Transaction + ) { + // this was a new recents animation + if (info.isRecentsTransition() && tasksSavedForRecents.isEmpty()) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: Recents animation running, saving tasks for later" + ) + // TODO (b/326391303) - avoid logging session exit if we can identify a cancelled + // recents animation + + // when recents animation is running, all freeform tasks are sent TO_BACK temporarily + // if the user ends up at home, we need to update the visible freeform tasks + // if the user cancels the animation, the subsequent transition is NONE + // if the user opens a new task, the subsequent transition is OPEN with flag + tasksSavedForRecents.putAll(visibleFreeformTaskInfos) + } + + // figure out what the new state of freeform tasks would be post transition + var postTransitionVisibleFreeformTasks = getPostTransitionVisibleFreeformTaskInfos(info) + + // A canceled recents animation is followed by a TRANSIT_NONE transition with no flags, if + // that's the case, we might have accidentally logged a session exit and would need to + // revaluate again. Add all the tasks back. + // This will start a new desktop mode session. + if ( + info.type == WindowManager.TRANSIT_NONE && + info.flags == 0 && + tasksSavedForRecents.isNotEmpty() + ) { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: Canceled recents animation, restoring tasks" + ) + // restore saved tasks in the updated set and clear for next use + postTransitionVisibleFreeformTasks += tasksSavedForRecents + tasksSavedForRecents.clear() + } + + // identify if we need to log any changes and update the state of visible freeform tasks + identifyLogEventAndUpdateState( + transitionInfo = info, + preTransitionVisibleFreeformTasks = visibleFreeformTaskInfos, + postTransitionVisibleFreeformTasks = postTransitionVisibleFreeformTasks + ) + } + + override fun onTransitionStarting(transition: IBinder) {} + + override fun onTransitionMerged(merged: IBinder, playing: IBinder) {} + + override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {} + + private fun getPostTransitionVisibleFreeformTaskInfos( + info: TransitionInfo + ): SparseArray<TaskInfo> { + // device is sleeping, so no task will be visible anymore + if (info.type == WindowManager.TRANSIT_SLEEP) { + return SparseArray() + } + + // filter changes involving freeform tasks or tasks that were cached in previous state + val changesToFreeformWindows = + info.changes + .filter { it.taskInfo != null && it.requireTaskInfo().taskId != INVALID_TASK_ID } + .filter { + it.requireTaskInfo().isFreeformWindow() || + visibleFreeformTaskInfos.containsKey(it.requireTaskInfo().taskId) + } + + val postTransitionFreeformTasks: SparseArray<TaskInfo> = SparseArray() + // start off by adding all existing tasks + postTransitionFreeformTasks.putAll(visibleFreeformTaskInfos) + + // the combined set of taskInfos we are interested in this transition change + for (change in changesToFreeformWindows) { + val taskInfo = change.requireTaskInfo() + + // check if this task existed as freeform window in previous cached state and it's now + // changing window modes + if ( + visibleFreeformTaskInfos.containsKey(taskInfo.taskId) && + visibleFreeformTaskInfos.get(taskInfo.taskId).isFreeformWindow() && + !taskInfo.isFreeformWindow() + ) { + postTransitionFreeformTasks.remove(taskInfo.taskId) + // no need to evaluate new visibility of this task, since it's no longer a freeform + // window + continue + } + + // check if the task is visible after this change, otherwise remove it + if (isTaskVisibleAfterChange(change)) { + postTransitionFreeformTasks.put(taskInfo.taskId, taskInfo) + } else { + postTransitionFreeformTasks.remove(taskInfo.taskId) + } + } + + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: taskInfo map after processing changes %s", + postTransitionFreeformTasks.size() + ) + + return postTransitionFreeformTasks + } + + /** + * Look at the [TransitionInfo.Change] and figure out if this task will be visible after this + * change is processed + */ + private fun isTaskVisibleAfterChange(change: TransitionInfo.Change): Boolean = + when { + TransitionUtil.isOpeningType(change.mode) -> true + TransitionUtil.isClosingType(change.mode) -> false + // change mode TRANSIT_CHANGE is only for visible to visible transitions + change.mode == WindowManager.TRANSIT_CHANGE -> true + else -> false + } + + /** + * Log the appropriate log event based on the new state of TasksInfos and previously cached + * state and update it + */ + private fun identifyLogEventAndUpdateState( + transitionInfo: TransitionInfo, + preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, + postTransitionVisibleFreeformTasks: SparseArray<TaskInfo> + ) { + if ( + postTransitionVisibleFreeformTasks.isEmpty() && + preTransitionVisibleFreeformTasks.isNotEmpty() && + isSessionActive + ) { + // Sessions is finishing, log task updates followed by an exit event + identifyAndLogTaskUpdates( + loggerInstanceId!!.id, + preTransitionVisibleFreeformTasks, + postTransitionVisibleFreeformTasks + ) + + desktopModeEventLogger.logSessionExit( + loggerInstanceId!!.id, + getExitReason(transitionInfo) + ) + + setSessionInactive() + } else if ( + postTransitionVisibleFreeformTasks.isNotEmpty() && + preTransitionVisibleFreeformTasks.isEmpty() && + !isSessionActive + ) { + // Session is starting, log enter event followed by task updates + loggerInstanceId = idSequence.newInstanceId() + desktopModeEventLogger.logSessionEnter( + loggerInstanceId!!.id, + getEnterReason(transitionInfo) + ) + + identifyAndLogTaskUpdates( + loggerInstanceId!!.id, + preTransitionVisibleFreeformTasks, + postTransitionVisibleFreeformTasks + ) + } else if (isSessionActive) { + // Session is neither starting, nor finishing, log task updates if there are any + identifyAndLogTaskUpdates( + loggerInstanceId!!.id, + preTransitionVisibleFreeformTasks, + postTransitionVisibleFreeformTasks + ) + } + + // update the state to the new version + visibleFreeformTaskInfos.clear() + visibleFreeformTaskInfos.putAll(postTransitionVisibleFreeformTasks) + } + + // TODO(b/326231724) - Add logging around taskInfoChanges Updates + /** Compare the old and new state of taskInfos and identify and log the changes */ + private fun identifyAndLogTaskUpdates( + sessionId: Int, + preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, + postTransitionVisibleFreeformTasks: SparseArray<TaskInfo> + ) { + // find new tasks that were added + postTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> + if (!preTransitionVisibleFreeformTasks.containsKey(taskId)) { + desktopModeEventLogger.logTaskAdded(sessionId, buildTaskUpdateForTask(taskInfo)) + } + } + + // find old tasks that were removed + preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> + if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) { + desktopModeEventLogger.logTaskRemoved(sessionId, buildTaskUpdateForTask(taskInfo)) + } + } + } + + // TODO(b/326231724: figure out how to get taskWidth and taskHeight from TaskInfo + private fun buildTaskUpdateForTask(taskInfo: TaskInfo): TaskUpdate { + val taskUpdate = TaskUpdate(taskInfo.taskId, taskInfo.userId) + // add task x, y if available + taskInfo.positionInParent?.let { taskUpdate.copy(taskX = it.x, taskY = it.y) } + + return taskUpdate + } + + /** Get [EnterReason] for this session enter */ + private fun getEnterReason(transitionInfo: TransitionInfo): EnterReason { + // TODO(b/326231756) - Add support for missing enter reasons + return when (transitionInfo.type) { + WindowManager.TRANSIT_WAKE -> EnterReason.SCREEN_ON + Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> EnterReason.APP_HANDLE_DRAG + Transitions.TRANSIT_MOVE_TO_DESKTOP -> EnterReason.APP_HANDLE_MENU_BUTTON + WindowManager.TRANSIT_OPEN -> EnterReason.APP_FREEFORM_INTENT + else -> EnterReason.UNKNOWN_ENTER + } + } + + /** Get [ExitReason] for this session exit */ + private fun getExitReason(transitionInfo: TransitionInfo): ExitReason { + // TODO(b/326231756) - Add support for missing exit reasons + return when { + transitionInfo.type == WindowManager.TRANSIT_SLEEP -> ExitReason.SCREEN_OFF + transitionInfo.type == WindowManager.TRANSIT_CLOSE -> ExitReason.TASK_FINISHED + transitionInfo.type == Transitions.TRANSIT_EXIT_DESKTOP_MODE -> ExitReason.DRAG_TO_EXIT + transitionInfo.isRecentsTransition() -> ExitReason.RETURN_HOME_OR_OVERVIEW + else -> ExitReason.UNKNOWN_EXIT + } + } + + /** Adds tasks to the saved copy of freeform taskId, taskInfo. Only used for testing. */ + @VisibleForTesting + fun addTaskInfosToCachedMap(taskInfo: TaskInfo) { + visibleFreeformTaskInfos.set(taskInfo.taskId, taskInfo) + } + + @VisibleForTesting fun getLoggerSessionId(): Int? = loggerInstanceId?.id + + @VisibleForTesting + fun setLoggerSessionId(id: Int) { + loggerInstanceId = InstanceId.fakeInstanceId(id) + } + + private fun TransitionInfo.Change.requireTaskInfo(): RunningTaskInfo { + return this.taskInfo ?: throw IllegalStateException("Expected TaskInfo in the Change") + } + + private fun TaskInfo.isFreeformWindow(): Boolean { + return this.windowingMode == WINDOWING_MODE_FREEFORM + } + + private fun TransitionInfo.isRecentsTransition(): Boolean { + return this.type == WindowManager.TRANSIT_TO_FRONT && + this.flags == WindowManager.TRANSIT_FLAG_IS_RECENTS + } +}
\ No newline at end of file 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 95237c38f309..dd8c1a0f2e02 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 @@ -102,6 +102,7 @@ class DesktopTasksController( ToggleResizeDesktopTaskTransitionHandler, private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler, private val desktopModeTaskRepository: DesktopModeTaskRepository, + private val desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver, private val launchAdjacentController: LaunchAdjacentController, private val recentsTransitionHandler: RecentsTransitionHandler, private val multiInstanceHelper: MultiInstanceHelper, 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 9dd4c193a006..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 @@ -29,6 +29,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -1450,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()); @@ -1469,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", @@ -1546,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. */ @@ -1611,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; @@ -2776,7 +2789,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, + " with " + taskInfo.taskId + " before startAnimation()."); record.addRecord(stage, true, taskInfo.taskId); } - } else if (isClosingType(change.getMode())) { + } else if (change.getMode() == TRANSIT_CLOSE) { if (stage.containsTask(taskInfo.taskId)) { record.addRecord(stage, false, taskInfo.taskId); Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java new file mode 100644 index 000000000000..a94f80241d4f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/DisplayImeChangeListener.java @@ -0,0 +1,34 @@ +/* + * 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.wm.shell.sysui; + +import android.graphics.Rect; + +/** + * Callbacks for when the Display IME changes. + */ +public interface DisplayImeChangeListener { + /** + * Called when the ime bounds change. + */ + default void onImeBoundsChanged(int displayId, Rect bounds) {} + + /** + * Called when the IME visibility change. + */ + default void onImeVisibilityChanged(int displayId, boolean isShowing) {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java index a7843e218a8a..2f6edc226c45 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -30,21 +30,28 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Bundle; import android.util.ArrayMap; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.SurfaceControlRegistry; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ExternalThread; import java.io.PrintWriter; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; import java.util.function.Supplier; /** @@ -57,6 +64,7 @@ public class ShellController { private final ShellInit mShellInit; private final ShellCommandHandler mShellCommandHandler; private final ShellExecutor mMainExecutor; + private final DisplayInsetsController mDisplayInsetsController; private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl(); private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners = @@ -65,6 +73,8 @@ public class ShellController { new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<UserChangeListener> mUserChangeListeners = new CopyOnWriteArrayList<>(); + private final ConcurrentHashMap<DisplayImeChangeListener, Executor> mDisplayImeChangeListeners = + new ConcurrentHashMap<>(); private ArrayMap<String, Supplier<ExternalInterfaceBinder>> mExternalInterfaceSuppliers = new ArrayMap<>(); @@ -73,20 +83,53 @@ public class ShellController { private Configuration mLastConfiguration; + private OnInsetsChangedListener mInsetsChangeListener = new OnInsetsChangedListener() { + private InsetsState mInsetsState = new InsetsState(); + + @Override + public void insetsChanged(InsetsState insetsState) { + if (mInsetsState == insetsState) { + return; + } + + InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME); + boolean wasVisible = (oldSource != null && oldSource.isVisible()); + Rect oldFrame = wasVisible ? oldSource.getFrame() : null; + + InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME); + boolean isVisible = (newSource != null && newSource.isVisible()); + Rect newFrame = isVisible ? newSource.getFrame() : null; + + if (wasVisible != isVisible) { + onImeVisibilityChanged(isVisible); + } + + if (newFrame != null && !newFrame.equals(oldFrame)) { + onImeBoundsChanged(newFrame); + } + + mInsetsState = insetsState; + } + }; + public ShellController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, + DisplayInsetsController displayInsetsController, ShellExecutor mainExecutor) { mContext = context; mShellInit = shellInit; mShellCommandHandler = shellCommandHandler; + mDisplayInsetsController = displayInsetsController; mMainExecutor = mainExecutor; shellInit.addInitCallback(this::onInit, this); } private void onInit() { mShellCommandHandler.addDumpCallback(this::dump, this); + mDisplayInsetsController.addInsetsChangedListener( + mContext.getDisplayId(), mInsetsChangeListener); } /** @@ -259,6 +302,25 @@ public class ShellController { } } + @VisibleForTesting + void onImeBoundsChanged(Rect bounds) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime bounds changed"); + mDisplayImeChangeListeners.forEach( + (DisplayImeChangeListener listener, Executor executor) -> + executor.execute(() -> listener.onImeBoundsChanged( + mContext.getDisplayId(), bounds))); + } + + @VisibleForTesting + void onImeVisibilityChanged(boolean isShowing) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Display Ime visibility changed: isShowing=%b", + isShowing); + mDisplayImeChangeListeners.forEach( + (DisplayImeChangeListener listener, Executor executor) -> + executor.execute(() -> listener.onImeVisibilityChanged( + mContext.getDisplayId(), isShowing))); + } + private void handleInit() { SurfaceControlRegistry.createProcessInstance(mContext); mShellInit.init(); @@ -329,6 +391,19 @@ public class ShellController { } @Override + public void addDisplayImeChangeListener(DisplayImeChangeListener listener, + Executor executor) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Adding new DisplayImeChangeListener"); + mDisplayImeChangeListeners.put(listener, executor); + } + + @Override + public void removeDisplayImeChangeListener(DisplayImeChangeListener listener) { + ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Removing DisplayImeChangeListener"); + mDisplayImeChangeListeners.remove(listener); + } + + @Override public boolean handleCommand(String[] args, PrintWriter pw) { try { boolean[] result = new boolean[1]; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java index bc5dd11ef54e..bd1c64a0d182 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java @@ -25,6 +25,7 @@ import androidx.annotation.NonNull; import java.io.PrintWriter; import java.util.List; +import java.util.concurrent.Executor; /** * General interface for notifying the Shell of common SysUI events like configuration or keyguard @@ -65,6 +66,18 @@ public interface ShellInterface { default void onUserProfilesChanged(@NonNull List<UserInfo> profiles) {} /** + * Registers a DisplayImeChangeListener to monitor for changes on Ime + * position and visibility. + */ + default void addDisplayImeChangeListener(DisplayImeChangeListener listener, + Executor executor) {} + + /** + * Removes a registered DisplayImeChangeListener. + */ + default void removeDisplayImeChangeListener(DisplayImeChangeListener listener) {} + + /** * Handles a shell command. */ default boolean handleCommand(final String[] args, PrintWriter pw) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt index ae39fbcb4eed..4a4c5e860bb2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt @@ -37,6 +37,7 @@ import com.android.wm.shell.WindowManagerShellWrapper import com.android.wm.shell.bubbles.bar.BubbleBarLayerView import com.android.wm.shell.bubbles.properties.BubbleProperties import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.FloatingContentCoordinator import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue @@ -94,7 +95,8 @@ class BubbleViewInfoTest : ShellTestCase() { val windowManager = context.getSystemService(WindowManager::class.java) val shellInit = ShellInit(mainExecutor) val shellCommandHandler = ShellCommandHandler() - val shellController = ShellController(context, shellInit, shellCommandHandler, mainExecutor) + val shellController = ShellController(context, shellInit, shellCommandHandler, + mock<DisplayInsetsController>(), mainExecutor) bubblePositioner = BubblePositioner(context, windowManager) val bubbleData = BubbleData( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt new file mode 100644 index 000000000000..65117f7e9eea --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -0,0 +1,358 @@ +/* + * 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.wm.shell.desktopmode + +import android.app.ActivityManager +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.os.IBinder +import android.testing.AndroidTestingRunner +import android.view.SurfaceControl +import android.view.WindowManager.TRANSIT_CHANGE +import android.view.WindowManager.TRANSIT_CLOSE +import android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS +import android.view.WindowManager.TRANSIT_NONE +import android.view.WindowManager.TRANSIT_OPEN +import android.view.WindowManager.TRANSIT_SLEEP +import android.view.WindowManager.TRANSIT_TO_BACK +import android.view.WindowManager.TRANSIT_TO_FRONT +import android.view.WindowManager.TRANSIT_WAKE +import android.window.IWindowContainerToken +import android.window.TransitionInfo +import android.window.TransitionInfo.Change +import android.window.WindowContainerToken +import androidx.test.filters.SmallTest +import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.wm.shell.common.ShellExecutor +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.transition.TransitionInfoBuilder +import com.android.wm.shell.transition.Transitions +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.never +import org.mockito.kotlin.same +import org.mockito.kotlin.times + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DesktopModeLoggerTransitionObserverTest { + + @JvmField + @Rule + val extendedMockitoRule = ExtendedMockitoRule.Builder(this) + .mockStatic(DesktopModeEventLogger::class.java) + .mockStatic(DesktopModeStatus::class.java).build()!! + + @Mock + lateinit var testExecutor: ShellExecutor + @Mock + private lateinit var mockShellInit: ShellInit + @Mock + private lateinit var transitions: Transitions + + private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver + private lateinit var shellInit: ShellInit + private lateinit var desktopModeEventLogger: DesktopModeEventLogger + + @Before + fun setup() { + Mockito.`when`(DesktopModeStatus.isEnabled()).thenReturn(true) + shellInit = Mockito.spy(ShellInit(testExecutor)) + desktopModeEventLogger = mock(DesktopModeEventLogger::class.java) + + transitionObserver = DesktopModeLoggerTransitionObserver( + mockShellInit, transitions, desktopModeEventLogger) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + val initRunnableCaptor = ArgumentCaptor.forClass( + Runnable::class.java) + verify(mockShellInit).addInitCallback(initRunnableCaptor.capture(), + same(transitionObserver)) + initRunnableCaptor.value.run() + } else { + transitionObserver.onInit() + } + } + + @Test + fun testRegistersObserverAtInit() { + verify(transitions) + .registerObserver(same( + transitionObserver)) + } + + @Test + fun taskCreated_notFreeformWindow_doesNotLogSessionEnterOrTaskAdded() { + val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, never()).logSessionEnter(any(), any()) + verify(desktopModeEventLogger, never()).logTaskAdded(any(), any()) + } + + @Test + fun taskCreated_FreeformWindowOpen_logSessionEnterAndTaskAdded() { + val change = createChange(TRANSIT_OPEN, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + val sessionId = transitionObserver.getLoggerSessionId() + + assertThat(sessionId).isNotNull() + verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), + eq(EnterReason.APP_FREEFORM_INTENT)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + } + + @Test + fun taskChanged_taskMovedToDesktopByDrag_logSessionEnterAndTaskAdded() { + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + // task change is finalised when drag ends + val transitionInfo = TransitionInfoBuilder( + Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP, 0).addChange(change).build() + + callOnTransitionReady(transitionInfo) + val sessionId = transitionObserver.getLoggerSessionId() + + assertThat(sessionId).isNotNull() + verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), + eq(EnterReason.APP_HANDLE_DRAG)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + } + + @Test + fun taskChanged_taskMovedToDesktopByButtonTap_logSessionEnterAndTaskAdded() { + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0) + .addChange(change).build() + + callOnTransitionReady(transitionInfo) + val sessionId = transitionObserver.getLoggerSessionId() + + assertThat(sessionId).isNotNull() + verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), + eq(EnterReason.APP_HANDLE_MENU_BUTTON)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + } + + @Test + fun taskChanged_existingFreeformTaskMadeVisible_logSessionEnterAndTaskAdded() { + val taskInfo = createTaskInfo(1, WINDOWING_MODE_FREEFORM) + taskInfo.isVisibleRequested = true + val change = createChange(TRANSIT_CHANGE, taskInfo) + val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_MOVE_TO_DESKTOP, 0) + .addChange(change).build() + + callOnTransitionReady(transitionInfo) + val sessionId = transitionObserver.getLoggerSessionId() + + assertThat(sessionId).isNotNull() + verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), + eq(EnterReason.APP_HANDLE_MENU_BUTTON)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + } + + @Test + fun taskToFront_screenWake_logSessionStartedAndTaskAdded() { + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_WAKE, 0) + .addChange(change).build() + + callOnTransitionReady(transitionInfo) + val sessionId = transitionObserver.getLoggerSessionId() + + assertThat(sessionId).isNotNull() + verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), + eq(EnterReason.SCREEN_ON)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + } + + @Test + fun freeformTaskVisible_screenTurnOff_logSessionExitAndTaskRemoved_sessionIdNull() { + val sessionId = 1 + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.setLoggerSessionId(sessionId) + + val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) + verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId), + eq(ExitReason.SCREEN_OFF)) + assertThat(transitionObserver.getLoggerSessionId()).isNull() + } + + @Test + fun freeformTaskVisible_exitDesktopUsingDrag_logSessionExitAndTaskRemoved_sessionIdNull() { + val sessionId = 1 + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.setLoggerSessionId(sessionId) + + // window mode changing from FREEFORM to FULLSCREEN + val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val transitionInfo = TransitionInfoBuilder(Transitions.TRANSIT_EXIT_DESKTOP_MODE) + .addChange(change).build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) + verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId), + eq(ExitReason.DRAG_TO_EXIT)) + assertThat(transitionObserver.getLoggerSessionId()).isNull() + } + + @Test + fun freeformTaskVisible_exitDesktopBySwipeUp_logSessionExitAndTaskRemoved_sessionIdNull() { + val sessionId = 1 + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.setLoggerSessionId(sessionId) + + // recents transition + val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) + .addChange(change).build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) + verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId), + eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + assertThat(transitionObserver.getLoggerSessionId()).isNull() + } + + @Test + fun freeformTaskVisible_taskFinished_logSessionExitAndTaskRemoved_sessionIdNull() { + val sessionId = 1 + // add a freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.setLoggerSessionId(sessionId) + + // task closing + val change = createChange(TRANSIT_CLOSE, createTaskInfo(1, WINDOWING_MODE_FULLSCREEN)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) + verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId), + eq(ExitReason.TASK_FINISHED)) + assertThat(transitionObserver.getLoggerSessionId()).isNull() + } + + @Test + fun sessionExitByRecents_cancelledAnimation_sessionRestored() { + val sessionId = 1 + // add a freeform task to an existing session + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.setLoggerSessionId(sessionId) + + // recents transition sent freeform window to back + val change = createChange(TRANSIT_TO_BACK, createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + val transitionInfo1 = + TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS).addChange(change) + .build() + callOnTransitionReady(transitionInfo1) + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) + verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId), + eq(ExitReason.RETURN_HOME_OR_OVERVIEW)) + assertThat(transitionObserver.getLoggerSessionId()).isNull() + + val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build() + callOnTransitionReady(transitionInfo2) + + verify(desktopModeEventLogger, times(1)).logSessionEnter(any(), any()) + verify(desktopModeEventLogger, times(1)).logTaskAdded(any(), any()) + } + + @Test + fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() { + val sessionId = 1 + // add an existing freeform task + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.setLoggerSessionId(sessionId) + + // new freeform task added + val change = createChange(TRANSIT_OPEN, createTaskInfo(2, WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_OPEN, 0).addChange(change).build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), any()) + verify(desktopModeEventLogger, never()).logSessionEnter(any(), any()) + } + + @Test + fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() { + val sessionId = 1 + // add two existing freeform tasks + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(1, WINDOWING_MODE_FREEFORM)) + transitionObserver.addTaskInfosToCachedMap(createTaskInfo(2, WINDOWING_MODE_FREEFORM)) + transitionObserver.setLoggerSessionId(sessionId) + + // new freeform task added + val change = createChange(TRANSIT_CLOSE, createTaskInfo(2, WINDOWING_MODE_FREEFORM)) + val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE, 0).addChange(change).build() + callOnTransitionReady(transitionInfo) + + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), any()) + verify(desktopModeEventLogger, never()).logSessionExit(any(), any()) + } + + /** + * Simulate calling the onTransitionReady() method + */ + private fun callOnTransitionReady(transitionInfo: TransitionInfo) { + val transition = mock(IBinder::class.java) + val startT = mock( + SurfaceControl.Transaction::class.java) + val finishT = mock( + SurfaceControl.Transaction::class.java) + + transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT) + } + + companion object { + fun createTaskInfo(taskId: Int, windowMode: Int): ActivityManager.RunningTaskInfo { + val taskInfo = ActivityManager.RunningTaskInfo() + taskInfo.taskId = taskId + taskInfo.configuration.windowConfiguration.windowingMode = windowMode + + return taskInfo + } + + fun createChange(mode: Int, taskInfo: ActivityManager.RunningTaskInfo): Change { + val change = Change( + WindowContainerToken(mock( + IWindowContainerToken::class.java)), + mock(SurfaceControl::class.java)) + change.mode = mode + change.taskInfo = taskInfo + return change + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 0136751d8c9a..5df9dd38a75d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -87,6 +87,7 @@ import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.verify +import org.mockito.kotlin.times import org.mockito.Mockito.`when` as whenever import org.mockito.quality.Strictness @@ -113,6 +114,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler @Mock lateinit var dragAndDropController: DragAndDropController @Mock lateinit var multiInstanceHelper: MultiInstanceHelper + @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -163,6 +165,7 @@ class DesktopTasksControllerTest : ShellTestCase() { mToggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, desktopModeTaskRepository, + desktopModeLoggerTransitionObserver, launchAdjacentController, recentsTransitionHandler, multiInstanceHelper, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 3384509f1da9..d38fc6cb6418 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -129,7 +129,7 @@ public class PipControllerTest extends ShellTestCase { }).when(mMockExecutor).execute(any()); mShellInit = spy(new ShellInit(mMockExecutor)); mShellController = spy(new ShellController(mContext, mShellInit, mMockShellCommandHandler, - mMockExecutor)); + mMockDisplayInsetsController, mMockExecutor)); mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler, mShellController, mMockDisplayController, mMockPipAnimationController, mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 10e9e11e9004..41a4e8d503c9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -58,6 +58,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; @@ -96,6 +97,8 @@ public class RecentTasksControllerTest extends ShellTestCase { private DesktopModeTaskRepository mDesktopModeTaskRepository; @Mock private ActivityTaskManager mActivityTaskManager; + @Mock + private DisplayInsetsController mDisplayInsetsController; private ShellTaskOrganizer mShellTaskOrganizer; private RecentTasksController mRecentTasksController; @@ -110,7 +113,7 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); mShellInit = spy(new ShellInit(mMainExecutor)); mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler, - mMainExecutor)); + mDisplayInsetsController, mMainExecutor)); mRecentTasksControllerReal = new RecentTasksController(mContext, mShellInit, mShellController, mShellCommandHandler, mTaskStackListener, mActivityTaskManager, Optional.of(mDesktopModeTaskRepository), mMainExecutor); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 315d97ed333b..3c387f0d7c34 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -123,7 +123,7 @@ public class SplitScreenControllerTests extends ShellTestCase { assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)); MockitoAnnotations.initMocks(this); mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler, - mMainExecutor)); + mDisplayInsetsController, mMainExecutor)); mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit, mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController, mDisplayImeController, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java index 012c40811811..ff76a2f13527 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java @@ -40,6 +40,7 @@ import com.android.internal.util.function.TriConsumer; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -65,6 +66,7 @@ public class StartingWindowControllerTests extends ShellTestCase { private @Mock Context mContext; private @Mock DisplayManager mDisplayManager; + private @Mock DisplayInsetsController mDisplayInsetsController; private @Mock ShellCommandHandler mShellCommandHandler; private @Mock ShellTaskOrganizer mTaskOrganizer; private @Mock ShellExecutor mMainExecutor; @@ -83,7 +85,7 @@ public class StartingWindowControllerTests extends ShellTestCase { doReturn(super.mContext.getResources()).when(mContext).getResources(); mShellInit = spy(new ShellInit(mMainExecutor)); mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler, - mMainExecutor)); + mDisplayInsetsController, mMainExecutor)); mController = new StartingWindowController(mContext, mShellInit, mShellController, mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool); mShellInit.init(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java index 7c520c34b29d..6292018ba35d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.mock; import android.content.Context; import android.content.pm.UserInfo; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -35,8 +36,8 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ExternalInterfaceBinder; -import com.android.wm.shell.common.ShellExecutor; import org.junit.After; import org.junit.Before; @@ -63,12 +64,15 @@ public class ShellControllerTest extends ShellTestCase { private ShellCommandHandler mShellCommandHandler; @Mock private Context mTestUserContext; + @Mock + private DisplayInsetsController mDisplayInsetsController; private TestShellExecutor mExecutor; private ShellController mController; private TestConfigurationChangeListener mConfigChangeListener; private TestKeyguardChangeListener mKeyguardChangeListener; private TestUserChangeListener mUserChangeListener; + private TestDisplayImeChangeListener mDisplayImeChangeListener; @Before @@ -77,8 +81,10 @@ public class ShellControllerTest extends ShellTestCase { mKeyguardChangeListener = new TestKeyguardChangeListener(); mConfigChangeListener = new TestConfigurationChangeListener(); mUserChangeListener = new TestUserChangeListener(); + mDisplayImeChangeListener = new TestDisplayImeChangeListener(); mExecutor = new TestShellExecutor(); - mController = new ShellController(mContext, mShellInit, mShellCommandHandler, mExecutor); + mController = new ShellController(mContext, mShellInit, mShellCommandHandler, + mDisplayInsetsController, mExecutor); mController.onConfigurationChanged(getConfigurationCopy()); } @@ -130,6 +136,45 @@ public class ShellControllerTest extends ShellTestCase { } @Test + public void testAddDisplayImeChangeListener_ensureCallback() { + mController.asShell().addDisplayImeChangeListener( + mDisplayImeChangeListener, mExecutor); + + final Rect bounds = new Rect(10, 20, 30, 40); + mController.onImeBoundsChanged(bounds); + mController.onImeVisibilityChanged(true); + mExecutor.flushAll(); + + assertTrue(mDisplayImeChangeListener.boundsChanged == 1); + assertTrue(bounds.equals(mDisplayImeChangeListener.lastBounds)); + assertTrue(mDisplayImeChangeListener.visibilityChanged == 1); + assertTrue(mDisplayImeChangeListener.lastVisibility); + } + + @Test + public void testDoubleAddDisplayImeChangeListener_ensureSingleCallback() { + mController.asShell().addDisplayImeChangeListener( + mDisplayImeChangeListener, mExecutor); + mController.asShell().addDisplayImeChangeListener( + mDisplayImeChangeListener, mExecutor); + + mController.onImeVisibilityChanged(true); + mExecutor.flushAll(); + assertTrue(mDisplayImeChangeListener.visibilityChanged == 1); + } + + @Test + public void testAddRemoveDisplayImeChangeListener_ensureNoCallback() { + mController.asShell().addDisplayImeChangeListener( + mDisplayImeChangeListener, mExecutor); + mController.asShell().removeDisplayImeChangeListener(mDisplayImeChangeListener); + + mController.onImeVisibilityChanged(true); + mExecutor.flushAll(); + assertTrue(mDisplayImeChangeListener.visibilityChanged == 0); + } + + @Test public void testAddUserChangeListener_ensureCallback() { mController.addUserChangeListener(mUserChangeListener); @@ -457,4 +502,23 @@ public class ShellControllerTest extends ShellTestCase { lastUserProfiles = profiles; } } + + private static class TestDisplayImeChangeListener implements DisplayImeChangeListener { + public int boundsChanged = 0; + public Rect lastBounds; + public int visibilityChanged = 0; + public boolean lastVisibility = false; + + @Override + public void onImeBoundsChanged(int displayId, Rect bounds) { + boundsChanged++; + lastBounds = bounds; + } + + @Override + public void onImeVisibilityChanged(int displayId, boolean isShowing) { + visibilityChanged++; + lastVisibility = isShowing; + } + } } diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index 156be389fe84..f33bcb7f9643 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -11,7 +11,7 @@ flag { name: "location_bypass" namespace: "location" description: "Enable location bypass appops behavior" - bug: "301150056" + bug: "329151785" } flag { diff --git a/nfc/Android.bp b/nfc/Android.bp index 7dd16ba6c18e..7698e2b2d054 100644 --- a/nfc/Android.bp +++ b/nfc/Android.bp @@ -76,6 +76,9 @@ java_sdk_library { "//apex_available:platform", "com.android.nfcservices", ], + aconfig_declarations: [ + "android.nfc.flags-aconfig", + ], } filegroup { 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/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt index 0e3949365646..cfdcaff4d34c 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt @@ -26,23 +26,10 @@ import java.util.concurrent.ConcurrentHashMap /** Manager of [BackupRestoreStorage]. */ class BackupRestoreStorageManager private constructor(private val application: Application) { - private val storages = ConcurrentHashMap<String, BackupRestoreStorage>() + private val storageWrappers = ConcurrentHashMap<String, StorageWrapper>() private val executor = MoreExecutors.directExecutor() - private val observer = Observer { reason -> notifyBackupManager(null, reason) } - - private val keyedObserver = - KeyedObserver<Any?> { key, reason -> notifyBackupManager(key, reason) } - - private fun notifyBackupManager(key: Any?, reason: Int) { - // prefer not triggering backup immediately after restore - if (reason == ChangeReason.RESTORE) return - // TODO: log storage name - Log.d(LOG_TAG, "Notify BackupManager data changed for change: key=$key") - BackupManager.dataChanged(application.packageName) - } - /** * Adds all the registered [BackupRestoreStorage] as the helpers of given [BackupAgentHelper]. * @@ -52,7 +39,8 @@ class BackupRestoreStorageManager private constructor(private val application: A */ fun addBackupAgentHelpers(backupAgentHelper: BackupAgentHelper) { val fileStorages = mutableListOf<BackupRestoreFileStorage>() - for ((keyPrefix, storage) in storages) { + for ((keyPrefix, storageWrapper) in storageWrappers) { + val storage = storageWrapper.storage if (storage is BackupRestoreFileStorage) { fileStorages.add(storage) } else { @@ -70,15 +58,8 @@ class BackupRestoreStorageManager private constructor(private val application: A * The observers of the storages will be notified. */ fun onRestoreFinished() { - for (storage in storages.values) { - storage.notifyRestoreFinished() - } - } - - private fun BackupRestoreStorage.notifyRestoreFinished() { - when (this) { - is KeyedObservable<*> -> notifyChange(ChangeReason.RESTORE) - is Observable -> notifyChange(ChangeReason.RESTORE) + for (storageWrapper in storageWrappers.values) { + storageWrapper.notifyRestoreFinished() } } @@ -99,50 +80,82 @@ class BackupRestoreStorageManager private constructor(private val application: A fun add(storage: BackupRestoreStorage) { if (storage is BackupRestoreFileStorage) storage.checkFilePaths() val name = storage.name - val oldStorage = storages.put(name, storage) + val oldStorage = storageWrappers.put(name, StorageWrapper(storage))?.storage if (oldStorage != null) { throw IllegalStateException( "Storage name '$name' conflicts:\n\told: $oldStorage\n\tnew: $storage" ) } - storage.addObserver() - } - - private fun BackupRestoreStorage.addObserver() { - when (this) { - is KeyedObservable<*> -> addObserver(keyedObserver, executor) - is Observable -> addObserver(observer, executor) - else -> - throw IllegalArgumentException( - "$this does not implement either KeyedObservable or Observable" - ) - } } /** Removes all the storages. */ fun removeAll() { - for ((name, _) in storages) remove(name) + for ((name, _) in storageWrappers) remove(name) } /** Removes storage with given name. */ fun remove(name: String): BackupRestoreStorage? { - val storage = storages.remove(name) - storage?.removeObserver() - return storage - } - - private fun BackupRestoreStorage.removeObserver() { - when (this) { - is KeyedObservable<*> -> removeObserver(keyedObserver) - is Observable -> removeObserver(observer) - } + val storageWrapper = storageWrappers.remove(name) + storageWrapper?.removeObserver() + return storageWrapper?.storage } /** Returns storage with given name. */ - fun get(name: String): BackupRestoreStorage? = storages[name] + fun get(name: String): BackupRestoreStorage? = storageWrappers[name]?.storage /** Returns storage with given name, exception is raised if not found. */ - fun getOrThrow(name: String): BackupRestoreStorage = storages[name]!! + fun getOrThrow(name: String): BackupRestoreStorage = storageWrappers[name]!!.storage + + private inner class StorageWrapper(val storage: BackupRestoreStorage) : + Observer, KeyedObserver<Any?> { + init { + when (storage) { + is KeyedObservable<*> -> storage.addObserver(this, executor) + is Observable -> storage.addObserver(this, executor) + else -> + throw IllegalArgumentException( + "$this does not implement either KeyedObservable or Observable" + ) + } + } + + override fun onChanged(reason: Int) = onKeyChanged(null, reason) + + override fun onKeyChanged(key: Any?, reason: Int) { + notifyBackupManager(key, reason) + } + + private fun notifyBackupManager(key: Any?, reason: Int) { + val name = storage.name + // prefer not triggering backup immediately after restore + if (reason == ChangeReason.RESTORE) { + Log.d( + LOG_TAG, + "Notify BackupManager dataChanged ignored for restore: storage=$name key=$key" + ) + return + } + Log.d( + LOG_TAG, + "Notify BackupManager dataChanged: storage=$name key=$key reason=$reason" + ) + BackupManager.dataChanged(application.packageName) + } + + fun removeObserver() { + when (storage) { + is KeyedObservable<*> -> storage.removeObserver(this) + is Observable -> storage.removeObserver(this) + } + } + + fun notifyRestoreFinished() { + when (storage) { + is KeyedObservable<*> -> storage.notifyChange(ChangeReason.RESTORE) + is Observable -> storage.notifyChange(ChangeReason.RESTORE) + } + } + } companion object { @Volatile private var instance: BackupRestoreStorageManager? = null 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/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java index 53daef1c4112..69c7410818dd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java @@ -242,6 +242,7 @@ public class CreateUserDialogController { .setMessage(messageResId) .setNegativeButtonText(R.string.cancel) .setPositiveButtonText(R.string.next); + mCustomDialogHelper.requestFocusOnTitle(); break; case GRANT_ADMIN_DIALOG: mEditUserInfoView.setVisibility(View.GONE); @@ -254,6 +255,7 @@ public class CreateUserDialogController { .setMessage(R.string.user_grant_admin_message) .setNegativeButtonText(R.string.back) .setPositiveButtonText(R.string.next); + mCustomDialogHelper.requestFocusOnTitle(); if (mIsAdmin == null) { mCustomDialogHelper.setButtonEnabled(false); } @@ -265,6 +267,7 @@ public class CreateUserDialogController { .setTitle(R.string.user_info_settings_title) .setNegativeButtonText(R.string.back) .setPositiveButtonText(R.string.done); + mCustomDialogHelper.requestFocusOnTitle(); mEditUserInfoView.setVisibility(View.VISIBLE); mGrantAdminView.setVisibility(View.GONE); break; @@ -273,7 +276,6 @@ public class CreateUserDialogController { && mEditUserPhotoController.getNewUserPhotoDrawable() != null) ? mEditUserPhotoController.getNewUserPhotoDrawable() : mSavedDrawable; - String newName = mUserNameView.getText().toString().trim(); String defaultName = mActivity.getString(R.string.user_new_user_name); mUserName = !newName.isEmpty() ? newName : defaultName; diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java index 5201b3ddc606..4cf3bc23b9a0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/utils/CustomDialogHelper.java @@ -23,6 +23,7 @@ import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; @@ -282,4 +283,13 @@ public class CustomDialogHelper { } return this; } + + /** + * Requests focus on dialog title when used. Used to let talkback know that the dialog content + * is updated and needs to be read from the beginning. + */ + public void requestFocusOnTitle() { + mDialogTitle.requestFocus(); + mDialogTitle.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt index 56b0bf74574f..1586b8f46ff8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt @@ -23,8 +23,8 @@ import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel import com.android.settingslib.volume.shared.model.RingerMode import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map /** Provides audio stream state and an ability to change it */ @@ -43,6 +43,9 @@ class AudioVolumeInteractor( streamModel.copy(volume = processVolume(streamModel, ringerMode, isZenMuted)) } + val ringerMode: StateFlow<RingerMode> + get() = audioRepository.ringerMode + suspend fun setVolume(audioStream: AudioStream, volume: Int) = audioRepository.setVolume(audioStream, volume) @@ -52,9 +55,14 @@ class AudioVolumeInteractor( /** Checks if the volume can be changed via the UI. */ fun canChangeVolume(audioStream: AudioStream): Flow<Boolean> { return if (audioStream.value == AudioManager.STREAM_NOTIFICATION) { - getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { !it.isMuted } + combine( + notificationsSoundPolicyInteractor.isZenMuted(audioStream), + getAudioStream(AudioStream(AudioManager.STREAM_RING)).map { it.isMuted }, + ) { isZenMuted, isRingMuted -> + !isZenMuted && !isRingMuted + } } else { - flowOf(true) + notificationsSoundPolicyInteractor.isZenMuted(audioStream).map { !it } } } 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/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 30d5d4b86a90..eaec617cfa70 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -269,6 +269,7 @@ public class SecureSettings { Settings.Secure.STYLUS_POINTER_ICON_ENABLED, Settings.Secure.CAMERA_EXTENSIONS_FALLBACK, Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED, - Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS + Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, + Settings.Secure.AUDIO_DEVICE_INVENTORY }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 893932f663b7..046d6e25ff31 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -424,5 +424,6 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.STYLUS_POINTER_ICON_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.CAMERA_EXTENSIONS_FALLBACK, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.IMMERSIVE_MODE_CONFIRMATIONS, ANY_STRING_VALIDATOR); + VALIDATORS.put(Secure.AUDIO_DEVICE_INVENTORY, ANY_STRING_VALIDATOR); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 46c89900b54e..6eb2dd043c94 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -670,7 +670,6 @@ public class SettingsBackupTest { Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED, Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN, Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY, - Settings.Secure.AUDIO_DEVICE_INVENTORY, // not controllable by user Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED, // not controllable by user Settings.Secure.BACKUP_AUTO_RESTORE, Settings.Secure.BACKUP_ENABLED, 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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 98591e9b76e9..e62c77dc2071 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -179,6 +179,7 @@ <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/> <uses-permission android:name="android.permission.USE_EXACT_ALARM"/> + <uses-permission android:name="android.permission.RECORD_SENSITIVE_CONTENT"/> <!-- Assist --> <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" /> 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/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt index 5d5f12e8e567..3f57f88a13d3 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt @@ -337,6 +337,7 @@ constructor( if (ghostedView is LaunchableView) { // Restore the ghosted view visibility. ghostedView.setShouldBlockVisibilityChanges(false) + ghostedView.onActivityLaunchAnimationEnd() } else { // Make the ghosted view visible. We ensure that the view is considered VISIBLE by // accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17 diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt index ed8e70568b48..da6ccaa2dd2c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt @@ -38,6 +38,9 @@ interface LaunchableView { * @param block whether we should block/postpone all calls to `setVisibility`. */ fun setShouldBlockVisibilityChanges(block: Boolean) + + /** Perform an action when the activity launch animation ends */ + fun onActivityLaunchAnimationEnd() {} } /** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */ diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt index ef15c8461b95..9a996496d41c 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt @@ -21,22 +21,25 @@ package com.android.compose import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background import androidx.compose.foundation.interaction.DragInteraction import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider import androidx.compose.material3.SliderState import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -44,17 +47,28 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.RoundRect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasurePolicy +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp -import com.android.compose.modifiers.padding +import androidx.compose.ui.util.fastFirst +import androidx.compose.ui.util.fastFirstOrNull import com.android.compose.theme.LocalAndroidColorScheme /** @@ -62,15 +76,16 @@ import com.android.compose.theme.LocalAndroidColorScheme * * @param onValueChangeFinished is called when the slider settles on a [value]. This callback * shouldn't be used to react to value changes. Use [onValueChange] instead - * @param interactionSource - the [MutableInteractionSource] representing the stream of Interactions + * @param interactionSource the [MutableInteractionSource] representing the stream of Interactions * for this slider. You can create and pass in your own remembered instance to observe * Interactions and customize the appearance / behavior of this slider in different states. - * @param colors - slider color scheme. - * @param draggingCornersRadius - radius of the slider indicator when the user drags it - * @param icon - icon at the start of the slider. Icon is limited to a square space at the start of - * the slider - * @param label - control shown next to the icon. + * @param colors determine slider color scheme. + * @param draggingCornersRadius is the radius of the slider indicator when the user drags it + * @param icon at the start of the slider. Icon is limited to a square space at the start of the + * slider + * @param label is shown next to the icon. */ +@OptIn(ExperimentalMaterial3Api::class) @Composable fun PlatformSlider( value: Float, @@ -86,7 +101,7 @@ fun PlatformSlider( label: (@Composable (isDragging: Boolean) -> Unit)? = null, ) { val sliderHeight: Dp = 64.dp - val iconWidth: Dp = sliderHeight + val thumbSize: Dp = sliderHeight var isDragging by remember { mutableStateOf(false) } LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction -> @@ -101,16 +116,6 @@ fun PlatformSlider( } } } - val paddingStart by - animateDpAsState( - targetValue = - if ((!isDragging && value == valueRange.start) || icon == null) { - 16.dp - } else { - 0.dp - }, - label = "LabelIconSpacingAnimation" - ) Box(modifier = modifier.height(sliderHeight)) { Slider( @@ -126,130 +131,275 @@ fun PlatformSlider( sliderState = it, enabled = enabled, colors = colors, - iconWidth = iconWidth, draggingCornersRadius = draggingCornersRadius, sliderHeight = sliderHeight, + thumbSize = thumbSize, isDragging = isDragging, - modifier = Modifier, + label = label, + icon = icon, + modifier = Modifier.fillMaxSize(), ) }, - thumb = { Spacer(Modifier.width(iconWidth).height(sliderHeight)) }, + thumb = { Spacer(Modifier.size(thumbSize)) }, ) - if (icon != null || label != null) { - Row(modifier = Modifier.fillMaxSize()) { - icon?.let { iconComposable -> - Box( - modifier = Modifier.fillMaxHeight().aspectRatio(1f), - contentAlignment = Alignment.Center, - ) { - iconComposable(isDragging) - } - } - - label?.let { labelComposable -> - Box( - modifier = - Modifier.fillMaxHeight() - .weight(1f) - .padding( - start = { paddingStart.roundToPx() }, - end = { sliderHeight.roundToPx() / 2 }, - ), - contentAlignment = Alignment.CenterStart, - ) { - labelComposable(isDragging) - } - } - } - } + Spacer( + Modifier.padding(8.dp) + .size(4.dp) + .align(Alignment.CenterEnd) + .background(color = colors.indicatorColor, shape = CircleShape) + ) } } +private enum class TrackComponent(val zIndex: Float) { + Background(0f), + Icon(1f), + Label(1f), +} + @Composable private fun Track( sliderState: SliderState, enabled: Boolean, colors: PlatformSliderColors, - iconWidth: Dp, draggingCornersRadius: Dp, sliderHeight: Dp, + thumbSize: Dp, isDragging: Boolean, + icon: (@Composable (isDragging: Boolean) -> Unit)?, + label: (@Composable (isDragging: Boolean) -> Unit)?, modifier: Modifier = Modifier, ) { val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl - val iconWidthPx: Float - val halfIconWidthPx: Float - val targetIndicatorRadiusPx: Float - val halfSliderHeightPx: Float - with(LocalDensity.current) { - halfSliderHeightPx = sliderHeight.toPx() / 2 - iconWidthPx = iconWidth.toPx() - halfIconWidthPx = iconWidthPx / 2 - targetIndicatorRadiusPx = - if (isDragging) draggingCornersRadius.toPx() else halfSliderHeightPx - } + var drawingState: DrawingState by remember { mutableStateOf(DrawingState()) } + Layout( + modifier = modifier, + content = { + TrackBackground( + modifier = Modifier.layoutId(TrackComponent.Background), + drawingState = drawingState, + enabled = enabled, + colors = colors, + draggingCornersRadiusActive = draggingCornersRadius, + draggingCornersRadiusIdle = sliderHeight / 2, + isDragging = isDragging, + ) + if (icon != null) { + Box( + modifier = Modifier.layoutId(TrackComponent.Icon).clip(CircleShape), + contentAlignment = Alignment.Center, + ) { + CompositionLocalProvider( + LocalContentColor provides + if (enabled) colors.iconColor else colors.disabledIconColor + ) { + icon(isDragging) + } + } + } + if (label != null) { + val offsetX by + animateFloatAsState( + targetValue = + if (enabled) { + if (drawingState.isLabelOnTopOfIndicator) { + drawingState.iconWidth.coerceAtLeast( + LocalDensity.current.run { 16.dp.toPx() } + ) + } else { + val indicatorWidth = + drawingState.indicatorRight - drawingState.indicatorLeft + indicatorWidth + LocalDensity.current.run { 16.dp.toPx() } + } + } else { + drawingState.iconWidth + }, + label = "LabelIconSpacingAnimation" + ) + Box( + modifier = + Modifier.layoutId(TrackComponent.Label).offset { + IntOffset(offsetX.toInt(), 0) + }, + contentAlignment = Alignment.CenterStart, + ) { + CompositionLocalProvider( + LocalContentColor provides + colors.getLabelColor( + isEnabled = enabled, + isLabelOnTopOfTheIndicator = drawingState.isLabelOnTopOfIndicator, + ) + ) { + label(isDragging) + } + } + } + }, + measurePolicy = + TrackMeasurePolicy( + sliderState = sliderState, + thumbSize = LocalDensity.current.run { thumbSize.roundToPx() }, + isRtl = isRtl, + onDrawingStateMeasured = { drawingState = it } + ) + ) +} - val indicatorRadiusPx: Float by - animateFloatAsState( - targetValue = targetIndicatorRadiusPx, +@Composable +private fun TrackBackground( + drawingState: DrawingState, + enabled: Boolean, + colors: PlatformSliderColors, + draggingCornersRadiusActive: Dp, + draggingCornersRadiusIdle: Dp, + isDragging: Boolean, + modifier: Modifier = Modifier, +) { + val indicatorRadiusDp: Dp by + animateDpAsState( + targetValue = + if (isDragging) draggingCornersRadiusActive else draggingCornersRadiusIdle, label = "PlatformSliderCornersAnimation", ) val trackColor = colors.getTrackColor(enabled) val indicatorColor = colors.getIndicatorColor(enabled) - val trackCornerRadius = CornerRadius(halfSliderHeightPx, halfSliderHeightPx) - val indicatorCornerRadius = CornerRadius(indicatorRadiusPx, indicatorRadiusPx) Canvas(modifier.fillMaxSize()) { + val trackCornerRadius = CornerRadius(size.height / 2, size.height / 2) val trackPath = Path() trackPath.addRoundRect( RoundRect( - left = -halfIconWidthPx, + left = 0f, top = 0f, - right = size.width + halfIconWidthPx, - bottom = size.height, + right = drawingState.totalWidth, + bottom = drawingState.totalHeight, cornerRadius = trackCornerRadius, ) ) drawPath(path = trackPath, color = trackColor) + val indicatorCornerRadius = CornerRadius(indicatorRadiusDp.toPx(), indicatorRadiusDp.toPx()) clipPath(trackPath) { val indicatorPath = Path() - if (isRtl) { - indicatorPath.addRoundRect( - RoundRect( - left = - size.width - - size.width * sliderState.coercedNormalizedValue - - halfIconWidthPx, - top = 0f, - right = size.width + iconWidthPx, - bottom = size.height, - topLeftCornerRadius = indicatorCornerRadius, - topRightCornerRadius = trackCornerRadius, - bottomRightCornerRadius = trackCornerRadius, - bottomLeftCornerRadius = indicatorCornerRadius, + indicatorPath.addRoundRect( + RoundRect( + left = drawingState.indicatorLeft, + top = drawingState.indicatorTop, + right = drawingState.indicatorRight, + bottom = drawingState.indicatorBottom, + topLeftCornerRadius = trackCornerRadius, + topRightCornerRadius = indicatorCornerRadius, + bottomRightCornerRadius = indicatorCornerRadius, + bottomLeftCornerRadius = trackCornerRadius, + ) + ) + drawPath(path = indicatorPath, color = indicatorColor) + } + } +} + +/** Measures track components sizes and calls [onDrawingStateMeasured] when it's done. */ +private class TrackMeasurePolicy( + private val sliderState: SliderState, + private val thumbSize: Int, + private val isRtl: Boolean, + private val onDrawingStateMeasured: (DrawingState) -> Unit, +) : MeasurePolicy { + + override fun MeasureScope.measure( + measurables: List<Measurable>, + constraints: Constraints + ): MeasureResult { + // Slider adds a paddings to the Track to make spase for thumb + val desiredWidth = constraints.maxWidth + thumbSize + val desiredHeight = constraints.maxHeight + val backgroundPlaceable: Placeable = + measurables + .fastFirst { it.layoutId == TrackComponent.Background } + .measure(Constraints(desiredWidth, desiredWidth, desiredHeight, desiredHeight)) + + val iconPlaceable: Placeable? = + measurables + .fastFirstOrNull { it.layoutId == TrackComponent.Icon } + ?.measure( + Constraints( + minWidth = desiredHeight, + maxWidth = desiredHeight, + minHeight = desiredHeight, + maxHeight = desiredHeight, ) ) - } else { - indicatorPath.addRoundRect( - RoundRect( - left = -halfIconWidthPx, - top = 0f, - right = size.width * sliderState.coercedNormalizedValue + halfIconWidthPx, - bottom = size.height, - topLeftCornerRadius = trackCornerRadius, - topRightCornerRadius = indicatorCornerRadius, - bottomRightCornerRadius = indicatorCornerRadius, - bottomLeftCornerRadius = trackCornerRadius, + + val iconSize = iconPlaceable?.width ?: 0 + val labelMaxWidth = (desiredWidth - iconSize) / 2 + val labelPlaceable: Placeable? = + measurables + .fastFirstOrNull { it.layoutId == TrackComponent.Label } + ?.measure( + Constraints( + minWidth = 0, + maxWidth = labelMaxWidth, + minHeight = desiredHeight, + maxHeight = desiredHeight, ) ) + + val drawingState = + if (isRtl) { + DrawingState( + isRtl = true, + totalWidth = desiredWidth.toFloat(), + totalHeight = desiredHeight.toFloat(), + indicatorLeft = + (desiredWidth - iconSize) * (1 - sliderState.coercedNormalizedValue), + indicatorTop = 0f, + indicatorRight = desiredWidth.toFloat(), + indicatorBottom = desiredHeight.toFloat(), + iconWidth = iconSize.toFloat(), + labelWidth = labelPlaceable?.width?.toFloat() ?: 0f, + ) + } else { + DrawingState( + isRtl = false, + totalWidth = desiredWidth.toFloat(), + totalHeight = desiredHeight.toFloat(), + indicatorLeft = 0f, + indicatorTop = 0f, + indicatorRight = + iconSize + (desiredWidth - iconSize) * sliderState.coercedNormalizedValue, + indicatorBottom = desiredHeight.toFloat(), + iconWidth = iconSize.toFloat(), + labelWidth = labelPlaceable?.width?.toFloat() ?: 0f, + ) } - drawPath(path = indicatorPath, color = indicatorColor) + + onDrawingStateMeasured(drawingState) + + return layout(desiredWidth, desiredHeight) { + backgroundPlaceable.placeRelative(0, 0, TrackComponent.Background.zIndex) + + iconPlaceable?.placeRelative(0, 0, TrackComponent.Icon.zIndex) + labelPlaceable?.placeRelative(0, 0, TrackComponent.Label.zIndex) } } } +private data class DrawingState( + val isRtl: Boolean = false, + val totalWidth: Float = 0f, + val totalHeight: Float = 0f, + val indicatorLeft: Float = 0f, + val indicatorTop: Float = 0f, + val indicatorRight: Float = 0f, + val indicatorBottom: Float = 0f, + val iconWidth: Float = 0f, + val labelWidth: Float = 0f, +) + +private val DrawingState.isLabelOnTopOfIndicator: Boolean + get() = labelWidth < indicatorRight - indicatorLeft - iconWidth + /** [SliderState.value] normalized using [SliderState.valueRange]. The result belongs to [0, 1] */ private val SliderState.coercedNormalizedValue: Float get() { @@ -268,17 +418,19 @@ private val SliderState.coercedNormalizedValue: Float * @param trackColor fills the track of the slider. This is a "background" of the slider * @param indicatorColor fills the slider from the start to the value * @param iconColor is the default icon color - * @param labelColor is the default icon color + * @param labelColorOnIndicator is the label color for when it's shown on top of the indicator + * @param labelColorOnTrack is the label color for when it's shown on top of the track * @param disabledTrackColor is the [trackColor] when the PlatformSlider#enabled == false * @param disabledIndicatorColor is the [indicatorColor] when the PlatformSlider#enabled == false * @param disabledIconColor is the [iconColor] when the PlatformSlider#enabled == false - * @param disabledLabelColor is the [labelColor] when the PlatformSlider#enabled == false + * @param disabledLabelColor is the label color when the PlatformSlider#enabled == false */ data class PlatformSliderColors( val trackColor: Color, val indicatorColor: Color, val iconColor: Color, - val labelColor: Color, + val labelColorOnIndicator: Color, + val labelColorOnTrack: Color, val disabledTrackColor: Color, val disabledIndicatorColor: Color, val disabledIconColor: Color, @@ -300,10 +452,11 @@ object PlatformSliderDefaults { @Composable private fun lightThemePlatformSliderColors() = PlatformSliderColors( - trackColor = MaterialTheme.colorScheme.tertiaryContainer, - indicatorColor = LocalAndroidColorScheme.current.tertiaryFixedDim, - iconColor = MaterialTheme.colorScheme.onTertiaryContainer, - labelColor = MaterialTheme.colorScheme.onTertiaryContainer, + trackColor = LocalAndroidColorScheme.current.tertiaryFixedDim, + indicatorColor = MaterialTheme.colorScheme.tertiary, + iconColor = MaterialTheme.colorScheme.onTertiary, + labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary, + labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed, disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest, disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest, disabledIconColor = MaterialTheme.colorScheme.outline, @@ -314,10 +467,11 @@ private fun lightThemePlatformSliderColors() = @Composable private fun darkThemePlatformSliderColors() = PlatformSliderColors( - trackColor = MaterialTheme.colorScheme.onTertiary, - indicatorColor = LocalAndroidColorScheme.current.onTertiaryFixedVariant, + trackColor = MaterialTheme.colorScheme.tertiary, + indicatorColor = MaterialTheme.colorScheme.tertiary, iconColor = MaterialTheme.colorScheme.onTertiaryContainer, - labelColor = MaterialTheme.colorScheme.onTertiaryContainer, + labelColorOnIndicator = MaterialTheme.colorScheme.onTertiary, + labelColorOnTrack = LocalAndroidColorScheme.current.onTertiaryFixed, disabledTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest, disabledIndicatorColor = MaterialTheme.colorScheme.surfaceContainerHighest, disabledIconColor = MaterialTheme.colorScheme.outline, @@ -329,3 +483,14 @@ private fun PlatformSliderColors.getTrackColor(isEnabled: Boolean): Color = private fun PlatformSliderColors.getIndicatorColor(isEnabled: Boolean): Color = if (isEnabled) indicatorColor else disabledIndicatorColor + +private fun PlatformSliderColors.getLabelColor( + isEnabled: Boolean, + isLabelOnTopOfTheIndicator: Boolean +): Color { + return if (isEnabled) { + if (isLabelOnTopOfTheIndicator) labelColorOnIndicator else labelColorOnTrack + } else { + disabledLabelColor + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt index 96520b21cc72..7acb4d5498db 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt @@ -19,61 +19,31 @@ package com.android.systemui.keyguard.ui.composable import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.android.compose.animation.scene.Edge -import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope -import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene import dagger.Lazy import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.stateIn /** The lock screen scene shows when the device is locked. */ @SysUISingleton class LockscreenScene @Inject constructor( - @Application private val applicationScope: CoroutineScope, viewModel: LockscreenSceneViewModel, private val lockscreenContent: Lazy<LockscreenContent>, ) : ComposableScene { override val key = Scenes.Lockscreen override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = - combine( - viewModel.upDestinationSceneKey, - viewModel.leftDestinationSceneKey, - viewModel.downFromTopEdgeDestinationSceneKey, - ) { upKey, leftKey, downFromTopEdgeKey -> - destinationScenes( - up = upKey, - left = leftKey, - downFromTopEdge = downFromTopEdgeKey, - ) - } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = - destinationScenes( - up = viewModel.upDestinationSceneKey.value, - left = viewModel.leftDestinationSceneKey.value, - downFromTopEdge = viewModel.downFromTopEdgeDestinationSceneKey.value, - ) - ) + viewModel.destinationScenes @Composable override fun SceneScope.Content( @@ -84,22 +54,6 @@ constructor( modifier = modifier, ) } - - private fun destinationScenes( - up: SceneKey?, - left: SceneKey?, - downFromTopEdge: SceneKey?, - ): Map<UserAction, UserActionResult> { - return buildMap { - up?.let { this[Swipe(SwipeDirection.Up)] = UserActionResult(up) } - left?.let { this[Swipe(SwipeDirection.Left)] = UserActionResult(left) } - downFromTopEdge?.let { - this[Swipe(fromSource = Edge.Top, direction = SwipeDirection.Down)] = - UserActionResult(downFromTopEdge) - } - this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade) - } - } } @Composable diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 51464d059890..31201c2f26e8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -373,7 +373,6 @@ private fun SceneScope.MediaIfVisible( ) { if (viewModel.isMediaVisible()) { val density = LocalDensity.current - val layoutWidth = remember { mutableStateOf(0) } val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded) MediaCarousel( @@ -389,7 +388,7 @@ private fun SceneScope.MediaIfVisible( layout(placeable.width, placeable.height) { placeable.placeRelative(0, 0) } }, mediaHost = mediaHost, - layoutWidth = layoutWidth.value, + layoutWidth = 0, layoutHeight = with(density) { mediaHeight.toPx() }.toInt(), carouselController = mediaCarouselController, ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt index 8ac84ff819eb..b1fbe35eccd8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.component.anc.ui.composable import android.content.Context +import android.view.ContextThemeWrapper import android.view.View import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.MaterialTheme @@ -73,15 +74,16 @@ constructor( AndroidView<SliceView>( modifier = Modifier.fillMaxWidth(), factory = { context: Context -> - SliceView(context).apply { - mode = SliceView.MODE_LARGE - isScrollable = false - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - setShowTitleItems(true) - addOnLayoutChangeListener( - OnWidthChangedLayoutListener(viewModel::changeSliceWidth) - ) - } + SliceView(ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel)) + .apply { + mode = SliceView.MODE_LARGE + isScrollable = false + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + setShowTitleItems(true) + addOnLayoutChangeListener( + OnWidthChangedLayoutListener(viewModel::changeSliceWidth) + ) + } }, update = { sliceView: SliceView -> sliceView.slice = slice } ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt index 4d810dfce89d..81d2da0688f0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt @@ -42,9 +42,6 @@ import androidx.compose.material3.IconButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -61,12 +58,13 @@ private const val COLLAPSE_DURATION_MILLIS = 300 @Composable fun ColumnVolumeSliders( viewModels: List<SliderViewModel>, + isExpanded: Boolean, + onExpandedChanged: (Boolean) -> Unit, sliderColors: PlatformSliderColors, isExpandable: Boolean, modifier: Modifier = Modifier, ) { require(viewModels.isNotEmpty()) - var isExpanded: Boolean by remember(isExpandable) { mutableStateOf(!isExpandable) } val transition = updateTransition(isExpanded, label = "CollapsableSliders") Column(modifier = modifier) { Row( @@ -85,7 +83,7 @@ fun ColumnVolumeSliders( if (isExpandable) { ExpandButton( isExpanded = isExpanded, - onExpandedChanged = { isExpanded = it }, + onExpandedChanged = onExpandedChanged, sliderColors, Modifier, ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index 18a62dca3769..3e0aee54ea34 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -22,6 +22,7 @@ import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -30,6 +31,7 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import com.android.compose.PlatformSlider import com.android.compose.PlatformSliderColors import com.android.systemui.common.ui.compose.Icon @@ -54,7 +56,7 @@ fun VolumeSlider( if (isDragging) { Text(text = value.toInt().toString()) } else { - state.icon?.let { Icon(icon = it) } + state.icon?.let { Icon(modifier = Modifier.size(24.dp), icon = it) } } }, colors = sliderColors, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt index 1ca18deeaac2..fdf8ee872019 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt @@ -48,8 +48,11 @@ constructor( modifier = modifier.fillMaxWidth(), ) } else { + val isExpanded by viewModel.isExpanded.collectAsState() ColumnVolumeSliders( viewModels = sliderViewModels, + isExpanded = isExpanded, + onExpandedChanged = viewModel::onExpandedChanged, sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(), isExpandable = isPortrait, modifier = modifier.fillMaxWidth(), diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 6114499a2f5e..63ec54fbef9c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round +import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified import com.android.compose.nestedscroll.PriorityNestedScrollConnection import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope @@ -353,10 +354,7 @@ private class DragControllerImpl( // If the swipe was not committed or if the swipe distance is not computed yet, don't do // anything. - if ( - swipeTransition._currentScene != toScene || - distance == SwipeTransition.DistanceUnspecified - ) { + if (swipeTransition._currentScene != toScene || distance == DistanceUnspecified) { return fromScene to 0f } @@ -418,7 +416,7 @@ private class DragControllerImpl( var targetScene: Scene var targetOffset: Float if ( - distance != SwipeTransition.DistanceUnspecified && + distance != DistanceUnspecified && shouldCommitSwipe( offset, distance, @@ -444,8 +442,8 @@ private class DragControllerImpl( if (targetScene == fromScene) { 0f } else { - check(distance != SwipeTransition.DistanceUnspecified) { - "distance is equal to ${SwipeTransition.DistanceUnspecified}" + check(distance != DistanceUnspecified) { + "distance is equal to $DistanceUnspecified" } distance } @@ -628,6 +626,12 @@ private class SwipeTransition( /** The spec to use when animating this transition to either [fromScene] or [toScene]. */ lateinit var swipeSpec: SpringSpec<Float> + override val overscrollScope: OverscrollScope = + object : OverscrollScope { + override val absoluteDistance: Float + get() = distance().absoluteValue + } + private var lastDistance = DistanceUnspecified /** Whether [TransitionState.Transition.finish] was called on this transition. */ @@ -753,10 +757,6 @@ private class SwipeTransition( /** The job in which [animatable] is animated. */ val job: Job, ) - - companion object { - const val DistanceUnspecified = 0f - } } private object DefaultSwipeDistance : UserActionDistance { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 86124df295b4..e6f5d585e915 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -236,19 +236,28 @@ sealed interface TransitionState { interface HasOverscrollProperties { /** - * The position of the [TransitionState.Transition.toScene]. + * The position of the [Transition.toScene]. * * Used to understand the direction of the overscroll. */ val isUpOrLeft: Boolean /** - * The relative orientation between [TransitionState.Transition.fromScene] and - * [TransitionState.Transition.toScene]. + * The relative orientation between [Transition.fromScene] and [Transition.toScene]. * * Used to understand the orientation of the overscroll. */ val orientation: Orientation + + /** + * Scope which can be used in the Overscroll DSL to define a transformation based on the + * distance between [Transition.fromScene] and [Transition.toScene]. + */ + val overscrollScope: OverscrollScope + + companion object { + const val DistanceUnspecified = 0f + } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index 2dd41cd329a2..b46614397ff4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -30,6 +30,7 @@ import com.android.compose.animation.scene.transformation.AnchoredTranslate import com.android.compose.animation.scene.transformation.DrawScale import com.android.compose.animation.scene.transformation.EdgeTranslate import com.android.compose.animation.scene.transformation.Fade +import com.android.compose.animation.scene.transformation.OverscrollTranslate import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.RangedPropertyTransformation import com.android.compose.animation.scene.transformation.ScaleSize @@ -124,7 +125,7 @@ internal constructor( overscrollSpecs.fastForEach { spec -> if (spec.orientation == orientation && filter(spec)) { if (match != null) { - error("Found multiple transition specs for transition $scene") + error("Found multiple overscroll specs for overscroll $scene") } match = spec } @@ -297,6 +298,7 @@ internal class TransformationSpecImpl( ) { when (current) { is Translate, + is OverscrollTranslate, is EdgeTranslate, is AnchoredTranslate -> { throwIfNotNull(offset, element, name = "offset") diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index bc52a28279dc..2c109a337f65 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified /** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */ fun transitions(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions { @@ -88,8 +89,7 @@ interface SceneTransitionsBuilder { ): OverscrollSpec } -@TransitionDsl -interface OverscrollBuilder : PropertyTransformationBuilder { +interface BaseTransitionBuilder : PropertyTransformationBuilder { /** * The distance it takes for this transition to animate from 0% to 100% when it is driven by a * [UserAction]. @@ -120,7 +120,7 @@ interface OverscrollBuilder : PropertyTransformationBuilder { } @TransitionDsl -interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder { +interface TransitionBuilder : BaseTransitionBuilder { /** * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when * the transition is triggered (i.e. it is not gesture-based). @@ -176,6 +176,24 @@ interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder { fun reversed(builder: TransitionBuilder.() -> Unit) } +@TransitionDsl +interface OverscrollBuilder : BaseTransitionBuilder { + /** Translate the element(s) matching [matcher] by ([x], [y]) pixels. */ + fun translate( + matcher: ElementMatcher, + x: OverscrollScope.() -> Float = { 0f }, + y: OverscrollScope.() -> Float = { 0f }, + ) +} + +interface OverscrollScope { + /** + * Return the absolute distance between fromScene and toScene, if available, otherwise + * [DistanceUnspecified]. + */ + val absoluteDistance: Float +} + /** * An interface to decide where we should draw shared Elements or compose MovableElements. * diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index 65e8ea5cc341..1c9080fa085d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -31,6 +31,7 @@ import com.android.compose.animation.scene.transformation.AnchoredTranslate import com.android.compose.animation.scene.transformation.DrawScale import com.android.compose.animation.scene.transformation.EdgeTranslate import com.android.compose.animation.scene.transformation.Fade +import com.android.compose.animation.scene.transformation.OverscrollTranslate import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.RangedPropertyTransformation import com.android.compose.animation.scene.transformation.ScaleSize @@ -114,7 +115,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { } } -internal open class OverscrollBuilderImpl : OverscrollBuilder { +internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { val transformations = mutableListOf<Transformation>() private var range: TransformationRange? = null protected var reversed = false @@ -130,7 +131,7 @@ internal open class OverscrollBuilderImpl : OverscrollBuilder { range = null } - private fun transformation(transformation: PropertyTransformation<*>) { + protected fun transformation(transformation: PropertyTransformation<*>) { val transformation = if (range != null) { RangedPropertyTransformation(transformation, range!!) @@ -185,7 +186,7 @@ internal open class OverscrollBuilderImpl : OverscrollBuilder { } } -internal class TransitionBuilderImpl : OverscrollBuilderImpl(), TransitionBuilder { +internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBuilder { override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow) override var swipeSpec: SpringSpec<Float>? = null override var distance: UserActionDistance? = null @@ -226,3 +227,13 @@ internal class TransitionBuilderImpl : OverscrollBuilderImpl(), TransitionBuilde fractionRange(start, end, builder) } } + +internal open class OverscrollBuilderImpl : BaseTransitionBuilderImpl(), OverscrollBuilder { + override fun translate( + matcher: ElementMatcher, + x: OverscrollScope.() -> Float, + y: OverscrollScope.() -> Float + ) { + transformation(OverscrollTranslate(matcher, x, y)) + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt index 04d5914bff69..849c9d71ec2f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt @@ -21,11 +21,11 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.ElementMatcher +import com.android.compose.animation.scene.OverscrollScope import com.android.compose.animation.scene.Scene import com.android.compose.animation.scene.SceneTransitionLayoutImpl import com.android.compose.animation.scene.TransitionState -/** Translate an element by a fixed amount of density-independent pixels. */ internal class Translate( override val matcher: ElementMatcher, private val x: Dp = 0.dp, @@ -47,3 +47,28 @@ internal class Translate( } } } + +internal class OverscrollTranslate( + override val matcher: ElementMatcher, + val x: OverscrollScope.() -> Float = { 0f }, + val y: OverscrollScope.() -> Float = { 0f }, +) : PropertyTransformation<Offset> { + override fun transform( + layoutImpl: SceneTransitionLayoutImpl, + scene: Scene, + element: Element, + sceneState: Element.SceneState, + transition: TransitionState.Transition, + value: Offset, + ): Offset { + // As this object is created by OverscrollBuilderImpl and we retrieve the current + // OverscrollSpec only when the transition implements HasOverscrollProperties, we can assume + // that this method was invoked after performing this check. + val overscrollProperties = transition as TransitionState.HasOverscrollProperties + + return Offset( + x = value.x + overscrollProperties.overscrollScope.x(), + y = value.y + overscrollProperties.overscrollScope.y(), + ) + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 059a10e23207..26e01fefcb9b 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -539,24 +539,20 @@ class ElementTest { } } - @Test - fun elementTransitionDuringOverscroll() { + private fun setupOverscrollScenario( + layoutWidth: Dp, + layoutHeight: Dp, + sceneTransitions: SceneTransitionsBuilder.() -> Unit, + firstScroll: Float + ): MutableSceneTransitionLayoutStateImpl { // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is // detected as a drag event. var touchSlop = 0f - val overscrollTranslateY = 10.dp - val layoutWidth = 200.dp - val layoutHeight = 400.dp val state = MutableSceneTransitionLayoutState( initialScene = TestScenes.SceneA, - transitions = - transitions { - overscroll(TestScenes.SceneB, Orientation.Vertical) { - translate(TestElements.Foo, y = overscrollTranslateY) - } - } + transitions = transitions(sceneTransitions), ) as MutableSceneTransitionLayoutStateImpl @@ -585,9 +581,30 @@ class ElementTest { rule.onRoot().performTouchInput { val middleTop = Offset((layoutWidth / 2).toPx(), 0f) down(middleTop) - // Scroll 50% - moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000) + val firstScrollHeight = layoutHeight.toPx() * firstScroll + moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000) } + return state + } + + @Test + fun elementTransitionDuringOverscroll() { + val layoutWidth = 200.dp + val layoutHeight = 400.dp + val overscrollTranslateY = 10.dp + + val state = + setupOverscrollScenario( + layoutWidth = layoutWidth, + layoutHeight = layoutHeight, + sceneTransitions = { + overscroll(TestScenes.SceneB, Orientation.Vertical) { + // On overscroll 100% -> Foo should translate by overscrollTranslateY + translate(TestElements.Foo, y = overscrollTranslateY) + } + }, + firstScroll = 0.5f, // Scroll 50% + ) val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) fooElement.assertTopPositionInRootIsEqualTo(0.dp) @@ -691,4 +708,48 @@ class ElementTest { assertThat(state.currentOverscrollSpec).isNotNull() fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f) } + + @Test + fun elementTransitionWithDistanceDuringOverscroll() { + val layoutWidth = 200.dp + val layoutHeight = 400.dp + val state = + setupOverscrollScenario( + layoutWidth = layoutWidth, + layoutHeight = layoutHeight, + sceneTransitions = { + overscroll(TestScenes.SceneB, Orientation.Vertical) { + // On overscroll 100% -> Foo should translate by layoutHeight + translate(TestElements.Foo, y = { absoluteDistance }) + } + }, + firstScroll = 1f, // 100% scroll + ) + + val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) + fooElement.assertTopPositionInRootIsEqualTo(0.dp) + + rule.onRoot().performTouchInput { + // Scroll another 50% + moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000) + } + + val transition = state.currentTransition + assertThat(transition).isNotNull() + + // Scroll 150% (100% scroll + 50% overscroll) + assertThat(transition!!.progress).isEqualTo(1.5f) + assertThat(state.currentOverscrollSpec).isNotNull() + fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f) + + rule.onRoot().performTouchInput { + // Scroll another 100% + moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000) + } + + // Scroll 250% (100% scroll + 150% overscroll) + assertThat(transition.progress).isEqualTo(2.5f) + assertThat(state.currentOverscrollSpec).isNotNull() + fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt index c9c3eccdedfc..825fe138c3c4 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt @@ -22,9 +22,9 @@ import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.gestures.Orientation import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.transformation.OverscrollTranslate import com.android.compose.animation.scene.transformation.Transformation import com.android.compose.animation.scene.transformation.TransformationRange -import com.android.compose.animation.scene.transformation.Translate import com.google.common.truth.Correspondence import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -228,12 +228,14 @@ class TransitionDslTest { @Test fun overscrollSpec() { val transitions = transitions { - overscroll(TestScenes.SceneA, Orientation.Vertical) { translate(TestElements.Bar) } + overscroll(TestScenes.SceneA, Orientation.Vertical) { + translate(TestElements.Bar, x = { 1f }, y = { 2f }) + } } val overscrollSpec = transitions.overscrollSpecs.single() val transformation = overscrollSpec.transformationSpec.transformations.single() - assertThat(transformation).isInstanceOf(Translate::class.java) + assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java) } companion object { diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt index 153d2b8769b3..73a66c629024 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt @@ -38,6 +38,10 @@ fun transition( override val isUserInputOngoing: Boolean = isUserInputOngoing override val isUpOrLeft: Boolean = isUpOrLeft override val orientation: Orientation = orientation + override val overscrollScope: OverscrollScope = + object : OverscrollScope { + override val absoluteDistance = 0f + } override fun finish(): Job { error("finish() is not supported in test transitions") 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/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt index 09fdd11a99dd..bd1403a6aa26 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -201,6 +202,7 @@ class SimBouncerInteractorTest : SysuiTestCase() { @Test fun verifySimPin() = testScope.runTest { + val msg by collectLastValue(underTest.bouncerMessageChanged) bouncerSimRepository.setSubscriptionId(1) bouncerSimRepository.setSimPukLocked(false) whenever(telephonyManager.createForSubscriptionId(anyInt())) @@ -208,8 +210,7 @@ class SimBouncerInteractorTest : SysuiTestCase() { whenever(telephonyManager.supplyIccLockPin(anyString())) .thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1)) - val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0)) - runCurrent() + verifySim(listOf(0, 0, 0, 0)) assertThat(msg).isNull() verify(keyguardUpdateMonitor).reportSimUnlocked(1) @@ -218,6 +219,7 @@ class SimBouncerInteractorTest : SysuiTestCase() { @Test fun verifySimPin_incorrect_oneRemainingAttempt() = testScope.runTest { + val msg by collectLastValue(underTest.bouncerMessageChanged) bouncerSimRepository.setSubscriptionId(1) bouncerSimRepository.setSimPukLocked(false) whenever(telephonyManager.createForSubscriptionId(anyInt())) @@ -229,9 +231,7 @@ class SimBouncerInteractorTest : SysuiTestCase() { 1, ) ) - - val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0)) - runCurrent() + verifySim(listOf(0, 0, 0, 0)) assertThat(msg).isNull() val errorDialogMessage by collectLastValue(bouncerSimRepository.errorDialogMessage) @@ -245,6 +245,7 @@ class SimBouncerInteractorTest : SysuiTestCase() { @Test fun verifySimPin_incorrect_threeRemainingAttempts() = testScope.runTest { + val msg by collectLastValue(underTest.bouncerMessageChanged) bouncerSimRepository.setSubscriptionId(1) bouncerSimRepository.setSimPukLocked(false) whenever(telephonyManager.createForSubscriptionId(anyInt())) @@ -257,8 +258,7 @@ class SimBouncerInteractorTest : SysuiTestCase() { ) ) - val msg = underTest.verifySim(listOf(0, 0, 0, 0)) - runCurrent() + verifySim(listOf(0, 0, 0, 0)) assertThat(msg).isEqualTo("Enter SIM PIN. You have 3 remaining attempts.") } @@ -266,10 +266,11 @@ class SimBouncerInteractorTest : SysuiTestCase() { @Test fun verifySimPin_notCorrectLength_tooShort() = testScope.runTest { + val msg by collectLastValue(underTest.bouncerMessageChanged) bouncerSimRepository.setSubscriptionId(1) bouncerSimRepository.setSimPukLocked(false) - val msg = underTest.verifySim(listOf(0)) + verifySim(listOf(0)) assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint)) } @@ -277,10 +278,12 @@ class SimBouncerInteractorTest : SysuiTestCase() { @Test fun verifySimPin_notCorrectLength_tooLong() = testScope.runTest { + val msg by collectLastValue(underTest.bouncerMessageChanged) + bouncerSimRepository.setSubscriptionId(1) bouncerSimRepository.setSimPukLocked(false) - val msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) + verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint)) } @@ -288,6 +291,7 @@ class SimBouncerInteractorTest : SysuiTestCase() { @Test fun verifySimPuk() = testScope.runTest { + val msg by collectLastValue(underTest.bouncerMessageChanged) whenever(telephonyManager.createForSubscriptionId(anyInt())) .thenReturn(telephonyManager) whenever(telephonyManager.supplyIccLockPuk(anyString(), anyString())) @@ -295,13 +299,13 @@ class SimBouncerInteractorTest : SysuiTestCase() { bouncerSimRepository.setSubscriptionId(1) bouncerSimRepository.setSimPukLocked(true) - var msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) + verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint)) - msg = underTest.verifySim(listOf(0, 0, 0, 0)) + verifySim(listOf(0, 0, 0, 0)) assertThat(msg).isEqualTo(resources.getString(R.string.kg_enter_confirm_pin_hint)) - msg = underTest.verifySim(listOf(0, 0, 0, 0)) + verifySim(listOf(0, 0, 0, 0)) assertThat(msg).isNull() runCurrent() @@ -311,37 +315,49 @@ class SimBouncerInteractorTest : SysuiTestCase() { @Test fun verifySimPuk_inputTooShort() = testScope.runTest { + val msg by collectLastValue(underTest.bouncerMessageChanged) + bouncerSimRepository.setSubscriptionId(1) bouncerSimRepository.setSimPukLocked(true) - val msg = underTest.verifySim(listOf(0, 0, 0, 0)) + + verifySim(listOf(0, 0, 0, 0)) assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_puk_hint)) } @Test fun verifySimPuk_pinNotCorrectLength() = testScope.runTest { + val msg by collectLastValue(underTest.bouncerMessageChanged) bouncerSimRepository.setSubscriptionId(1) bouncerSimRepository.setSimPukLocked(true) - underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) + verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) + + verifySim(listOf(0, 0, 0)) - val msg = underTest.verifySim(listOf(0, 0, 0)) assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint)) } @Test fun verifySimPuk_confirmedPinDoesNotMatch() = testScope.runTest { + val msg by collectLastValue(underTest.bouncerMessageChanged) + bouncerSimRepository.setSubscriptionId(1) bouncerSimRepository.setSimPukLocked(true) - underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) - underTest.verifySim(listOf(0, 0, 0, 0)) + verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) + verifySim(listOf(0, 0, 0, 0)) - val msg = underTest.verifySim(listOf(0, 0, 0, 1)) + verifySim(listOf(0, 0, 0, 1)) assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint)) } + private suspend fun TestScope.verifySim(pinDigits: List<Int>) { + runCurrent() + underTest.verifySim(pinDigits) + } + @Test fun onErrorDialogDismissed_clearsErrorDialogMessageInRepository() { bouncerSimRepository.setSimVerificationErrorMessage("abc") diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt index 2e9ee5ca2851..4a7757ba1820 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractorTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.biometricSettingsRepository +import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -35,6 +36,7 @@ class DeviceEntryBiometricSettingsInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val biometricSettingsRepository = kosmos.biometricSettingsRepository private val underTest = kosmos.deviceEntryBiometricSettingsInteractor + private val testScope = kosmos.testScope @Test fun isCoex_true() = runTest { @@ -59,4 +61,25 @@ class DeviceEntryBiometricSettingsInteractorTest : SysuiTestCase() { biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) assertThat(isCoex).isFalse() } + + @Test + fun authenticationFlags_providesAuthFlagsFromRepository() = + testScope.runTest { + assertThat(underTest.authenticationFlags) + .isSameInstanceAs(biometricSettingsRepository.authenticationFlags) + } + + @Test + fun isFaceAuthEnrolledAndEnabled_providesValueFromRepository() = + testScope.runTest { + assertThat(underTest.isFaceAuthEnrolledAndEnabled) + .isSameInstanceAs(biometricSettingsRepository.isFaceAuthEnrolledAndEnabled) + } + + @Test + fun isFingerprintAuthEnrolledAndEnabled_providesValueFromRepository() = + testScope.runTest { + assertThat(underTest.isFingerprintAuthEnrolledAndEnabled) + .isSameInstanceAs(biometricSettingsRepository.isFingerprintEnrolledAndEnabled) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index 4f44705b7e72..70ceb2a75d7c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.SceneKey +import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository @@ -27,8 +28,21 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.AdaptiveAuthRequest +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.BouncerLockedOut +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.PolicyLockdown +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.SecurityTimeout +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown +import com.android.systemui.flags.fakeSystemPropertiesHelper +import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeTrustRepository +import com.android.systemui.keyguard.shared.model.AuthenticationFlags import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags @@ -36,6 +50,7 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -230,8 +245,8 @@ class DeviceEntryInteractorTest : SysuiTestCase() { assertThat(canSwipeToEnter).isFalse() trustRepository.setCurrentUserTrusted(true) - runCurrent() faceAuthRepository.isAuthenticated.value = false + runCurrent() assertThat(canSwipeToEnter).isTrue() } @@ -383,6 +398,204 @@ class DeviceEntryInteractorTest : SysuiTestCase() { assertThat(isUnlocked).isTrue() } + @Test + fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() = + testScope.runTest { + kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false) + kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + kosmos.fakeTrustRepository.setTrustUsuallyManaged(false) + runCurrent() + + verifyRestrictionReasonsForAuthFlags( + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to null, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null, + LockPatternUtils.StrongAuthTracker + .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to + null, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to + null, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to null + ) + } + + @Test + fun deviceEntryRestrictionReason_whenFaceIsEnrolledAndEnabled_mapsToAuthFlagsState() = + testScope.runTest { + kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) + kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + kosmos.fakeTrustRepository.setTrustUsuallyManaged(false) + kosmos.fakeSystemPropertiesHelper.set( + DeviceEntryInteractor.SYS_BOOT_REASON_PROP, + "not mainline reboot" + ) + runCurrent() + + verifyRestrictionReasonsForAuthFlags( + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to + DeviceNotUnlockedSinceReboot, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to + AdaptiveAuthRequest, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to + BouncerLockedOut, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to + SecurityTimeout, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to + UserLockdown, + LockPatternUtils.StrongAuthTracker + .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to + NonStrongBiometricsSecurityTimeout, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to + UnattendedUpdate, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to + PolicyLockdown, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to + null, + ) + } + + @Test + fun deviceEntryRestrictionReason_whenFingerprintIsEnrolledAndEnabled_mapsToAuthFlagsState() = + testScope.runTest { + kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false) + kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + kosmos.fakeTrustRepository.setTrustUsuallyManaged(false) + kosmos.fakeSystemPropertiesHelper.set( + DeviceEntryInteractor.SYS_BOOT_REASON_PROP, + "not mainline reboot" + ) + runCurrent() + + verifyRestrictionReasonsForAuthFlags( + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to + DeviceNotUnlockedSinceReboot, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to + AdaptiveAuthRequest, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to + BouncerLockedOut, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to + SecurityTimeout, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to + UserLockdown, + LockPatternUtils.StrongAuthTracker + .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to + NonStrongBiometricsSecurityTimeout, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to + UnattendedUpdate, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to + PolicyLockdown, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to + null, + ) + } + + @Test + fun deviceEntryRestrictionReason_whenTrustAgentIsEnabled_mapsToAuthFlagsState() = + testScope.runTest { + kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false) + kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + kosmos.fakeTrustRepository.setTrustUsuallyManaged(true) + kosmos.fakeTrustRepository.setCurrentUserTrustManaged(false) + kosmos.fakeSystemPropertiesHelper.set( + DeviceEntryInteractor.SYS_BOOT_REASON_PROP, + "not mainline reboot" + ) + runCurrent() + + verifyRestrictionReasonsForAuthFlags( + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT to + DeviceNotUnlockedSinceReboot, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST to + AdaptiveAuthRequest, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to + BouncerLockedOut, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to + SecurityTimeout, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to + UserLockdown, + LockPatternUtils.StrongAuthTracker + .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to + NonStrongBiometricsSecurityTimeout, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to + UnattendedUpdate, + LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to + PolicyLockdown, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to + TrustAgentDisabled, + LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to + TrustAgentDisabled, + ) + } + + @Test + fun deviceEntryRestrictionReason_whenDeviceRebootedForMainlineUpdate_mapsToTheCorrectReason() = + testScope.runTest { + val deviceEntryRestrictionReason by + collectLastValue(underTest.deviceEntryRestrictionReason) + kosmos.fakeSystemPropertiesHelper.set( + DeviceEntryInteractor.SYS_BOOT_REASON_PROP, + DeviceEntryInteractor.REBOOT_MAINLINE_UPDATE + ) + kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags( + AuthenticationFlags( + userId = 1, + flag = LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT + ) + ) + runCurrent() + + kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false) + kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + kosmos.fakeTrustRepository.setTrustUsuallyManaged(false) + runCurrent() + + assertThat(deviceEntryRestrictionReason).isNull() + + kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) + runCurrent() + + assertThat(deviceEntryRestrictionReason) + .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate) + + kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false) + kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + runCurrent() + + assertThat(deviceEntryRestrictionReason) + .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate) + + kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + kosmos.fakeTrustRepository.setTrustUsuallyManaged(true) + runCurrent() + + assertThat(deviceEntryRestrictionReason) + .isEqualTo(DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate) + } + + private fun TestScope.verifyRestrictionReasonsForAuthFlags( + vararg authFlagToDeviceEntryRestriction: Pair<Int, DeviceEntryRestrictionReason?> + ) { + val deviceEntryRestrictionReason by collectLastValue(underTest.deviceEntryRestrictionReason) + + authFlagToDeviceEntryRestriction.forEach { (flag, expectedReason) -> + kosmos.fakeBiometricSettingsRepository.setAuthenticationFlags( + AuthenticationFlags(userId = 1, flag = flag) + ) + runCurrent() + + if (expectedReason == null) { + assertThat(deviceEntryRestrictionReason).isNull() + } else { + assertThat(deviceEntryRestrictionReason).isEqualTo(expectedReason) + } + } + } + private fun switchToScene(sceneKey: SceneKey) { sceneInteractor.changeScene(sceneKey, "reason") } 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/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt new file mode 100644 index 000000000000..8f03717b42f2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt @@ -0,0 +1,332 @@ +/* + * 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.haptics.qs + +import android.os.VibrationEffect +import android.testing.TestableLooper.RunWithLooper +import android.view.MotionEvent +import android.view.View +import androidx.test.core.view.MotionEventBuilder +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.AnimatorTestRule +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.VibratorHelper +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +@RunWithLooper(setAsMainLooper = true) +class QSLongPressEffectTest : SysuiTestCase() { + + @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule() + @Mock private lateinit var vibratorHelper: VibratorHelper + @Mock private lateinit var testView: View + @get:Rule val animatorTestRule = AnimatorTestRule(this) + private val kosmos = testKosmos() + + private val effectDuration = 400 + private val lowTickDuration = 12 + private val spinDuration = 133 + + private lateinit var longPressEffect: QSLongPressEffect + + @Before + fun setup() { + whenever( + vibratorHelper.getPrimitiveDurations( + VibrationEffect.Composition.PRIMITIVE_LOW_TICK, + VibrationEffect.Composition.PRIMITIVE_SPIN, + ) + ) + .thenReturn(intArrayOf(lowTickDuration, spinDuration)) + + longPressEffect = + QSLongPressEffect( + vibratorHelper, + effectDuration, + ) + } + + @Test + fun onActionDown_whileIdle_startsWait() = testWithScope { + // GIVEN an action down event occurs + val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN) + longPressEffect.onTouch(testView, downEvent) + + // THEN the effect moves to the TIMEOUT_WAIT state + assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT) + } + + @Test + fun onActionCancel_whileWaiting_goesIdle() = testWhileWaiting { + // GIVEN an action cancel occurs + val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL) + longPressEffect.onTouch(testView, cancelEvent) + + // THEN the effect goes back to idle and does not start + assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE) + assertEffectDidNotStart() + } + + @Test + fun onActionUp_whileWaiting_performsClick() = testWhileWaiting { + // GIVEN an action is being collected + val action by collectLastValue(longPressEffect.actionType) + + // GIVEN an action up occurs + val upEvent = buildMotionEvent(MotionEvent.ACTION_UP) + longPressEffect.onTouch(testView, upEvent) + + // THEN the action to invoke is the click action and the effect does not start + assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK) + assertEffectDidNotStart() + } + + @Test + fun onWaitComplete_whileWaiting_beginsEffect() = testWhileWaiting { + // GIVEN the pressed timeout is complete + advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L) + + // THEN the effect starts + assertEffectStarted() + } + + @Test + fun onActionUp_whileEffectHasBegun_reversesEffect() = testWhileRunning { + // GIVEN that the effect is at the middle of its completion (progress of 50%) + animatorTestRule.advanceTimeBy(effectDuration / 2L) + + // WHEN an action up occurs + val upEvent = buildMotionEvent(MotionEvent.ACTION_UP) + longPressEffect.onTouch(testView, upEvent) + + // THEN the effect gets reversed at 50% progress + assertEffectReverses(0.5f) + } + + @Test + fun onActionCancel_whileEffectHasBegun_reversesEffect() = testWhileRunning { + // GIVEN that the effect is at the middle of its completion (progress of 50%) + animatorTestRule.advanceTimeBy(effectDuration / 2L) + + // WHEN an action cancel occurs + val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL) + longPressEffect.onTouch(testView, cancelEvent) + + // THEN the effect gets reversed at 50% progress + assertEffectReverses(0.5f) + } + + @Test + fun onAnimationComplete_effectEnds() = testWhileRunning { + // GIVEN that the animation completes + animatorTestRule.advanceTimeBy(effectDuration + 10L) + + // THEN the long-press effect completes + assertEffectCompleted() + } + + @Test + fun onActionDown_whileRunningBackwards_resets() = testWhileRunning { + // GIVEN that the effect is at the middle of its completion (progress of 50%) + animatorTestRule.advanceTimeBy(effectDuration / 2L) + + // GIVEN an action cancel occurs and the effect gets reversed + val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL) + longPressEffect.onTouch(testView, cancelEvent) + + // GIVEN an action down occurs + val downEvent = buildMotionEvent(MotionEvent.ACTION_DOWN) + longPressEffect.onTouch(testView, downEvent) + + // THEN the effect resets + assertEffectResets() + } + + @Test + fun onAnimationComplete_whileRunningBackwards_goesToIdle() = testWhileRunning { + // GIVEN that the effect is at the middle of its completion (progress of 50%) + animatorTestRule.advanceTimeBy(effectDuration / 2L) + + // GIVEN an action cancel occurs and the effect gets reversed + val cancelEvent = buildMotionEvent(MotionEvent.ACTION_CANCEL) + longPressEffect.onTouch(testView, cancelEvent) + + // GIVEN that the animation completes after a sufficient amount of time + animatorTestRule.advanceTimeBy(effectDuration.toLong()) + + // THEN the state goes to [QSLongPressEffect.State.IDLE] + assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE) + } + + private fun buildMotionEvent(action: Int): MotionEvent = + MotionEventBuilder.newBuilder().setAction(action).build() + + private fun testWithScope(test: suspend TestScope.() -> Unit) = + with(kosmos) { + testScope.runTest { + // GIVEN an effect with a testing scope + longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler)) + + // THEN run the test + test() + } + } + + private fun testWhileWaiting(test: suspend TestScope.() -> Unit) = + with(kosmos) { + testScope.runTest { + // GIVEN an effect with a testing scope + longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler)) + + // GIVEN the TIMEOUT_WAIT state is entered + val downEvent = + MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build() + longPressEffect.onTouch(testView, downEvent) + + // THEN run the test + test() + } + } + + private fun testWhileRunning(test: suspend TestScope.() -> Unit) = + with(kosmos) { + testScope.runTest { + // GIVEN an effect with a testing scope + longPressEffect.scope = CoroutineScope(UnconfinedTestDispatcher(testScheduler)) + + // GIVEN the down event that enters the TIMEOUT_WAIT state + val downEvent = + MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_DOWN).build() + longPressEffect.onTouch(testView, downEvent) + + // GIVEN that the timeout completes and the effect starts + advanceTimeBy(QSLongPressEffect.PRESSED_TIMEOUT + 10L) + + // THEN run the test + test() + } + } + + /** + * Asserts that the effect started by checking that: + * 1. The effect progress is 0f + * 2. Initial hint haptics are played + * 3. The internal state is [QSLongPressEffect.State.RUNNING_FORWARD] + */ + private fun TestScope.assertEffectStarted() { + val effectProgress by collectLastValue(longPressEffect.effectProgress) + val longPressHint = + LongPressHapticBuilder.createLongPressHint( + lowTickDuration, + spinDuration, + effectDuration, + ) + + assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_FORWARD) + assertThat(effectProgress).isEqualTo(0f) + assertThat(longPressHint).isNotNull() + verify(vibratorHelper).vibrate(longPressHint!!) + } + + /** + * Asserts that the effect did not start by checking that: + * 1. No effect progress is emitted + * 2. No haptics are played + * 3. The internal state is not [QSLongPressEffect.State.RUNNING_BACKWARDS] or + * [QSLongPressEffect.State.RUNNING_FORWARD] + */ + private fun TestScope.assertEffectDidNotStart() { + val effectProgress by collectLastValue(longPressEffect.effectProgress) + + assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_FORWARD) + assertThat(longPressEffect.state).isNotEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS) + assertThat(effectProgress).isNull() + verify(vibratorHelper, never()).vibrate(any(/* type= */ VibrationEffect::class.java)) + } + + /** + * Asserts that the effect completes by checking that: + * 1. The progress is null + * 2. The final snap haptics are played + * 3. The internal state goes back to [QSLongPressEffect.State.IDLE] + * 4. The action to perform on the tile is the long-press action + */ + private fun TestScope.assertEffectCompleted() { + val action by collectLastValue(longPressEffect.actionType) + val effectProgress by collectLastValue(longPressEffect.effectProgress) + val snapEffect = LongPressHapticBuilder.createSnapEffect() + + assertThat(effectProgress).isNull() + assertThat(snapEffect).isNotNull() + verify(vibratorHelper).vibrate(snapEffect!!) + assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE) + assertThat(action).isEqualTo(QSLongPressEffect.ActionType.LONG_PRESS) + } + + /** + * Assert that the effect gets reverted by checking that: + * 1. The internal state is [QSLongPressEffect.State.RUNNING_BACKWARDS] + * 2. The reverse haptics plays at the point where the animation was paused + */ + private fun assertEffectReverses(pausedProgress: Float) { + val reverseHaptics = + LongPressHapticBuilder.createReversedEffect( + pausedProgress, + lowTickDuration, + effectDuration, + ) + + assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS) + assertThat(reverseHaptics).isNotNull() + verify(vibratorHelper).vibrate(reverseHaptics!!) + } + + /** + * Asserts that the effect resets by checking that: + * 1. The effect progress resets to 0 + * 2. The internal state goes back to [QSLongPressEffect.State.TIMEOUT_WAIT] + */ + private fun TestScope.assertEffectResets() { + val effectProgress by collectLastValue(longPressEffect.effectProgress) + assertThat(effectProgress).isEqualTo(0f) + + assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 19950a5fb89d..2fd2ef1f3240 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import android.platform.test.annotations.EnableFlags -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.compose.animation.scene.Edge +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel @@ -31,86 +34,129 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.kosmos.testScope -import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor -import com.android.systemui.shade.domain.startable.shadeStartable +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith +import platform.test.runner.parameterized.Parameter +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) class LockscreenSceneViewModelTest : SysuiTestCase() { + companion object { + @Parameters( + name = + "canSwipeToEnter={0}, downWithTwoPointers={1}, downFromEdge={2}," + + " isSingleShade={3}, isCommunalAvailable={4}" + ) + @JvmStatic + fun combinations() = buildList { + repeat(32) { combination -> + add( + arrayOf( + /* canSwipeToEnter= */ combination and 1 != 0, + /* downWithTwoPointers= */ combination and 2 != 0, + /* downFromEdge= */ combination and 4 != 0, + /* isSingleShade= */ combination and 8 != 0, + /* isCommunalAvailable= */ combination and 16 != 0, + ) + ) + } + } + + @JvmStatic + @BeforeClass + fun setUp() { + val combinationStrings = + combinations().map { array -> + check(array.size == 5) + "${array[4]},${array[3]},${array[2]},${array[1]},${array[0]}" + } + val uniqueCombinations = combinationStrings.toSet() + assertThat(combinationStrings).hasSize(uniqueCombinations.size) + } + + private fun expectedDownDestination( + downFromEdge: Boolean, + isSingleShade: Boolean, + ): SceneKey { + return if (downFromEdge && isSingleShade) Scenes.QuickSettings else Scenes.Shade + } + } + private val kosmos = testKosmos() private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } + @JvmField @Parameter(0) var canSwipeToEnter: Boolean = false + @JvmField @Parameter(1) var downWithTwoPointers: Boolean = false + @JvmField @Parameter(2) var downFromEdge: Boolean = false + @JvmField @Parameter(3) var isSingleShade: Boolean = true + @JvmField @Parameter(4) var isCommunalAvailable: Boolean = false + private val underTest by lazy { createLockscreenSceneViewModel() } @Test - fun upTransitionSceneKey_canSwipeToUnlock_gone() = + @EnableFlags(Flags.FLAG_COMMUNAL_HUB) + fun destinationScenes() = testScope.runTest { - val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - kosmos.fakeAuthenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.None - ) kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) - kosmos.fakeDeviceEntryRepository.setUnlocked(true) - sceneInteractor.changeScene(Scenes.Lockscreen, "reason") - - assertThat(upTransitionSceneKey).isEqualTo(Scenes.Gone) - } - - @Test - fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() = - testScope.runTest { - val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( - AuthenticationMethodModel.Pin + if (canSwipeToEnter) { + AuthenticationMethodModel.None + } else { + AuthenticationMethodModel.Pin + } ) - kosmos.fakeDeviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter) sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + kosmos.shadeRepository.setShadeMode( + if (isSingleShade) { + ShadeMode.Single + } else { + ShadeMode.Split + } + ) + kosmos.setCommunalAvailable(isCommunalAvailable) - assertThat(upTransitionSceneKey).isEqualTo(Scenes.Bouncer) - } - - @EnableFlags(FLAG_COMMUNAL_HUB) - @Test - fun leftTransitionSceneKey_communalIsAvailable_communal() = - testScope.runTest { - val leftDestinationSceneKey by collectLastValue(underTest.leftDestinationSceneKey) - assertThat(leftDestinationSceneKey).isNull() + val destinationScenes by collectLastValue(underTest.destinationScenes) - kosmos.setCommunalAvailable(true) - runCurrent() - assertThat(leftDestinationSceneKey).isEqualTo(Scenes.Communal) - } + assertThat( + destinationScenes + ?.get( + Swipe( + SwipeDirection.Down, + fromSource = Edge.Top.takeIf { downFromEdge }, + pointerCount = if (downWithTwoPointers) 2 else 1, + ) + ) + ?.toScene + ) + .isEqualTo( + expectedDownDestination( + downFromEdge = downFromEdge, + isSingleShade = isSingleShade, + ) + ) - @Test - fun downFromTopEdgeDestinationSceneKey_whenNotSplitShade_quickSettings() = - testScope.runTest { - overrideResource(R.bool.config_use_split_notification_shade, false) - kosmos.shadeStartable.start() - val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey) - assertThat(sceneKey).isEqualTo(Scenes.QuickSettings) - } + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene) + .isEqualTo(if (canSwipeToEnter) Scenes.Gone else Scenes.Bouncer) - @Test - fun downFromTopEdgeDestinationSceneKey_whenSplitShade_null() = - testScope.runTest { - overrideResource(R.bool.config_use_split_notification_shade, true) - kosmos.shadeStartable.start() - val sceneKey by collectLastValue(underTest.downFromTopEdgeDestinationSceneKey) - assertThat(sceneKey).isNull() + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene) + .isEqualTo(Scenes.Communal.takeIf { isCommunalAvailable }) } private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel { 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/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 42c33544416d..af9abcda73fe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -26,7 +26,6 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.Swipe -import com.android.compose.animation.scene.SwipeDirection import com.android.internal.R import com.android.internal.util.EmergencyAffordanceManager import com.android.internal.util.emergencyAffordanceManager @@ -317,8 +316,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { @Test fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() = testScope.runTest { - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition( to = upDestinationSceneKey, @@ -337,8 +336,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) emulateUserDrivenTransition( to = upDestinationSceneKey, @@ -356,7 +355,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { emulateUserDrivenTransition(to = Scenes.Shade) assertCurrentScene(Scenes.Shade) - val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen) emulateUserDrivenTransition( to = upDestinationSceneKey, @@ -379,7 +378,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { emulateUserDrivenTransition(to = Scenes.Shade) assertCurrentScene(Scenes.Shade) - val upDestinationSceneKey = destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) emulateUserDrivenTransition( to = upDestinationSceneKey, @@ -447,8 +446,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() = testScope.runTest { unlockDevice() - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone) } @@ -469,8 +468,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() = testScope.runTest { setAuthMethod(AuthenticationMethodModel.Password) - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition( to = upDestinationSceneKey, @@ -487,8 +486,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun bouncerActionButtonClick_opensEmergencyServicesDialer() = testScope.runTest { setAuthMethod(AuthenticationMethodModel.Password) - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) @@ -507,8 +506,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { testScope.runTest { setAuthMethod(AuthenticationMethodModel.Password) startPhoneCall() - val upDestinationSceneKey by - collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey) + val destinationScenes by collectLastValue(lockscreenSceneViewModel.destinationScenes) + val upDestinationSceneKey = destinationScenes?.get(Swipe.Up)?.toScene assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer) emulateUserDrivenTransition(to = upDestinationSceneKey) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt index d3fa3603d722..cd79ed1a8965 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt @@ -54,7 +54,7 @@ class ShadeControllerSceneImplTest : SysuiTestCase() { private val kosmos = Kosmos() private val testScope = kosmos.testScope private val sceneInteractor = kosmos.sceneInteractor - private val deviceEntryInteractor = kosmos.deviceEntryInteractor + private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } private lateinit var shadeInteractor: ShadeInteractor private lateinit var underTest: ShadeControllerSceneImpl 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/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt index a2f3ccb8c416..e06efe8c4021 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt @@ -113,6 +113,28 @@ class AudioVolumeInteractorTest : SysuiTestCase() { } @Test + fun zenMuted_cantChange() { + with(kosmos) { + testScope.runTest { + notificationsSoundPolicyRepository.updateNotificationPolicy() + notificationsSoundPolicyRepository.updateZenMode( + ZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) + ) + + val canChangeVolume by + collectLastValue( + underTest.canChangeVolume(AudioStream(AudioManager.STREAM_NOTIFICATION)) + ) + + underTest.setMuted(AudioStream(AudioManager.STREAM_RING), true) + runCurrent() + + assertThat(canChangeVolume).isFalse() + } + } + } + + @Test fun streamIsMuted_getStream_volumeZero() { with(kosmos) { testScope.runTest { diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index f51e1098f333..7341015e8690 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -304,6 +304,15 @@ <!-- An explanation text that the password needs to be entered since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] --> <string name="kg_prompt_reason_timeout_password">For additional security, use password instead</string> + <!-- An explanation text that the pin needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] --> + <string name="kg_prompt_added_security_pin">PIN required for additional security</string> + + <!-- An explanation text that the pattern needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] --> + <string name="kg_prompt_added_security_pattern">Pattern required for additional security</string> + + <!-- An explanation text that the password needs to be provided to enter the device for security reasons. [CHAR LIMIT=70] --> + <string name="kg_prompt_added_security_password">Password required for additional security</string> + <!-- An explanation text that the credential needs to be entered because a device admin has locked the device. [CHAR LIMIT=80] --> <string name="kg_prompt_reason_device_admin">Device locked by admin</string> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 25596cce3b97..b8f20f6ad8a2 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1536,8 +1536,12 @@ <!-- Media device casting volume slider label [CHAR_LIMIT=20] --> <string name="media_device_cast">Cast</string> - <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=40]--> + <!-- A message shown when the notification volume changing is disabled because of the muted ring stream [CHAR_LIMIT=50]--> <string name="stream_notification_unavailable">Unavailable because ring is muted</string> + <!-- A message shown when the alarm volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]--> + <string name="stream_alarm_unavailable">Unavailable because Do Not Disturb is on</string> + <!-- A message shown when the media volume changing is disabled because of the don't disturb mode [CHAR_LIMIT=50]--> + <string name="stream_media_unavailable">Unavailable because Do Not Disturb is on</string> <!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on vibrate. [CHAR_LIMIT=NONE] --> <!-- Shown in the header of quick settings to indicate to the user that their phone ringer is on silent (muted). [CHAR_LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 4e7809ade792..59516be65a5e 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -965,6 +965,10 @@ <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> </style> + <style name="Widget.SliceView.VolumePanel"> + <item name="hideHeaderRow">true</item> + </style> + <style name="Theme.VolumePanelActivity.Popup" parent="@style/Theme.SystemUI.Dialog"> <item name="android:dialogCornerRadius">44dp</item> <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainerHigh diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 84c8ea708031..26e91b62d19a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -122,7 +122,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { case PROMPT_REASON_USER_REQUEST: return R.string.kg_prompt_after_user_lockdown_password; case PROMPT_REASON_PREPARE_FOR_UPDATE: - return R.string.kg_prompt_reason_timeout_password; + return R.string.kg_prompt_added_security_password; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: return R.string.kg_prompt_reason_timeout_password; case PROMPT_REASON_TRUSTAGENT_EXPIRED: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index bf8900da887a..caa74780538e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -323,7 +323,7 @@ public class KeyguardPatternViewController resId = R.string.kg_prompt_after_user_lockdown_pattern; break; case PROMPT_REASON_PREPARE_FOR_UPDATE: - resId = R.string.kg_prompt_reason_timeout_pattern; + resId = R.string.kg_prompt_added_security_pattern; break; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: resId = R.string.kg_prompt_reason_timeout_pattern; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index bcab6f054dd6..fbe9edfd6680 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -134,7 +134,7 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView case PROMPT_REASON_USER_REQUEST: return R.string.kg_prompt_after_user_lockdown_pin; case PROMPT_REASON_PREPARE_FOR_UPDATE: - return R.string.kg_prompt_reason_timeout_pin; + return R.string.kg_prompt_added_security_pin; case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: return R.string.kg_prompt_reason_timeout_pin; case PROMPT_REASON_TRUSTAGENT_EXPIRED: 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/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..986391791b2c 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 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..983ec2adbe91 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 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/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt index c25e748f8668..7f6fc914e92b 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt @@ -23,10 +23,12 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.Flags +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.bouncer.data.repository.BouncerMessageRepository import com.android.systemui.bouncer.shared.model.BouncerMessageModel +import com.android.systemui.bouncer.shared.model.BouncerMessageStrings import com.android.systemui.bouncer.shared.model.Message import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -35,46 +37,6 @@ import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.TrustRepository -import com.android.systemui.res.R.string.bouncer_face_not_recognized -import com.android.systemui.res.R.string.keyguard_enter_password -import com.android.systemui.res.R.string.keyguard_enter_pattern -import com.android.systemui.res.R.string.keyguard_enter_pin -import com.android.systemui.res.R.string.kg_bio_too_many_attempts_password -import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pattern -import com.android.systemui.res.R.string.kg_bio_too_many_attempts_pin -import com.android.systemui.res.R.string.kg_bio_try_again_or_password -import com.android.systemui.res.R.string.kg_bio_try_again_or_pattern -import com.android.systemui.res.R.string.kg_bio_try_again_or_pin -import com.android.systemui.res.R.string.kg_face_locked_out -import com.android.systemui.res.R.string.kg_fp_not_recognized -import com.android.systemui.res.R.string.kg_primary_auth_locked_out_password -import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pattern -import com.android.systemui.res.R.string.kg_primary_auth_locked_out_pin -import com.android.systemui.res.R.string.kg_prompt_after_adaptive_auth_lock -import com.android.systemui.res.R.string.kg_prompt_after_dpm_lock -import com.android.systemui.res.R.string.kg_prompt_after_update_password -import com.android.systemui.res.R.string.kg_prompt_after_update_pattern -import com.android.systemui.res.R.string.kg_prompt_after_update_pin -import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_password -import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pattern -import com.android.systemui.res.R.string.kg_prompt_after_user_lockdown_pin -import com.android.systemui.res.R.string.kg_prompt_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_password_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_pattern_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_pin_auth_timeout -import com.android.systemui.res.R.string.kg_prompt_reason_restart_password -import com.android.systemui.res.R.string.kg_prompt_reason_restart_pattern -import com.android.systemui.res.R.string.kg_prompt_reason_restart_pin -import com.android.systemui.res.R.string.kg_prompt_unattended_update -import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown -import com.android.systemui.res.R.string.kg_trust_agent_disabled -import com.android.systemui.res.R.string.kg_unlock_with_password_or_fp -import com.android.systemui.res.R.string.kg_unlock_with_pattern_or_fp -import com.android.systemui.res.R.string.kg_unlock_with_pin_or_fp -import com.android.systemui.res.R.string.kg_wrong_input_try_fp_suggestion -import com.android.systemui.res.R.string.kg_wrong_password_try_again -import com.android.systemui.res.R.string.kg_wrong_pattern_try_again -import com.android.systemui.res.R.string.kg_wrong_pin_try_again import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.Quint import javax.inject.Inject @@ -130,17 +92,22 @@ constructor( repository.setMessage( when (biometricSourceType) { BiometricSourceType.FINGERPRINT -> - incorrectFingerprintInput(currentSecurityMode) + BouncerMessageStrings.incorrectFingerprintInput( + currentSecurityMode.toAuthModel() + ) + .toMessage() BiometricSourceType.FACE -> - incorrectFaceInput( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.incorrectFaceInput( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() else -> - defaultMessage( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.defaultMessage( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } ) } @@ -189,45 +156,79 @@ constructor( trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot ) { if (wasRebootedForMainlineUpdate) { - authRequiredForMainlineUpdate(currentSecurityMode) + BouncerMessageStrings.authRequiredForMainlineUpdate( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else { - authRequiredAfterReboot(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterReboot( + currentSecurityMode.toAuthModel() + ) + .toMessage() } } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) { - authRequiredAfterPrimaryAuthTimeout(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterPrimaryAuthTimeout( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) { - authRequiredAfterAdminLockdown(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterAdminLockdown( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else if ( - trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate + trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredForUnattendedUpdate ) { - authRequiredForUnattendedUpdate(currentSecurityMode) + BouncerMessageStrings.authRequiredForUnattendedUpdate( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else if (fpLockedOut) { - class3AuthLockedOut(currentSecurityMode) + BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel()) + .toMessage() } else if (faceLockedOut) { if (isFaceAuthClass3) { - class3AuthLockedOut(currentSecurityMode) + BouncerMessageStrings.class3AuthLockedOut(currentSecurityMode.toAuthModel()) + .toMessage() } else { - faceLockedOut(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.faceLockedOut( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } } else if (flags.isSomeAuthRequiredAfterAdaptiveAuthRequest) { - authRequiredAfterAdaptiveAuthRequest( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.authRequiredAfterAdaptiveAuthRequest( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if ( trustOrBiometricsAvailable && flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout ) { - nonStrongAuthTimeout( - currentSecurityMode, - isFingerprintAuthCurrentlyAllowed.value - ) + BouncerMessageStrings.nonStrongAuthTimeout( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterUserRequest) { - trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.trustAgentDisabled( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if (isTrustUsuallyManaged && flags.someAuthRequiredAfterTrustAgentExpired) { - trustAgentDisabled(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.trustAgentDisabled( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) { - authRequiredAfterUserLockdown(currentSecurityMode) + BouncerMessageStrings.authRequiredAfterUserLockdown( + currentSecurityMode.toAuthModel() + ) + .toMessage() } else { defaultMessage } @@ -244,7 +245,11 @@ constructor( override fun onTick(millisUntilFinished: Long) { val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt() - val message = primaryAuthLockedOut(currentSecurityMode) + val message = + BouncerMessageStrings.primaryAuthLockedOut( + currentSecurityMode.toAuthModel() + ) + .toMessage() message.message?.animate = false message.message?.formatterArgs = mutableMapOf<String, Any>(Pair("count", secondsRemaining)) @@ -258,7 +263,11 @@ constructor( if (!Flags.revampedBouncerMessages()) return repository.setMessage( - incorrectSecurityInput(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + BouncerMessageStrings.incorrectSecurityInput( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() ) } @@ -285,7 +294,12 @@ constructor( } private val defaultMessage: BouncerMessageModel - get() = defaultMessage(currentSecurityMode, isFingerprintAuthCurrentlyAllowed.value) + get() = + BouncerMessageStrings.defaultMessage( + currentSecurityMode.toAuthModel(), + isFingerprintAuthCurrentlyAllowed.value + ) + .toMessage() fun onPrimaryBouncerUserInput() { if (!Flags.revampedBouncerMessages()) return @@ -354,283 +368,35 @@ private fun defaultMessage( return BouncerMessageModel( message = Message( - messageResId = defaultMessage(securityMode, fpAuthIsAllowed).message?.messageResId, + messageResId = + BouncerMessageStrings.defaultMessage( + securityMode.toAuthModel(), + fpAuthIsAllowed + ) + .toMessage() + .message + ?.messageResId, animate = false ), secondaryMessage = Message(message = secondaryMessage, animate = false) ) } -private fun defaultMessage( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) { - defaultMessageWithFingerprint(securityMode) - } else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0) - SecurityMode.Password -> Pair(keyguard_enter_password, 0) - SecurityMode.PIN -> Pair(keyguard_enter_pin, 0) - else -> Pair(0, 0) - }.toMessage() -} - -private fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, 0) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectSecurityInput( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) { - incorrectSecurityInputWithFingerprint(securityMode) - } else - when (securityMode) { - SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0) - SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0) - SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, 0) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion) - SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion) - SecurityMode.PIN -> Pair(kg_wrong_pin_try_again, kg_wrong_input_try_fp_suggestion) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern) - SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password) - SecurityMode.PIN -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectFaceInput( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode) - else - when (securityMode) { - SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern) - SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password) - SecurityMode.PIN -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun incorrectFaceInputWithFingerprintAllowed( - securityMode: SecurityMode -): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, bouncer_face_not_recognized) - else -> Pair(0, 0) - }.toMessage() -} - -private fun biometricLockout(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterAdaptiveAuthRequest( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) authRequiredAfterAdaptiveAuthRequestFingerprintAllowed(securityMode) - else - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_adaptive_auth_lock) - SecurityMode.Password -> - Pair(keyguard_enter_password, kg_prompt_after_adaptive_auth_lock) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_adaptive_auth_lock) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterAdaptiveAuthRequestFingerprintAllowed( - securityMode: SecurityMode -): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> - Pair(kg_unlock_with_pattern_or_fp, kg_prompt_after_adaptive_auth_lock) - SecurityMode.Password -> - Pair(kg_unlock_with_password_or_fp, kg_prompt_after_adaptive_auth_lock) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_after_adaptive_auth_lock) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern) - SecurityMode.Password -> - Pair(keyguard_enter_password, kg_prompt_after_user_lockdown_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_unattended_update) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredForMainlineUpdate(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_update_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_update_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_after_update_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout) - else -> Pair(0, 0) - }.toMessage() -} - -private fun nonStrongAuthTimeout( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) { - nonStrongAuthTimeoutWithFingerprintAllowed(securityMode) - } else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_prompt_auth_timeout) - else -> Pair(0, 0) - }.toMessage() -} - -fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_prompt_auth_timeout) - else -> Pair(0, 0) - }.toMessage() -} - -private fun faceLockedOut( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) faceLockedOutButFingerprintAvailable(securityMode) - else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_face_locked_out) - else -> Pair(0, 0) - }.toMessage() -} - -private fun faceLockedOutButFingerprintAvailable(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_face_locked_out) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_face_locked_out) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_face_locked_out) - else -> Pair(0, 0) - }.toMessage() -} - -private fun class3AuthLockedOut(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_bio_too_many_attempts_pin) - else -> Pair(0, 0) - }.toMessage() -} - -private fun trustAgentDisabled( - securityMode: SecurityMode, - fpAuthIsAllowed: Boolean -): BouncerMessageModel { - return if (fpAuthIsAllowed) trustAgentDisabledWithFingerprintAllowed(securityMode) - else - when (securityMode) { - SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled) - SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled) - SecurityMode.PIN -> Pair(keyguard_enter_pin, kg_trust_agent_disabled) - else -> Pair(0, 0) - }.toMessage() -} - -private fun trustAgentDisabledWithFingerprintAllowed( - securityMode: SecurityMode -): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled) - SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled) - SecurityMode.PIN -> Pair(kg_unlock_with_pin_or_fp, kg_trust_agent_disabled) - else -> Pair(0, 0) - }.toMessage() -} - -private fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessageModel { - return when (securityMode) { - SecurityMode.Pattern -> - Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern) - SecurityMode.Password -> - Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_password) - SecurityMode.PIN -> - Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pin) - else -> Pair(0, 0) - }.toMessage() -} - private fun Pair<Int, Int>.toMessage(): BouncerMessageModel { return BouncerMessageModel( message = Message(messageResId = this.first, animate = false), secondaryMessage = Message(messageResId = this.second, animate = false) ) } + +private fun SecurityMode.toAuthModel(): AuthenticationMethodModel { + return when (this) { + SecurityMode.Invalid -> AuthenticationMethodModel.None + SecurityMode.None -> AuthenticationMethodModel.None + SecurityMode.Pattern -> AuthenticationMethodModel.Pattern + SecurityMode.Password -> AuthenticationMethodModel.Password + SecurityMode.PIN -> AuthenticationMethodModel.Pin + SecurityMode.SimPin -> AuthenticationMethodModel.Sim + SecurityMode.SimPuk -> AuthenticationMethodModel.Sim + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt index c3d4cb30e700..7d3075a9dd74 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt @@ -44,6 +44,8 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -70,6 +72,9 @@ constructor( val isLockedEsim: StateFlow<Boolean?> = repository.isLockedEsim val errorDialogMessage: StateFlow<String?> = repository.errorDialogMessage + private val _bouncerMessageChanged = MutableSharedFlow<String?>() + val bouncerMessageChanged: SharedFlow<String?> = _bouncerMessageChanged + /** Returns the default message for the sim pin screen. */ fun getDefaultMessage(): String { val isEsimLocked = repository.isLockedEsim.value ?: false @@ -81,7 +86,7 @@ constructor( return "" } - var count = telephonyManager.activeModemCount + val count = telephonyManager.activeModemCount val info: SubscriptionInfo? = repository.activeSubscriptionInfo.value val displayName = info?.displayName var msg: String = @@ -156,32 +161,24 @@ constructor( repository.setSimVerificationErrorMessage(null) } - /** - * Based on sim state, unlock the locked sim with the given credentials. - * - * @return Any message that should show associated with the provided input. Null means that no - * message needs to be shown. - */ - suspend fun verifySim(input: List<Any>): String? { + /** Based on sim state, unlock the locked sim with the given credentials. */ + suspend fun verifySim(input: List<Any>) { + val code = input.joinToString(separator = "") if (repository.isSimPukLocked.value) { - return verifySimPuk(input.joinToString(separator = "")) + verifySimPuk(code) + } else { + verifySimPin(code) } - - return verifySimPin(input.joinToString(separator = "")) } - /** - * Verifies the input and unlocks the locked sim with a 4-8 digit pin code. - * - * @return Any message that should show associated with the provided input. Null means that no - * message needs to be shown. - */ - private suspend fun verifySimPin(input: String): String? { + /** Verifies the input and unlocks the locked sim with a 4-8 digit pin code. */ + private suspend fun verifySimPin(input: String) { val subscriptionId = repository.subscriptionId.value // A SIM PIN is 4 to 8 decimal digits according to // GSM 02.17 version 5.0.1, Section 5.6 PIN Management if (input.length < MIN_SIM_PIN_LENGTH || input.length > MAX_SIM_PIN_LENGTH) { - return resources.getString(R.string.kg_invalid_sim_pin_hint) + _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint)) + return } val result = withContext(backgroundDispatcher) { @@ -190,8 +187,10 @@ constructor( telephonyManager.supplyIccLockPin(input) } when (result.result) { - PinResult.PIN_RESULT_TYPE_SUCCESS -> + PinResult.PIN_RESULT_TYPE_SUCCESS -> { keyguardUpdateMonitor.reportSimUnlocked(subscriptionId) + _bouncerMessageChanged.emit(null) + } PinResult.PIN_RESULT_TYPE_INCORRECT -> { if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) { // Show a dialog to display the remaining number of attempts to verify the sim @@ -199,24 +198,22 @@ constructor( repository.setSimVerificationErrorMessage( getPinPasswordErrorMessage(result.attemptsRemaining) ) + _bouncerMessageChanged.emit(null) } else { - return getPinPasswordErrorMessage(result.attemptsRemaining) + _bouncerMessageChanged.emit( + getPinPasswordErrorMessage(result.attemptsRemaining) + ) } } } - - return null } /** * Verifies the input and unlocks the locked sim with a puk code instead of pin. * * This occurs after incorrectly verifying the sim pin multiple times. - * - * @return Any message that should show associated with the provided input. Null means that no - * message needs to be shown. */ - private suspend fun verifySimPuk(entry: String): String? { + private suspend fun verifySimPuk(entry: String) { val (enteredSimPuk, enteredSimPin) = repository.simPukInputModel val subscriptionId: Int = repository.subscriptionId.value @@ -224,10 +221,11 @@ constructor( if (enteredSimPuk == null) { if (entry.length >= MIN_SIM_PUK_LENGTH) { repository.setSimPukUserInput(enteredSimPuk = entry) - return resources.getString(R.string.kg_puk_enter_pin_hint) + _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint)) } else { - return resources.getString(R.string.kg_invalid_sim_puk_hint) + _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_puk_hint)) } + return } // Stage 2: Set a new sim pin to lock the sim card. @@ -237,10 +235,11 @@ constructor( enteredSimPuk = enteredSimPuk, enteredSimPin = entry, ) - return resources.getString(R.string.kg_enter_confirm_pin_hint) + _bouncerMessageChanged.emit(resources.getString(R.string.kg_enter_confirm_pin_hint)) } else { - return resources.getString(R.string.kg_invalid_sim_pin_hint) + _bouncerMessageChanged.emit(resources.getString(R.string.kg_invalid_sim_pin_hint)) } + return } // Stage 3: Confirm the newly set sim pin. @@ -250,7 +249,8 @@ constructor( resources.getString(R.string.kg_invalid_confirm_pin_hint) ) repository.setSimPukUserInput(enteredSimPuk = enteredSimPuk) - return resources.getString(R.string.kg_puk_enter_pin_hint) + _bouncerMessageChanged.emit(resources.getString(R.string.kg_puk_enter_pin_hint)) + return } val result = @@ -261,9 +261,11 @@ constructor( resetSimPukUserInput() when (result.result) { - PinResult.PIN_RESULT_TYPE_SUCCESS -> + PinResult.PIN_RESULT_TYPE_SUCCESS -> { keyguardUpdateMonitor.reportSimUnlocked(subscriptionId) - PinResult.PIN_RESULT_TYPE_INCORRECT -> + _bouncerMessageChanged.emit(null) + } + PinResult.PIN_RESULT_TYPE_INCORRECT -> { if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) { // Show a dialog to display the remaining number of attempts to verify the sim // puk to the user. @@ -274,17 +276,21 @@ constructor( isEsimLocked = repository.isLockedEsim.value == true ) ) + _bouncerMessageChanged.emit(null) } else { - return getPukPasswordErrorMessage( - result.attemptsRemaining, - isDefault = false, - isEsimLocked = repository.isLockedEsim.value == true + _bouncerMessageChanged.emit( + getPukPasswordErrorMessage( + result.attemptsRemaining, + isDefault = false, + isEsimLocked = repository.isLockedEsim.value == true + ) ) } - else -> return resources.getString(R.string.kg_password_puk_failed) + } + else -> { + _bouncerMessageChanged.emit(resources.getString(R.string.kg_password_puk_failed)) + } } - - return null } private fun getPinPasswordErrorMessage(attemptsRemaining: Int): String { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt new file mode 100644 index 000000000000..cb12ce50dd23 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/model/BouncerMessageStrings.kt @@ -0,0 +1,267 @@ +/* + * 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.bouncer.shared.model + +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin +import com.android.systemui.res.R + +typealias BouncerMessagePair = Pair<Int, Int> + +val BouncerMessagePair.primaryMessage: Int + get() = this.first + +val BouncerMessagePair.secondaryMessage: Int + get() = this.second + +object BouncerMessageStrings { + private val EmptyMessage = Pair(0, 0) + + fun defaultMessage( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), 0) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), 0) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), 0) + else -> EmptyMessage + } + } + + fun incorrectSecurityInput( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMessage = incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed) + return when (securityMode) { + Pattern -> Pair(R.string.kg_wrong_pattern_try_again, secondaryMessage) + Password -> Pair(R.string.kg_wrong_password_try_again, secondaryMessage) + Pin -> Pair(R.string.kg_wrong_pin_try_again, secondaryMessage) + else -> EmptyMessage + } + } + + private fun incorrectSecurityInputSecondaryMessage(fpAuthIsAllowed: Boolean): Int { + return if (fpAuthIsAllowed) R.string.kg_wrong_input_try_fp_suggestion else 0 + } + + fun incorrectFingerprintInput(securityMode: AuthenticationMethodModel): BouncerMessagePair { + val primaryMessage = R.string.kg_fp_not_recognized + return when (securityMode) { + Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern) + Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password) + Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin) + else -> EmptyMessage + } + } + + fun incorrectFaceInput( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + return if (fpAuthIsAllowed) incorrectFaceInputWithFingerprintAllowed(securityMode) + else { + val primaryMessage = R.string.bouncer_face_not_recognized + when (securityMode) { + Pattern -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pattern) + Password -> Pair(primaryMessage, R.string.kg_bio_try_again_or_password) + Pin -> Pair(primaryMessage, R.string.kg_bio_try_again_or_pin) + else -> EmptyMessage + } + } + } + + private fun incorrectFaceInputWithFingerprintAllowed( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + val secondaryMsg = R.string.bouncer_face_not_recognized + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(true), secondaryMsg) + Password -> Pair(passwordDefaultMessage(true), secondaryMsg) + Pin -> Pair(pinDefaultMessage(true), secondaryMsg) + else -> EmptyMessage + } + } + + fun authRequiredAfterReboot(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_reason_restart_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_reason_restart_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_reason_restart_pin) + else -> EmptyMessage + } + } + + fun authRequiredAfterAdminLockdown( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_prompt_after_dpm_lock + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), secondaryMsg) + Password -> Pair(passwordDefaultMessage(false), secondaryMsg) + Pin -> Pair(pinDefaultMessage(false), secondaryMsg) + else -> EmptyMessage + } + } + + fun authRequiredAfterAdaptiveAuthRequest( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_prompt_after_adaptive_auth_lock + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun authRequiredAfterUserLockdown(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> + Pair(patternDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_user_lockdown_pin) + else -> EmptyMessage + } + } + + fun authRequiredForUnattendedUpdate( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_added_security_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_added_security_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_added_security_pin) + else -> EmptyMessage + } + } + + fun authRequiredForMainlineUpdate(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_after_update_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_after_update_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_after_update_pin) + else -> EmptyMessage + } + } + + fun authRequiredAfterPrimaryAuthTimeout( + securityMode: AuthenticationMethodModel + ): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_prompt_pattern_auth_timeout) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_prompt_password_auth_timeout) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_prompt_pin_auth_timeout) + else -> EmptyMessage + } + } + + fun nonStrongAuthTimeout( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_prompt_auth_timeout + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun faceLockedOut( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_face_locked_out + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun class3AuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(false), R.string.kg_bio_too_many_attempts_pattern) + Password -> + Pair(passwordDefaultMessage(false), R.string.kg_bio_too_many_attempts_password) + Pin -> Pair(pinDefaultMessage(false), R.string.kg_bio_too_many_attempts_pin) + else -> EmptyMessage + } + } + + fun trustAgentDisabled( + securityMode: AuthenticationMethodModel, + fpAuthIsAllowed: Boolean + ): BouncerMessagePair { + val secondaryMsg = R.string.kg_trust_agent_disabled + return when (securityMode) { + Pattern -> Pair(patternDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Password -> Pair(passwordDefaultMessage(fpAuthIsAllowed), secondaryMsg) + Pin -> Pair(pinDefaultMessage(fpAuthIsAllowed), secondaryMsg) + else -> EmptyMessage + } + } + + fun primaryAuthLockedOut(securityMode: AuthenticationMethodModel): BouncerMessagePair { + return when (securityMode) { + Pattern -> + Pair( + R.string.kg_too_many_failed_attempts_countdown, + R.string.kg_primary_auth_locked_out_pattern + ) + Password -> + Pair( + R.string.kg_too_many_failed_attempts_countdown, + R.string.kg_primary_auth_locked_out_password + ) + Pin -> + Pair( + R.string.kg_too_many_failed_attempts_countdown, + R.string.kg_primary_auth_locked_out_pin + ) + else -> EmptyMessage + } + } + + private fun patternDefaultMessage(fingerprintAllowed: Boolean): Int { + return if (fingerprintAllowed) R.string.kg_unlock_with_pattern_or_fp + else R.string.keyguard_enter_pattern + } + + private fun pinDefaultMessage(fingerprintAllowed: Boolean): Int { + return if (fingerprintAllowed) R.string.kg_unlock_with_pin_or_fp + else R.string.keyguard_enter_pin + } + + private fun passwordDefaultMessage(fingerprintAllowed: Boolean): Int { + return if (fingerprintAllowed) R.string.kg_unlock_with_password_or_fp + else R.string.keyguard_enter_password + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 7f4a0296ebdc..e910a9271ee2 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -157,8 +157,7 @@ class PinBouncerViewModel( if (authenticationMethod == AuthenticationMethodModel.Sim) { viewModelScope.launch { isSimUnlockingDialogVisible.value = true - val msg = simBouncerInteractor.verifySim(getInput()) - interactor.setMessage(msg) + simBouncerInteractor.verifySim(getInput()) isSimUnlockingDialogVisible.value = false clearInput() } 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/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt index 96171aa6566e..d495facdfe9d 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.keyguard.shared.model.AuthenticationFlags import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -31,9 +32,22 @@ class DeviceEntryBiometricSettingsInteractor constructor( repository: BiometricSettingsRepository, ) { + + /** + * Flags that control the device entry authentication behavior. + * + * This exposes why biometrics may not be currently allowed. + */ + val authenticationFlags: Flow<AuthenticationFlags> = repository.authenticationFlags + + /** Whether the current user has enrolled and enabled fingerprint auth. */ + val isFingerprintAuthEnrolledAndEnabled: Flow<Boolean> = + repository.isFingerprintEnrolledAndEnabled + val fingerprintAuthCurrentlyAllowed: Flow<Boolean> = repository.isFingerprintAuthCurrentlyAllowed - val faceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled + /** Whether the current user has enrolled and enabled face auth. */ + val isFaceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled val faceAuthCurrentlyAllowed: Flow<Boolean> = repository.isFaceAuthCurrentlyAllowed /** Whether both fingerprint and face are enrolled and enabled for device entry. */ diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt index 99bd25bf0e52..7733de49ce9c 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt @@ -32,6 +32,10 @@ interface DeviceEntryFaceAuthInteractor { /** Current detection status */ val detectionStatus: Flow<FaceDetectionStatus> + val lockedOut: Flow<Boolean> + + val authenticated: Flow<Boolean> + /** Can face auth be run right now */ fun canFaceAuthRun(): Boolean diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt index a5f6f7c77a38..805999397282 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt @@ -16,6 +16,8 @@ package com.android.systemui.deviceentry.domain.interactor +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus @@ -23,14 +25,20 @@ import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationS import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.map +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class DeviceEntryFingerprintAuthInteractor @Inject constructor( repository: DeviceEntryFingerprintAuthRepository, + biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, + fingerprintPropertyRepository: FingerprintPropertyRepository, ) { /** Whether fingerprint authentication is currently running or not */ val isRunning: Flow<Boolean> = repository.isRunning @@ -47,4 +55,21 @@ constructor( repository.authenticationStatus.filterIsInstance<ErrorFingerprintAuthenticationStatus>() val fingerprintHelp: Flow<HelpFingerprintAuthenticationStatus> = repository.authenticationStatus.filterIsInstance<HelpFingerprintAuthenticationStatus>() + + /** + * Whether fingerprint authentication is currently allowed for the user. This is true if the + * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by + * [com.android.systemui.keyguard.shared.model.AuthenticationFlags] and not locked out due to + * too many incorrect attempts. + */ + val isFingerprintAuthCurrentlyAllowed: Flow<Boolean> = + combine(isLockedOut, biometricSettingsInteractor.fingerprintAuthCurrentlyAllowed, ::Pair) + .map { (lockedOut, currentlyAllowed) -> !lockedOut && currentlyAllowed } + + /** + * Whether the fingerprint sensor is present under the display as opposed to being on the power + * button or behind/rear of the phone. + */ + val isSensorUnderDisplay = + fingerprintPropertyRepository.sensorType.map(FingerprintSensorType::isUdfps) } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 029a4f33cd27..fa2421a3516d 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -16,24 +16,30 @@ package com.android.systemui.deviceentry.domain.interactor +import androidx.annotation.VisibleForTesting import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository -import com.android.systemui.keyguard.data.repository.TrustRepository +import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason +import com.android.systemui.flags.SystemPropertiesHelper +import com.android.systemui.keyguard.domain.interactor.TrustInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.util.kotlin.Quad import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart @@ -55,10 +61,13 @@ constructor( private val repository: DeviceEntryRepository, private val authenticationInteractor: AuthenticationInteractor, private val sceneInteractor: SceneInteractor, - deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository, - trustRepository: TrustRepository, + faceAuthInteractor: DeviceEntryFaceAuthInteractor, + private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, + private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, + private val trustInteractor: TrustInteractor, flags: SceneContainerFlags, deviceUnlockedInteractor: DeviceUnlockedInteractor, + private val systemPropertiesHelper: SystemPropertiesHelper, ) { /** * Whether the device is unlocked. @@ -96,8 +105,8 @@ constructor( */ private val isPassivelyAuthenticated = merge( - trustRepository.isCurrentUserTrusted, - deviceEntryFaceAuthRepository.isAuthenticated, + trustInteractor.isTrusted, + faceAuthInteractor.authenticated, ) .onStart { emit(false) } @@ -134,6 +143,67 @@ constructor( initialValue = null, ) + private val faceEnrolledAndEnabled = biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled + private val fingerprintEnrolledAndEnabled = + biometricSettingsInteractor.isFingerprintAuthEnrolledAndEnabled + private val trustAgentEnabled = trustInteractor.isEnrolledAndEnabled + + private val faceOrFingerprintOrTrustEnabled: Flow<Triple<Boolean, Boolean, Boolean>> = + combine(faceEnrolledAndEnabled, fingerprintEnrolledAndEnabled, trustAgentEnabled, ::Triple) + + /** + * Reason why device entry is restricted to certain authentication methods for the current user. + * + * Emits null when there are no device entry restrictions active. + */ + val deviceEntryRestrictionReason: Flow<DeviceEntryRestrictionReason?> = + faceOrFingerprintOrTrustEnabled.flatMapLatest { + (faceEnabled, fingerprintEnabled, trustEnabled) -> + if (faceEnabled || fingerprintEnabled || trustEnabled) { + combine( + biometricSettingsInteractor.authenticationFlags, + faceAuthInteractor.lockedOut, + fingerprintAuthInteractor.isLockedOut, + trustInteractor.isTrustAgentCurrentlyAllowed, + ::Quad + ) + .map { (authFlags, isFaceLockedOut, isFingerprintLockedOut, trustManaged) -> + when { + authFlags.isPrimaryAuthRequiredAfterReboot && + wasRebootedForMainlineUpdate -> + DeviceEntryRestrictionReason.DeviceNotUnlockedSinceMainlineUpdate + authFlags.isPrimaryAuthRequiredAfterReboot -> + DeviceEntryRestrictionReason.DeviceNotUnlockedSinceReboot + authFlags.isPrimaryAuthRequiredAfterDpmLockdown -> + DeviceEntryRestrictionReason.PolicyLockdown + authFlags.isInUserLockdown -> DeviceEntryRestrictionReason.UserLockdown + authFlags.isPrimaryAuthRequiredForUnattendedUpdate -> + DeviceEntryRestrictionReason.UnattendedUpdate + authFlags.isPrimaryAuthRequiredAfterTimeout -> + DeviceEntryRestrictionReason.SecurityTimeout + authFlags.isPrimaryAuthRequiredAfterLockout -> + DeviceEntryRestrictionReason.BouncerLockedOut + isFingerprintLockedOut -> + DeviceEntryRestrictionReason.StrongBiometricsLockedOut + isFaceLockedOut && faceAuthInteractor.isFaceAuthStrong() -> + DeviceEntryRestrictionReason.StrongBiometricsLockedOut + isFaceLockedOut -> DeviceEntryRestrictionReason.NonStrongFaceLockedOut + authFlags.isSomeAuthRequiredAfterAdaptiveAuthRequest -> + DeviceEntryRestrictionReason.AdaptiveAuthRequest + (trustEnabled && !trustManaged) && + (authFlags.someAuthRequiredAfterTrustAgentExpired || + authFlags.someAuthRequiredAfterUserRequest) -> + DeviceEntryRestrictionReason.TrustAgentDisabled + authFlags.strongerAuthRequiredAfterNonStrongBiometricsTimeout -> + DeviceEntryRestrictionReason.NonStrongBiometricsSecurityTimeout + else -> null + } + } + } else { + flowOf(null) + } + } + /** * Attempt to enter the device and dismiss the lockscreen. If authentication is required to * unlock the device it will transition to bouncer. @@ -187,4 +257,12 @@ constructor( } } } + + private val wasRebootedForMainlineUpdate + get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE + + companion object { + @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last" + @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update" + } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt index fd6fbc9610f4..98deda09613e 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt @@ -77,7 +77,7 @@ constructor( private fun startUpdatingFaceHelpMessageDeferral() { scope.launch { - biometricSettingsInteractor.faceAuthEnrolledAndEnabled + biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled .flatMapLatest { faceEnrolledAndEnabled -> if (faceEnrolledAndEnabled) { faceAcquired @@ -94,7 +94,7 @@ constructor( } scope.launch { - biometricSettingsInteractor.faceAuthEnrolledAndEnabled + biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled .flatMapLatest { faceEnrolledAndEnabled -> if (faceEnrolledAndEnabled) { faceHelp diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt index 3b9416690a85..65f3eb762693 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt @@ -31,10 +31,10 @@ import kotlinx.coroutines.flow.emptyFlow */ @SysUISingleton class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceAuthInteractor { - override val authenticationStatus: Flow<FaceAuthenticationStatus> - get() = emptyFlow() - override val detectionStatus: Flow<FaceDetectionStatus> - get() = emptyFlow() + override val authenticationStatus: Flow<FaceAuthenticationStatus> = emptyFlow() + override val detectionStatus: Flow<FaceDetectionStatus> = emptyFlow() + override val lockedOut: Flow<Boolean> = emptyFlow() + override val authenticated: Flow<Boolean> = emptyFlow() override fun canFaceAuthRun(): Boolean = false diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt index 0c9fbc27cc5d..a7266503b7a1 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt @@ -272,6 +272,8 @@ constructor( /** Provide the status of face detection */ override val detectionStatus = repository.detectionStatus + override val lockedOut: Flow<Boolean> = repository.isLockedOut + override val authenticated: Flow<Boolean> = repository.isAuthenticated private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) { if (repository.isLockedOut.value) { diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt new file mode 100644 index 000000000000..5b672ac372db --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceEntryRestrictionReason.kt @@ -0,0 +1,119 @@ +/* + * 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.deviceentry.shared.model + +/** List of reasons why device entry can be restricted to certain authentication methods. */ +enum class DeviceEntryRestrictionReason { + /** + * Reason: Lockdown initiated by the user. + * + * Restriction: Only bouncer based device entry is allowed. + */ + UserLockdown, + + /** + * Reason: Not unlocked since reboot. + * + * Restriction: Only bouncer based device entry is allowed. + */ + DeviceNotUnlockedSinceReboot, + + /** + * Reason: Not unlocked since reboot after a mainline update. + * + * Restriction: Only bouncer based device entry is allowed. + */ + DeviceNotUnlockedSinceMainlineUpdate, + + /** + * Reason: Lockdown initiated by admin through installed device policy + * + * Restriction: Only bouncer based device entry is allowed. + */ + PolicyLockdown, + + /** + * Reason: Device entry credentials need to be used for an unattended update at a later point in + * time. + * + * Restriction: Only bouncer based device entry is allowed. + */ + UnattendedUpdate, + + /** + * Reason: Device was not unlocked using PIN/Pattern/Password for a prolonged period of time. + * + * Restriction: Only bouncer based device entry is allowed. + */ + SecurityTimeout, + + /** + * Reason: A "class 3"/strong biometrics device entry method was locked out after many incorrect + * authentication attempts. + * + * Restriction: Only bouncer based device entry is allowed. + * + * @see + * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes) + */ + StrongBiometricsLockedOut, + + /** + * Reason: A weak (class 2)/convenience (class 3) strength face biometrics device entry method + * was locked out after many incorrect authentication attempts. + * + * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed. + * + * @see + * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes) + */ + NonStrongFaceLockedOut, + + /** + * Reason: Device was last unlocked using a weak/convenience strength biometrics device entry + * method and a stronger authentication method wasn't used to unlock the device for a prolonged + * period of time. + * + * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed. + * + * @see + * [Biometric classes](https://source.android.com/docs/security/features/biometric/measure#biometric-classes) + */ + NonStrongBiometricsSecurityTimeout, + + /** + * Reason: A trust agent that was granting trust has either expired or disabled by the user by + * opening the power menu. + * + * Restriction: Only non trust agent device entry methods are allowed. + */ + TrustAgentDisabled, + + /** + * Reason: Theft protection is enabled after too many unlock attempts. + * + * Restriction: Only stronger authentication methods (class 3 or bouncer) are allowed. + */ + AdaptiveAuthRequest, + + /** + * Reason: Bouncer was locked out after too many incorrect authentication attempts. + * + * Restriction: Only bouncer based device entry is allowed. + */ + BouncerLockedOut, +} 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/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt index 6fa20de1fb7f..1e3c6049edd4 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt @@ -20,36 +20,34 @@ import android.os.SystemProperties import javax.inject.Inject import javax.inject.Singleton -/** - * Proxy to make {@link SystemProperties} easily testable. - */ +/** Proxy to make {@link SystemProperties} easily testable. */ @Singleton open class SystemPropertiesHelper @Inject constructor() { - fun get(name: String): String { + open fun get(name: String): String { return SystemProperties.get(name) } - fun get(name: String, def: String?): String { + open fun get(name: String, def: String?): String { return SystemProperties.get(name, def) } - fun getBoolean(name: String, default: Boolean): Boolean { + open fun getBoolean(name: String, default: Boolean): Boolean { return SystemProperties.getBoolean(name, default) } - fun setBoolean(name: String, value: Boolean) { + open fun setBoolean(name: String, value: Boolean) { SystemProperties.set(name, if (value) "1" else "0") } - fun set(name: String, value: String) { + open fun set(name: String, value: String) { SystemProperties.set(name, value) } - fun set(name: String, value: Int) { + open fun set(name: String, value: Int) { set(name, value.toString()) } - fun erase(name: String) { + open fun erase(name: String) { set(name, "") } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt new file mode 100644 index 000000000000..0143b85a4fbf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/LongPressHapticBuilder.kt @@ -0,0 +1,115 @@ +/* + * 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.haptics.qs + +import android.os.VibrationEffect +import android.util.Log +import kotlin.math.max + +object LongPressHapticBuilder { + + const val INVALID_DURATION = 0 /* in ms */ + + private const val TAG = "LongPressHapticBuilder" + private const val SPIN_SCALE = 0.2f + private const val CLICK_SCALE = 0.5f + private const val LOW_TICK_SCALE = 0.08f + private const val WARMUP_TIME = 75 /* in ms */ + private const val DAMPING_TIME = 24 /* in ms */ + + /** Create the signal that indicates that a long-press action is available. */ + fun createLongPressHint( + lowTickDuration: Int, + spinDuration: Int, + effectDuration: Int + ): VibrationEffect? { + if (lowTickDuration == 0 || spinDuration == 0) { + Log.d( + TAG, + "The LOW_TICK and/or SPIN primitives are not supported. No signal created.", + ) + return null + } + if (effectDuration < WARMUP_TIME + spinDuration + DAMPING_TIME) { + Log.d( + TAG, + "Cannot fit long-press hint signal in the effect duration. No signal created", + ) + return null + } + + val nLowTicks = WARMUP_TIME / lowTickDuration + val rampDownLowTicks = DAMPING_TIME / lowTickDuration + val composition = VibrationEffect.startComposition() + + // Warmup low ticks + repeat(nLowTicks) { + composition.addPrimitive( + VibrationEffect.Composition.PRIMITIVE_LOW_TICK, + LOW_TICK_SCALE, + 0, + ) + } + + // Spin effect + composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, SPIN_SCALE, 0) + + // Damping low ticks + repeat(rampDownLowTicks) { i -> + composition.addPrimitive( + VibrationEffect.Composition.PRIMITIVE_LOW_TICK, + LOW_TICK_SCALE / (i + 1), + 0, + ) + } + + return composition.compose() + } + + /** Create a "snapping" effect that triggers at the end of a long-press gesture */ + fun createSnapEffect(): VibrationEffect? = + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, CLICK_SCALE, 0) + .compose() + + /** Creates a signal that indicates the reversal of the long-press animation. */ + fun createReversedEffect( + pausedProgress: Float, + lowTickDuration: Int, + effectDuration: Int, + ): VibrationEffect? { + val duration = pausedProgress * effectDuration + if (duration == 0f) return null + + if (lowTickDuration == 0) { + Log.d(TAG, "Cannot play reverse haptics because LOW_TICK is not supported") + return null + } + + val nLowTicks = (duration / lowTickDuration).toInt() + if (nLowTicks == 0) return null + + val composition = VibrationEffect.startComposition() + var scale: Float + val step = LOW_TICK_SCALE / nLowTicks + repeat(nLowTicks) { i -> + scale = max(LOW_TICK_SCALE - step * i, 0f) + composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale, 0) + } + return composition.compose() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt new file mode 100644 index 000000000000..ec72a1422973 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt @@ -0,0 +1,237 @@ +/* + * 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.haptics.qs + +import android.animation.ValueAnimator +import android.annotation.SuppressLint +import android.os.VibrationEffect +import android.view.MotionEvent +import android.view.View +import android.view.ViewConfiguration +import android.view.animation.AccelerateDecelerateInterpolator +import androidx.annotation.VisibleForTesting +import androidx.core.animation.doOnCancel +import androidx.core.animation.doOnEnd +import androidx.core.animation.doOnStart +import com.android.systemui.statusbar.VibratorHelper +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +/** + * A class that handles the long press visuo-haptic effect for a QS tile. + * + * The class is also a [View.OnTouchListener] to handle the touch events, clicks and long-press + * gestures of the tile. The class also provides a [State] that can be used to determine the current + * state of the long press effect. + * + * @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects. + * @property[effectDuration] The duration of the effect in ms. + */ +class QSLongPressEffect( + private val vibratorHelper: VibratorHelper?, + private val effectDuration: Int, +) : View.OnTouchListener { + + /** Current state */ + var state = State.IDLE + @VisibleForTesting set + + /** Flows for view control and action */ + private val _effectProgress = MutableStateFlow<Float?>(null) + val effectProgress = _effectProgress.asStateFlow() + + private val _actionType = MutableStateFlow<ActionType?>(null) + val actionType = _actionType.asStateFlow() + + /** Haptic effects */ + private val durations = + vibratorHelper?.getPrimitiveDurations( + VibrationEffect.Composition.PRIMITIVE_LOW_TICK, + VibrationEffect.Composition.PRIMITIVE_SPIN + ) + + private val longPressHint = + LongPressHapticBuilder.createLongPressHint( + durations?.get(0) ?: LongPressHapticBuilder.INVALID_DURATION, + durations?.get(1) ?: LongPressHapticBuilder.INVALID_DURATION, + effectDuration + ) + + private val snapEffect = LongPressHapticBuilder.createSnapEffect() + + /* A coroutine scope and a timer job that waits for the pressedTimeout */ + var scope: CoroutineScope? = null + private var waitJob: Job? = null + + private val effectAnimator = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = effectDuration.toLong() + interpolator = AccelerateDecelerateInterpolator() + + doOnStart { handleAnimationStart() } + addUpdateListener { _effectProgress.value = animatedValue as Float } + doOnEnd { handleAnimationComplete() } + doOnCancel { handleAnimationCancel() } + } + + private fun reverse() { + val pausedProgress = effectAnimator.animatedFraction + val effect = + LongPressHapticBuilder.createReversedEffect( + pausedProgress, + durations?.get(0) ?: 0, + effectDuration, + ) + vibratorHelper?.cancel() + vibrate(effect) + effectAnimator.reverse() + } + + private fun vibrate(effect: VibrationEffect?) { + if (vibratorHelper != null && effect != null) { + vibratorHelper.vibrate(effect) + } + } + + /** + * Handle relevant touch events for the operation of a Tile. + * + * A click action is performed following the relevant logic that originates from the + * [MotionEvent.ACTION_UP] event depending on the current state. + */ + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(view: View?, event: MotionEvent?): Boolean { + when (event?.actionMasked) { + MotionEvent.ACTION_DOWN -> handleActionDown() + MotionEvent.ACTION_UP -> handleActionUp() + MotionEvent.ACTION_CANCEL -> handleActionCancel() + } + return true + } + + private fun handleActionDown() { + when (state) { + State.IDLE -> { + startPressedTimeoutWait() + state = State.TIMEOUT_WAIT + } + State.RUNNING_BACKWARDS -> effectAnimator.cancel() + else -> {} + } + } + + private fun startPressedTimeoutWait() { + waitJob = + scope?.launch { + try { + delay(PRESSED_TIMEOUT) + handleTimeoutComplete() + } catch (_: CancellationException) { + state = State.IDLE + } + } + } + + private fun handleActionUp() { + when (state) { + State.TIMEOUT_WAIT -> { + waitJob?.cancel() + _actionType.value = ActionType.CLICK + state = State.IDLE + } + State.RUNNING_FORWARD -> { + reverse() + state = State.RUNNING_BACKWARDS + } + else -> {} + } + } + + private fun handleActionCancel() { + when (state) { + State.TIMEOUT_WAIT -> { + waitJob?.cancel() + state = State.IDLE + } + State.RUNNING_FORWARD -> { + reverse() + state = State.RUNNING_BACKWARDS + } + else -> {} + } + } + + private fun handleAnimationStart() { + vibrate(longPressHint) + state = State.RUNNING_FORWARD + } + + /** This function is called both when an animator completes or gets cancelled */ + private fun handleAnimationComplete() { + if (state == State.RUNNING_FORWARD) { + vibrate(snapEffect) + _actionType.value = ActionType.LONG_PRESS + _effectProgress.value = null + } + if (state != State.TIMEOUT_WAIT) { + // This will happen if the animator did not finish by being cancelled + state = State.IDLE + } + } + + private fun handleAnimationCancel() { + _effectProgress.value = 0f + startPressedTimeoutWait() + state = State.TIMEOUT_WAIT + } + + private fun handleTimeoutComplete() { + if (state == State.TIMEOUT_WAIT && !effectAnimator.isRunning) { + effectAnimator.start() + } + } + + fun clearActionType() { + _actionType.value = null + } + + enum class State { + IDLE, /* The effect is idle waiting for touch input */ + TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */ + RUNNING_FORWARD, /* The effect is running normally */ + RUNNING_BACKWARDS, /* The effect was interrupted and is now running backwards */ + } + + /* A type of action to perform on the view depending on the effect's state and logic */ + enum class ActionType { + CLICK, + LONG_PRESS, + } + + companion object { + /** + * A timeout to let the tile resolve if it is being swiped/scrolled. Since QS tiles are + * inside a scrollable container, they will be considered pressed only after a tap timeout. + */ + val PRESSED_TIMEOUT = ViewConfiguration.getTapTimeout().toLong() + 20L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt new file mode 100644 index 000000000000..e298154159b2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt @@ -0,0 +1,62 @@ +/* + * 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.haptics.qs + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.qs.tileimpl.QSTileViewImpl +import kotlinx.coroutines.launch + +object QSLongPressEffectViewBinder { + + fun bind( + tile: QSTileViewImpl, + effect: QSLongPressEffect?, + ) { + if (effect == null) return + + tile.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + effect.scope = this + + launch { + effect.effectProgress.collect { progress -> + progress?.let { + if (it == 0f) { + tile.bringToFront() + } + tile.updateLongPressEffectProperties(it) + } + } + } + + launch { + effect.actionType.collect { action -> + action?.let { + when (it) { + QSLongPressEffect.ActionType.CLICK -> tile.performClick() + QSLongPressEffect.ActionType.LONG_PRESS -> tile.performLongClick() + } + effect.clearActionType() + } + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index f5f5571dbb1b..882f2315f6e8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -175,7 +175,6 @@ constructor( pw.println("isFingerprintAuthCurrentlyAllowed=${isFingerprintAuthCurrentlyAllowed.value}") pw.println("isNonStrongBiometricAllowed=${isNonStrongBiometricAllowed.value}") pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}") - pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}") } /** UserId of the current selected user. */ @@ -324,22 +323,14 @@ constructor( else isNonStrongBiometricAllowed } - private val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> = - selectedUserId - .flatMapLatest { userId -> - devicePolicyChangedForAllUsers - .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) } - .flowOn(backgroundDispatcher) - .distinctUntilChanged() - } - .stateIn( - scope, - started = SharingStarted.Eagerly, - initialValue = - devicePolicyManager.isFingerprintDisabled( - userRepository.getSelectedUserInfo().id - ) - ) + private val isFingerprintEnabledByDevicePolicy: Flow<Boolean> = + selectedUserId.flatMapLatest { userId -> + devicePolicyChangedForAllUsers + .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) } + .onStart { emit(devicePolicyManager.isFingerprintDisabled(userId)) } + .flowOn(backgroundDispatcher) + .distinctUntilChanged() + } override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> = isFingerprintEnrolled diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt new file mode 100644 index 000000000000..2ff6e165293b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TrustInteractor.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.TrustRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +/** Encapsulates any state relevant to trust agents and trust grants. */ +@SysUISingleton +class TrustInteractor @Inject constructor(repository: TrustRepository) { + /** + * Whether the current user has a trust agent enabled. This is true if the user has at least one + * trust agent enabled in settings. + */ + val isEnrolledAndEnabled: StateFlow<Boolean> = repository.isCurrentUserTrustUsuallyManaged + + /** + * Whether the current user's trust agent is currently allowed, this will be false if trust + * agent is disabled for any reason (security timeout, disabled on lock screen by opening the + * power menu, etc), it does not include temporary biometric lockouts. + */ + val isTrustAgentCurrentlyAllowed: StateFlow<Boolean> = repository.isCurrentUserTrustManaged + + /** Whether the current user is trusted by any of the enabled trust agents. */ + val isTrusted: Flow<Boolean> = repository.isCurrentUserTrusted +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt index 08904b6ffa86..d6f3634fdcd5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt @@ -32,6 +32,9 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) { val isPrimaryAuthRequiredAfterTimeout = containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) + val isPrimaryAuthRequiredAfterLockout = + containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) + val isPrimaryAuthRequiredAfterDpmLockdown = containsFlag( flag, @@ -47,7 +50,7 @@ data class AuthenticationFlags(val userId: Int, val flag: Int) { LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED ) - val primaryAuthRequiredForUnattendedUpdate = + val isPrimaryAuthRequiredForUnattendedUpdate = containsFlag( flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 288ef3c52e21..993e81bfbf69 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -14,9 +14,15 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.ui.viewmodel -import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.Edge +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -27,9 +33,10 @@ import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn /** Models UI state and handles user input for the lockscreen scene. */ @@ -44,37 +51,69 @@ constructor( val longPress: KeyguardLongPressViewModel, val notifications: NotificationsPlaceholderViewModel, ) { - /** The key of the scene we should switch to when swiping up. */ - val upDestinationSceneKey: StateFlow<SceneKey> = - deviceEntryInteractor.isUnlocked - .map { isUnlocked -> upDestinationSceneKey(isUnlocked) } + val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = + combine( + deviceEntryInteractor.isUnlocked, + communalInteractor.isCommunalAvailable, + shadeInteractor.shadeMode, + ) { isDeviceUnlocked, isCommunalAvailable, shadeMode -> + destinationScenes( + isDeviceUnlocked = isDeviceUnlocked, + isCommunalAvailable = isCommunalAvailable, + shadeMode = shadeMode, + ) + } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = upDestinationSceneKey(deviceEntryInteractor.isUnlocked.value), + initialValue = + destinationScenes( + isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value, + isCommunalAvailable = false, + shadeMode = shadeInteractor.shadeMode.value, + ), ) - private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey { - return if (isUnlocked) Scenes.Gone else Scenes.Bouncer - } + private fun destinationScenes( + isDeviceUnlocked: Boolean, + isCommunalAvailable: Boolean, + shadeMode: ShadeMode, + ): Map<UserAction, UserActionResult> { + val quickSettingsIfSingleShade = + if (shadeMode is ShadeMode.Single) { + Scenes.QuickSettings + } else { + Scenes.Shade + } - /** The key of the scene we should switch to when swiping left. */ - val leftDestinationSceneKey: StateFlow<SceneKey?> = - communalInteractor.isCommunalAvailable - .map { available -> if (available) Scenes.Communal else null } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = null, - ) + return mapOf( + Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable }, + Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer, - /** The key of the scene we should switch to when swiping down from the top edge. */ - val downFromTopEdgeDestinationSceneKey: StateFlow<SceneKey?> = - shadeInteractor.shadeMode - .map { shadeMode -> Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single } } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = null, + // Swiping down from the top edge goes to QS (or shade if in split shade mode). + swipeDownFromTop(pointerCount = 1) to quickSettingsIfSingleShade, + swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade, + + // Swiping down, not from the edge, always navigates to the shade scene. + swipeDown(pointerCount = 1) to Scenes.Shade, + swipeDown(pointerCount = 2) to Scenes.Shade, ) + .filterValues { it != null } + .mapValues { checkNotNull(it.value) } + } + + private fun swipeDownFromTop(pointerCount: Int): Swipe { + return Swipe( + SwipeDirection.Down, + fromSource = Edge.Top, + pointerCount = pointerCount, + ) + } + + private fun swipeDown(pointerCount: Int): Swipe { + return Swipe( + SwipeDirection.Down, + pointerCount = pointerCount, + ) + } } 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/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 2440651555d7..cd6511979375 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -38,6 +38,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.settings.brightness.BrightnessController; import com.android.systemui.settings.brightness.BrightnessMirrorHandler; import com.android.systemui.settings.brightness.BrightnessSliderController; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.statusbar.policy.SplitShadeStateController; @@ -90,9 +91,11 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { FalsingManager falsingManager, StatusBarKeyguardViewManager statusBarKeyguardViewManager, SplitShadeStateController splitShadeStateController, - SceneContainerFlags sceneContainerFlags) { + SceneContainerFlags sceneContainerFlags, + VibratorHelper vibratorHelper) { super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, - metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController); + metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController, + vibratorHelper); mTunerService = tunerService; mQsCustomizerController = qsCustomizerController; mQsTileRevealControllerFactory = qsTileRevealControllerFactory; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 975c871bd006..5e12b9d4cc34 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -39,6 +39,7 @@ import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileViewImpl; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.ViewController; import com.android.systemui.util.animation.DisappearParameters; @@ -87,6 +88,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr private SplitShadeStateController mSplitShadeStateController; + private final VibratorHelper mVibratorHelper; + @VisibleForTesting protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @@ -144,7 +147,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr UiEventLogger uiEventLogger, QSLogger qsLogger, DumpManager dumpManager, - SplitShadeStateController splitShadeStateController + SplitShadeStateController splitShadeStateController, + VibratorHelper vibratorHelper ) { super(view); mHost = host; @@ -158,6 +162,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mSplitShadeStateController = splitShadeStateController; mShouldUseSplitNotificationShade = mSplitShadeStateController.shouldUseSplitNotificationShade(getResources()); + mVibratorHelper = vibratorHelper; } @Override @@ -300,7 +305,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr } private void addTile(final QSTile tile, boolean collapsedView) { - final QSTileViewImpl tileView = new QSTileViewImpl(getContext(), collapsedView); + final QSTileViewImpl tileView = new QSTileViewImpl( + getContext(), collapsedView, mVibratorHelper); final TileRecord r = new TileRecord(tile, tileView); // TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of // b/250618218. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index a8e88da5d288..05bb08813cc5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -32,6 +32,7 @@ import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.res.R; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.leak.RotationUtils; @@ -56,10 +57,11 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) Provider<Boolean> usingCollapsedLandscapeMediaProvider, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, - DumpManager dumpManager, SplitShadeStateController splitShadeStateController + DumpManager dumpManager, SplitShadeStateController splitShadeStateController, + VibratorHelper vibratorHelper ) { super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, - uiEventLogger, qsLogger, dumpManager, splitShadeStateController); + uiEventLogger, qsLogger, dumpManager, splitShadeStateController, vibratorHelper); mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt new file mode 100644 index 000000000000..a2ded6a6aacf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSLongPressProperties.kt @@ -0,0 +1,34 @@ +/* + * 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.qs.tileimpl + +/** + * List of properties that define the state of a tile during a long-press gesture. + * + * These properties are used during animation if a tile supports a long-press action. + */ +data class QSLongPressProperties( + var xScale: Float, + var yScale: Float, + var cornerRadius: Float, + var backgroundColor: Int, + var labelColor: Int, + var secondaryLabelColor: Int, + var chevronColor: Int, + var overlayColor: Int, + var iconColor: Int, +) 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/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 6cc682ae3c96..63963ded2923 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -26,6 +26,7 @@ import android.content.res.Resources.ID_NULL import android.graphics.Color import android.graphics.PorterDuff import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.os.Trace @@ -36,6 +37,7 @@ import android.util.TypedValue import android.view.Gravity import android.view.LayoutInflater import android.view.View +import android.view.ViewConfiguration import android.view.ViewGroup import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo @@ -48,9 +50,12 @@ import androidx.annotation.VisibleForTesting import com.android.app.tracing.traceSection import com.android.settingslib.Utils import com.android.systemui.Flags +import com.android.systemui.Flags.quickSettingsVisualHapticsLongpress import com.android.systemui.FontSizeUtils import com.android.systemui.animation.LaunchableView import com.android.systemui.animation.LaunchableViewDelegate +import com.android.systemui.haptics.qs.QSLongPressEffect +import com.android.systemui.haptics.qs.QSLongPressEffectViewBinder import com.android.systemui.plugins.qs.QSIconView import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.qs.QSTile.AdapterState @@ -58,12 +63,15 @@ import com.android.systemui.plugins.qs.QSTileView import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH import com.android.systemui.res.R +import com.android.systemui.statusbar.VibratorHelper +import com.android.systemui.util.children import java.util.Objects private const val TAG = "QSTileViewImpl" open class QSTileViewImpl @JvmOverloads constructor( context: Context, - private val collapsed: Boolean = false + private val collapsed: Boolean = false, + private val vibratorHelper: VibratorHelper? = null, ) : QSTileView(context), HeightOverrideable, LaunchableView { companion object { @@ -163,6 +171,7 @@ open class QSTileViewImpl @JvmOverloads constructor( private var lastStateDescription: CharSequence? = null private var tileState = false private var lastState = INVALID + private var lastIconTint = 0 private val launchableViewDelegate = LaunchableViewDelegate( this, superSetVisibility = { super.setVisibility(it) }, @@ -171,6 +180,12 @@ open class QSTileViewImpl @JvmOverloads constructor( private val locInScreen = IntArray(2) + /** Visuo-haptic long-press effects */ + private var longPressEffect: QSLongPressEffect? = null + private var initialLongPressProperties: QSLongPressProperties? = null + private var finalLongPressProperties: QSLongPressProperties? = null + private val colorEvaluator = ArgbEvaluator.getInstance() + init { val typedValue = TypedValue() if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) { @@ -339,6 +354,9 @@ open class QSTileViewImpl @JvmOverloads constructor( true } ) + if (quickSettingsVisualHapticsLongpress()) { + isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect] + } } private fun init( @@ -589,6 +607,27 @@ open class QSTileViewImpl @JvmOverloads constructor( lastState = state.state lastDisabledByPolicy = state.disabledByPolicy + lastIconTint = icon.getColor(state) + + // Long-press effects + if (quickSettingsVisualHapticsLongpress()){ + if (state.handlesLongClick) { + // initialize the long-press effect and set it as the touch listener + showRippleEffect = false + initializeLongPressEffect() + setOnTouchListener(longPressEffect) + QSLongPressEffectViewBinder.bind(this, longPressEffect) + } else { + // Long-press effects might have been enabled before but the new state does not + // handle a long-press. In this case, we go back to the behaviour of a regular tile + // and clean-up the resources + showRippleEffect = isClickable + setOnTouchListener(null) + longPressEffect = null + initialLongPressProperties = null + finalLongPressProperties = null + } + } } private fun setAllColors( @@ -709,6 +748,140 @@ open class QSTileViewImpl @JvmOverloads constructor( } } + override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties() + + fun updateLongPressEffectProperties(effectProgress: Float) { + if (!isLongClickable) return + setAllColors( + colorEvaluator.evaluate( + effectProgress, + initialLongPressProperties?.backgroundColor ?: 0, + finalLongPressProperties?.backgroundColor ?: 0, + ) as Int, + colorEvaluator.evaluate( + effectProgress, + initialLongPressProperties?.labelColor ?: 0, + finalLongPressProperties?.labelColor ?: 0, + ) as Int, + colorEvaluator.evaluate( + effectProgress, + initialLongPressProperties?.secondaryLabelColor ?: 0, + finalLongPressProperties?.secondaryLabelColor ?: 0, + ) as Int, + colorEvaluator.evaluate( + effectProgress, + initialLongPressProperties?.chevronColor ?: 0, + finalLongPressProperties?.chevronColor ?: 0, + ) as Int, + colorEvaluator.evaluate( + effectProgress, + initialLongPressProperties?.overlayColor ?: 0, + finalLongPressProperties?.overlayColor ?: 0, + ) as Int, + ) + icon.setTint( + icon.mIcon as ImageView, + colorEvaluator.evaluate( + effectProgress, + initialLongPressProperties?.iconColor ?: 0, + finalLongPressProperties?.iconColor ?: 0, + ) as Int, + ) + + val newScaleX = + interpolateFloat( + effectProgress, + initialLongPressProperties?.xScale ?: 1f, + finalLongPressProperties?.xScale ?: 1f, + ) + val newScaleY = + interpolateFloat( + effectProgress, + initialLongPressProperties?.xScale ?: 1f, + finalLongPressProperties?.xScale ?: 1f, + ) + val newRadius = + interpolateFloat( + effectProgress, + initialLongPressProperties?.cornerRadius ?: 0f, + finalLongPressProperties?.cornerRadius ?: 0f, + ) + scaleX = newScaleX + scaleY = newScaleY + for (child in children) { + child.scaleX = 1f / newScaleX + child.scaleY = 1f / newScaleY + } + changeCornerRadius(newRadius) + } + + private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float = + start + fraction * (end - start) + + private fun resetLongPressEffectProperties() { + scaleY = 1f + scaleX = 1f + for (child in children) { + child.scaleY = 1f + child.scaleX = 1f + } + changeCornerRadius(resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat()) + setAllColors( + getBackgroundColorForState(lastState, lastDisabledByPolicy), + getLabelColorForState(lastState, lastDisabledByPolicy), + getSecondaryLabelColorForState(lastState, lastDisabledByPolicy), + getChevronColorForState(lastState, lastDisabledByPolicy), + getOverlayColorForState(lastState), + ) + icon.setTint(icon.mIcon as ImageView, lastIconTint) + } + + private fun initializeLongPressEffect() { + initializeLongPressProperties() + longPressEffect = + QSLongPressEffect( + vibratorHelper, + ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(), + ) + } + + private fun initializeLongPressProperties() { + initialLongPressProperties = + QSLongPressProperties( + /* xScale= */1f, + /* yScale= */1f, + resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat(), + getBackgroundColorForState(lastState), + getLabelColorForState(lastState), + getSecondaryLabelColorForState(lastState), + getChevronColorForState(lastState), + getOverlayColorForState(lastState), + lastIconTint, + ) + + finalLongPressProperties = + QSLongPressProperties( + /* xScale= */1.1f, + /* yScale= */1.2f, + resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat() - 20, + getBackgroundColorForState(Tile.STATE_ACTIVE), + getLabelColorForState(Tile.STATE_ACTIVE), + getSecondaryLabelColorForState(Tile.STATE_ACTIVE), + getChevronColorForState(Tile.STATE_ACTIVE), + getOverlayColorForState(Tile.STATE_ACTIVE), + Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive), + ) + } + + private fun changeCornerRadius(radius: Float) { + for (i in 0 until backgroundDrawable.numberOfLayers) { + val layer = backgroundDrawable.getDrawable(i) + if (layer is GradientDrawable) { + layer.cornerRadius = radius + } + } + } + @VisibleForTesting internal fun getCurrentColors(): List<Int> = listOf( backgroundColor, 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/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt index f01e9bea1372..1f6d2122ebfc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt @@ -21,9 +21,7 @@ import android.app.Notification import android.content.Context import android.graphics.Bitmap import android.graphics.Rect -import android.graphics.drawable.Drawable import android.util.Log -import android.view.Display import android.view.KeyEvent import android.view.LayoutInflater import android.view.ScrollCaptureResponse @@ -32,45 +30,53 @@ import android.view.ViewTreeObserver import android.view.WindowInsets import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher +import androidx.appcompat.content.res.AppCompatResources import com.android.internal.logging.UiEventLogger import com.android.systemui.flags.FeatureFlags import com.android.systemui.res.R import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject /** * Legacy implementation of screenshot view methods. Just proxies the calls down into the original * ScreenshotView. */ -class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLogger) : - ScreenshotViewProxy { +class LegacyScreenshotViewProxy +@AssistedInject +constructor( + private val logger: UiEventLogger, + flags: FeatureFlags, + @Assisted private val context: Context, + @Assisted private val displayId: Int +) : ScreenshotViewProxy { override val view: ScreenshotView = LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView override val screenshotPreview: View - - override var defaultDisplay: Int = Display.DEFAULT_DISPLAY - set(value) { - view.setDefaultDisplay(value) - } - override var defaultTimeoutMillis: Long = 6000 - set(value) { - view.setDefaultTimeoutMillis(value) - } - override var flags: FeatureFlags? = null - set(value) { - view.setFlags(value) - } override var packageName: String = "" set(value) { + field = value view.setPackageName(value) } override var callbacks: ScreenshotView.ScreenshotViewCallback? = null set(value) { + field = value view.setCallbacks(value) } override var screenshot: ScreenshotData? = null set(value) { - view.setScreenshot(value) + field = value + value?.let { + val badgeBg = + AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background) + val user = it.userHandle + if (badgeBg != null && user != null) { + view.badgeScreenshot(context.packageManager.getUserBadgedIcon(badgeBg, user)) + } + view.setScreenshot(it) + } } override val isAttachedToWindow @@ -82,6 +88,8 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog init { view.setUiEventLogger(logger) + view.setDefaultDisplay(displayId) + view.setFlags(flags) addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) } setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) } if (LogConfig.DEBUG_WINDOW) { @@ -95,8 +103,6 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets) override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets) - override fun badgeScreenshot(userBadgedIcon: Drawable) = view.badgeScreenshot(userBadgedIcon) - override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator = view.createScreenshotDropInAnimation(screenRect, showFlash) @@ -130,14 +136,17 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog response: ScrollCaptureResponse, screenBitmap: Bitmap, newScreenshot: Bitmap, - screenshotTakenInPortrait: Boolean - ) = + screenshotTakenInPortrait: Boolean, + onTransitionPrepared: Runnable, + ) { view.prepareScrollingTransition( response, screenBitmap, newScreenshot, screenshotTakenInPortrait ) + view.post { onTransitionPrepared.run() } + } override fun startLongScreenshotTransition( transitionDestination: Rect, @@ -155,10 +164,19 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog override fun announceForAccessibility(string: String) = view.announceForAccessibility(string) - override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver - - override fun post(runnable: Runnable) { - view.post(runnable) + override fun prepareEntranceAnimation(runnable: Runnable) { + view.viewTreeObserver.addOnPreDrawListener( + object : ViewTreeObserver.OnPreDrawListener { + override fun onPreDraw(): Boolean { + if (LogConfig.DEBUG_WINDOW) { + Log.d(TAG, "onPreDraw: startAnimation") + } + view.viewTreeObserver.removeOnPreDrawListener(this) + runnable.run() + return true + } + } + ) } private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) { @@ -166,7 +184,7 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog if (LogConfig.DEBUG_INPUT) { Log.d(TAG, "Predictive Back callback dispatched") } - onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER) + onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER) } view.addOnAttachStateChangeListener( object : View.OnAttachStateChangeListener { @@ -201,7 +219,7 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog if (LogConfig.DEBUG_INPUT) { Log.d(TAG, "onKeyEvent: $keyCode") } - onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER) + onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER) return true } return false @@ -210,10 +228,9 @@ class LegacyScreenshotViewProxy(context: Context, private val logger: UiEventLog ) } - class Factory : ScreenshotViewProxy.Factory { - override fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy { - return LegacyScreenshotViewProxy(context, logger) - } + @AssistedFactory + interface Factory : ScreenshotViewProxy.Factory { + override fun getProxy(context: Context, displayId: Int): LegacyScreenshotViewProxy } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 6bab956ca09a..198a29c4ed5b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -228,7 +228,7 @@ public class ScreenshotController { // From WizardManagerHelper.java private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; - private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000; + static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000; private final WindowContext mContext; private final FeatureFlags mFlags; @@ -344,7 +344,7 @@ public class ScreenshotController { mMessageContainerController = messageContainerController; mAssistContentRequester = assistContentRequester; - mViewProxy = viewProxyFactory.getProxy(mContext, mUiEventLogger); + mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId); mScreenshotHandler.setOnTimeoutRunnable(() -> { if (DEBUG_UI) { @@ -460,7 +460,7 @@ public class ScreenshotController { attachWindow(); - boolean showFlash = true; + boolean showFlash; if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { if (screenshot.getScreenBounds() != null && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(), @@ -472,15 +472,14 @@ public class ScreenshotController { screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(), screenshot.getBitmap().getHeight())); } + } else { + showFlash = true; } - prepareAnimation(screenshot.getScreenBounds(), showFlash, () -> { - mMessageContainerController.onScreenshotTaken(screenshot); - }); + mViewProxy.prepareEntranceAnimation( + () -> startAnimation(screenshot.getScreenBounds(), showFlash, + () -> mMessageContainerController.onScreenshotTaken(screenshot))); - mViewProxy.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon( - mContext.getDrawable(R.drawable.overlay_badge_background), - screenshot.getUserHandle())); mViewProxy.setScreenshot(screenshot); // ignore system bar insets for the purpose of window layout @@ -596,9 +595,6 @@ public class ScreenshotController { setWindowFocusable(false); } }); - mViewProxy.setFlags(mFlags); - mViewProxy.setDefaultDisplay(mDisplayId); - mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); if (DEBUG_WINDOW) { Log.d(TAG, "setContentView: " + mViewProxy.getView()); @@ -606,22 +602,6 @@ public class ScreenshotController { setContentView(mViewProxy.getView()); } - private void prepareAnimation(Rect screenRect, boolean showFlash, - Runnable onAnimationComplete) { - mViewProxy.getViewTreeObserver().addOnPreDrawListener( - new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - if (DEBUG_WINDOW) { - Log.d(TAG, "onPreDraw: startAnimation"); - } - mViewProxy.getViewTreeObserver().removeOnPreDrawListener(this); - startAnimation(screenRect, showFlash, onAnimationComplete); - return true; - } - }); - } - private void enqueueScrollCaptureRequest(UserHandle owner) { // Wait until this window is attached to request because it is // the reference used to locate the target window (below). @@ -706,10 +686,14 @@ public class ScreenshotController { Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId, new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels)); - mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, - mScreenshotTakenInPortrait); - // delay starting scroll capture to make sure the scrim is up before the app moves - mViewProxy.post(() -> runBatchScrollCapture(response, owner)); + if (newScreenshot != null) { + // delay starting scroll capture to make sure scrim is up before the app moves + mViewProxy.prepareScrollingTransition( + response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait, + () -> runBatchScrollCapture(response, owner)); + } else { + Log.wtf(TAG, "failed to capture current screenshot for scroll transition"); + } }); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "requestScrollCapture failed", e); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 8a8766dbab94..1c5a8a1a9fd3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -26,6 +26,7 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL; import static com.android.systemui.screenshot.LogConfig.DEBUG_UI; import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW; import static com.android.systemui.screenshot.LogConfig.logTag; +import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS; import static java.util.Objects.requireNonNull; @@ -33,6 +34,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ValueAnimator; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.BroadcastOptions; import android.app.Notification; @@ -168,7 +170,6 @@ public class ScreenshotView extends FrameLayout implements private ScreenshotData mScreenshotData; private final InteractionJankMonitor mInteractionJankMonitor; - private long mDefaultTimeoutOfTimeoutHandler; private FeatureFlags mFlags; private final Bundle mInteractiveBroadcastOption; @@ -244,10 +245,6 @@ public class ScreenshotView extends FrameLayout implements return InteractionJankMonitor.getInstance(); } - void setDefaultTimeoutMillis(long timeout) { - mDefaultTimeoutOfTimeoutHandler = timeout; - } - public void hideScrollChip() { mScrollChip.setVisibility(View.GONE); } @@ -755,7 +752,7 @@ public class ScreenshotView extends FrameLayout implements InteractionJankMonitor.Configuration.Builder.withView( CUJ_TAKE_SCREENSHOT, mScreenshotStatic) .setTag("Actions") - .setTimeout(mDefaultTimeoutOfTimeoutHandler); + .setTimeout(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS); mInteractionJankMonitor.begin(builder); } }); @@ -781,7 +778,7 @@ public class ScreenshotView extends FrameLayout implements return animator; } - void badgeScreenshot(Drawable badge) { + void badgeScreenshot(@Nullable Drawable badge) { mScreenshotBadge.setImageDrawable(badge); mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt index d5c7f95ce289..182b8894677a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt @@ -21,23 +21,16 @@ import android.app.Notification import android.content.Context import android.graphics.Bitmap import android.graphics.Rect -import android.graphics.drawable.Drawable import android.view.ScrollCaptureResponse import android.view.View import android.view.ViewGroup -import android.view.ViewTreeObserver import android.view.WindowInsets -import com.android.internal.logging.UiEventLogger -import com.android.systemui.flags.FeatureFlags /** Abstraction of the surface between ScreenshotController and ScreenshotView */ interface ScreenshotViewProxy { val view: ViewGroup val screenshotPreview: View - var defaultDisplay: Int - var defaultTimeoutMillis: Long - var flags: FeatureFlags? var packageName: String var callbacks: ScreenshotView.ScreenshotViewCallback? var screenshot: ScreenshotData? @@ -49,7 +42,6 @@ interface ScreenshotViewProxy { fun reset() fun updateInsets(insets: WindowInsets) fun updateOrientation(insets: WindowInsets) - fun badgeScreenshot(userBadgedIcon: Drawable) fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator fun addQuickShareChip(quickShareAction: Notification.Action) fun setChipIntents(imageData: ScreenshotController.SavedImageData) @@ -61,7 +53,8 @@ interface ScreenshotViewProxy { response: ScrollCaptureResponse, screenBitmap: Bitmap, newScreenshot: Bitmap, - screenshotTakenInPortrait: Boolean + screenshotTakenInPortrait: Boolean, + onTransitionPrepared: Runnable, ) fun startLongScreenshotTransition( transitionDestination: Rect, @@ -73,10 +66,9 @@ interface ScreenshotViewProxy { fun stopInputListening() fun requestFocus() fun announceForAccessibility(string: String) - fun getViewTreeObserver(): ViewTreeObserver - fun post(runnable: Runnable) + fun prepareEntranceAnimation(runnable: Runnable) interface Factory { - fun getProxy(context: Context, logger: UiEventLogger): ScreenshotViewProxy + fun getProxy(context: Context, displayId: Int): ScreenshotViewProxy } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java index 71c2cb4a5cb9..5df6c45295b6 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TimeoutHandler.java @@ -40,7 +40,7 @@ public class TimeoutHandler extends Handler { private final Context mContext; private Runnable mOnTimeout; - private int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS; + int mDefaultTimeout = DEFAULT_TIMEOUT_MILLIS; @Inject public TimeoutHandler(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java index a00c81d43b9e..cdb9abb15e84 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java @@ -86,7 +86,8 @@ public abstract class ScreenshotModule { ScreenshotSoundControllerImpl screenshotSoundProviderImpl); @Provides - static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() { - return new LegacyScreenshotViewProxy.Factory(); + static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory( + LegacyScreenshotViewProxy.Factory legacyScreenshotViewProxyFactory) { + return legacyScreenshotViewProxyFactory; } } 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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index b8675500a351..8b791de429ed 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1753,10 +1753,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateKeyguardStatusViewAlignment(boolean animate) { + boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); if (migrateClocksToBlueprint()) { + mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered); return; } - boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); ConstraintLayout layout = mNotificationContainerParent; mKeyguardStatusViewController.updateAlignment( layout, mSplitShadeEnabled, shouldBeCentered, animate); 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/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 589537ef713b..c05c3c3df2c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -353,7 +353,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView nowExpanded = !isExpanded(); setUserExpanded(nowExpanded); } - notifyHeightChanged(true); + notifyHeightChanged(/* needsAnimation= */ true); mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded); mMetricsLogger.action(MetricsEvent.ACTION_NOTIFICATION_EXPANDER, nowExpanded); } @@ -776,7 +776,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mChildrenContainer.updateGroupOverflow(); } if (intrinsicBefore != getIntrinsicHeight()) { - notifyHeightChanged(false /* needsAnimation */); + notifyHeightChanged(/* needsAnimation= */ false); } if (isHeadsUp) { mMustStayOnScreen = true; @@ -824,7 +824,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mChildrenContainer != null) { mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount); } - notifyHeightChanged(false /* needsAnimation */); + notifyHeightChanged(/* needsAnimation= */ false); } } @@ -1086,7 +1086,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView boolean wasAboveShelf = isAboveShelf(); mIsPinned = pinned; if (intrinsicHeight != getIntrinsicHeight()) { - notifyHeightChanged(false /* needsAnimation */); + notifyHeightChanged(/* needsAnimation= */ false); } if (pinned) { setAnimationRunning(true); @@ -2609,7 +2609,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView onExpansionChanged(true /* userAction */, wasExpanded); if (!wasExpanded && isExpanded() && getActualHeight() != getIntrinsicHeight()) { - notifyHeightChanged(true /* needsAnimation */); + notifyHeightChanged(/* needsAnimation= */ true); } } @@ -2621,7 +2621,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mIsSummaryWithChildren) { mChildrenContainer.onExpansionChanged(); } - notifyHeightChanged(false /* needsAnimation */); + notifyHeightChanged(/* needsAnimation= */ false); } updateShelfIconColor(); } @@ -2659,7 +2659,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (expand != mIsSystemExpanded) { final boolean wasExpanded = isExpanded(); mIsSystemExpanded = expand; - notifyHeightChanged(false /* needsAnimation */); + notifyHeightChanged(/* needsAnimation= */ false); onExpansionChanged(false /* userAction */, wasExpanded); if (mIsSummaryWithChildren) { mChildrenContainer.updateGroupOverflow(); @@ -2678,7 +2678,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mIsSummaryWithChildren) { mChildrenContainer.updateGroupOverflow(); } - notifyHeightChanged(false /* needsAnimation */); + notifyHeightChanged(/* needsAnimation= */ false); } if (isAboveShelf() != wasAboveShelf) { mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf); @@ -2835,7 +2835,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView super.onLayout(changed, left, top, right, bottom); if (intrinsicBefore != getIntrinsicHeight() && (intrinsicBefore != 0 || getActualHeight() > 0)) { - notifyHeightChanged(true /* needsAnimation */); + notifyHeightChanged(/* needsAnimation= */ true); } if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.onParentHeightUpdate(); @@ -2878,7 +2878,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mSensitiveHiddenInGeneral = hideSensitive; int intrinsicAfter = getIntrinsicHeight(); if (intrinsicBefore != intrinsicAfter) { - notifyHeightChanged(true); + notifyHeightChanged(/* needsAnimation= */ true); } } @@ -3015,7 +3015,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (isChildInGroup()) { mGroupExpansionManager.setGroupExpanded(mEntry, true); } - notifyHeightChanged(false /* needsAnimation */); + notifyHeightChanged(/* needsAnimation= */ false); } public void setChildrenExpanded(boolean expanded, boolean animate) { 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/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index d4960d7a8dec..712f65df2e59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -328,7 +328,14 @@ public enum ScrimState { GLANCEABLE_HUB_OVER_DREAM { @Override public void prepare(ScrimState previousState) { - GLANCEABLE_HUB.prepare(previousState); + // No scrims should be visible by default in this state. + mBehindAlpha = 0; + mNotifAlpha = 0; + mFrontAlpha = 0; + + mFrontTint = Color.TRANSPARENT; + mBehindTint = mBackgroundColor; + mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT; } }; 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/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/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt index 71df8e53b5e2..1bceee9b2d34 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt @@ -36,3 +36,7 @@ sealed interface MediaDeviceSession { /** Current media state is unknown yet. */ data object Unknown : MediaDeviceSession } + +/** Returns true when the audio is playing for the [MediaDeviceSession]. */ +fun MediaDeviceSession.isPlaying(): Boolean = + this is MediaDeviceSession.Active && playbackState?.isActive == true diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt index 37661b53c98a..d49cb1ea6958 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.res.R import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession +import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel import javax.inject.Inject @@ -110,9 +111,6 @@ constructor( null, ) - private fun MediaDeviceSession.isPlaying(): Boolean = - this is MediaDeviceSession.Active && playbackState?.isActive == true - fun onBarClick(expandable: Expandable) { actionsInteractor.onBarClick(mediaDeviceSession.value, expandable) volumePanelViewModel.dismissPanel() diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt index faf743475579..532e517bf45d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt @@ -21,6 +21,7 @@ import android.media.AudioManager import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel +import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.common.shared.model.Icon import com.android.systemui.res.R import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor @@ -54,14 +55,6 @@ constructor( AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer, AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm, ) - private val mutedIconsByStream = - mapOf( - AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_volume_off, - AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_volume_off, - AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_volume_off, - AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_off, - AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_off, - ) private val labelsByStream = mapOf( AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music, @@ -74,6 +67,8 @@ constructor( mapOf( AudioStream(AudioManager.STREAM_NOTIFICATION) to R.string.stream_notification_unavailable, + AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm_unavailable, + AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable, ) private var value = 0f @@ -81,8 +76,9 @@ constructor( combine( audioVolumeInteractor.getAudioStream(audioStream), audioVolumeInteractor.canChangeVolume(audioStream), - ) { model, isEnabled -> - model.toState(value, isEnabled) + audioVolumeInteractor.ringerMode, + ) { model, isEnabled, ringerMode -> + model.toState(value, isEnabled, ringerMode) } .stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState) @@ -100,7 +96,11 @@ constructor( } } - private fun AudioStreamModel.toState(value: Float, isEnabled: Boolean): State { + private fun AudioStreamModel.toState( + value: Float, + isEnabled: Boolean, + ringerMode: RingerMode, + ): State { return State( value = volumeSliderInteractor.processVolumeToValue( @@ -110,7 +110,7 @@ constructor( isMuted, ), valueRange = volumeSliderInteractor.displayValueRange, - icon = getIcon(this), + icon = getIcon(ringerMode), label = labelsByStream[audioStream]?.let(context::getString) ?: error("No label for the stream: $audioStream"), disabledMessage = disabledTextByStream[audioStream]?.let(context::getString), @@ -119,14 +119,31 @@ constructor( ) } - private fun getIcon(model: AudioStreamModel): Icon { - val isMutedOrNoVolume = model.isMuted || model.volume == model.minVolume + private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon { + val isMutedOrNoVolume = isMuted || volume == minVolume val iconRes = if (isMutedOrNoVolume) { - mutedIconsByStream + when (audioStream.value) { + AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off + AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off + AudioManager.STREAM_RING -> + if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { + R.drawable.ic_volume_ringer_vibrate + } else { + R.drawable.ic_volume_off + } + AudioManager.STREAM_NOTIFICATION -> + if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { + R.drawable.ic_volume_ringer_vibrate + } else { + R.drawable.ic_volume_off + } + AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off + else -> null + } } else { - iconsByStream - }[audioStream] + iconsByStream[audioStream] + } ?: error("No icon for the stream: $audioStream") return Icon.Resource(iconRes, null) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt index 2824323775d3..aaee24b9357f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt @@ -18,6 +18,8 @@ package com.android.systemui.volume.panel.component.volume.ui.viewmodel import android.media.AudioManager import com.android.settingslib.volume.shared.model.AudioStream +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isPlaying import com.android.systemui.volume.panel.component.volume.domain.interactor.CastVolumeInteractor import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel @@ -28,12 +30,17 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest +import kotlinx.coroutines.launch /** * Controls the behaviour of the whole audio @@ -46,6 +53,7 @@ class AudioVolumeComponentViewModel constructor( @VolumePanelScope private val scope: CoroutineScope, castVolumeInteractor: CastVolumeInteractor, + mediaOutputInteractor: MediaOutputInteractor, private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory, private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory, ) { @@ -90,4 +98,17 @@ constructor( remoteSessionsViewModels + streamViewModels } .stateIn(scope, SharingStarted.Eagerly, emptyList()) + + private val mutableIsExpanded = MutableSharedFlow<Boolean>() + + val isExpanded: StateFlow<Boolean> = + merge( + mutableIsExpanded.onStart { emit(false) }, + mediaOutputInteractor.mediaDeviceSession.map { !it.isPlaying() }, + ) + .stateIn(scope, SharingStarted.Eagerly, false) + + fun onExpandedChanged(isExpanded: Boolean) { + scope.launch { mutableIsExpanded.emit(isExpanded) } + } } 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/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt index e7963031411d..701b7039a1ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt @@ -401,7 +401,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to Pair("Enter PIN", "PIN is required after lockdown"), LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to - Pair("Enter PIN", "Update will install when device not in use"), + Pair("Enter PIN", "PIN required for additional security"), LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to Pair( @@ -439,7 +439,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to Pair("Enter PIN", "PIN is required after lockdown"), LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to - Pair("Enter PIN", "Update will install when device not in use"), + Pair("Enter PIN", "PIN required for additional security"), LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to Pair( @@ -481,7 +481,7 @@ class BouncerMessageInteractorTest : SysuiTestCase() { LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to Pair("Enter PIN", "PIN is required after lockdown"), LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to - Pair("Enter PIN", "Update will install when device not in use"), + Pair("Enter PIN", "PIN required for additional security"), LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to Pair( 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/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt index e1e9fcb2d059..dac88a340cb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt @@ -532,6 +532,18 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() { assertThat(faceAuthRepository.runningAuthRequest.value).isNull() } + @Test + fun lockedOut_providesSameValueFromRepository() = + testScope.runTest { + assertThat(underTest.lockedOut).isSameInstanceAs(faceAuthRepository.isLockedOut) + } + + @Test + fun authenticated_providesSameValueFromRepository() = + testScope.runTest { + assertThat(underTest.authenticated).isSameInstanceAs(faceAuthRepository.isAuthenticated) + } + companion object { private const val primaryUserId = 1 private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY) diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt new file mode 100644 index 000000000000..decbdaf0feee --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorTest.kt @@ -0,0 +1,75 @@ +/* + * 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.deviceentry.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.biometricSettingsRepository +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceEntryFingerprintAuthInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val underTest = kosmos.deviceEntryFingerprintAuthInteractor + private val fingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository + private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository + private val biometricSettingsRepository = kosmos.biometricSettingsRepository + + @Test + fun isFingerprintAuthCurrentlyAllowed_allowedOnlyWhenItIsNotLockedOutAndAllowedBySettings() = + testScope.runTest { + val currentlyAllowed by collectLastValue(underTest.isFingerprintAuthCurrentlyAllowed) + biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true) + fingerprintAuthRepository.setLockedOut(true) + + assertThat(currentlyAllowed).isFalse() + + fingerprintAuthRepository.setLockedOut(false) + assertThat(currentlyAllowed).isTrue() + + biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false) + assertThat(currentlyAllowed).isFalse() + } + + @Test + fun isSensorUnderDisplay_trueForUdfpsSensorTypes() = + testScope.runTest { + val isSensorUnderDisplay by collectLastValue(underTest.isSensorUnderDisplay) + + fingerprintPropertyRepository.supportsUdfps() + assertThat(isSensorUnderDisplay).isTrue() + + fingerprintPropertyRepository.supportsRearFps() + assertThat(isSensorUnderDisplay).isFalse() + + fingerprintPropertyRepository.supportsSideFps() + assertThat(isSensorUnderDisplay).isFalse() + } +} 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/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java index 65ede89a1514..0101741a9242 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java @@ -51,6 +51,7 @@ import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.util.animation.DisappearParameters; @@ -100,6 +101,8 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { Configuration mConfiguration; @Mock Runnable mHorizontalLayoutListener; + @Mock + VibratorHelper mVibratorHelper; private QSPanelControllerBase<QSPanel> mController; @@ -110,7 +113,8 @@ public class QSPanelControllerBaseTest extends SysuiTestCase { MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, DumpManager dumpManager) { super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger, - qsLogger, dumpManager, new ResourcesSplitShadeStateController()); + qsLogger, dumpManager, new ResourcesSplitShadeStateController(), + mVibratorHelper); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt index 85d7d9865c7c..916e8ddb6e8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt @@ -19,6 +19,7 @@ import com.android.systemui.qs.logging.QSLogger import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.settings.brightness.BrightnessController import com.android.systemui.settings.brightness.BrightnessSliderController +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.tuner.TunerService @@ -61,6 +62,7 @@ class QSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @Mock private lateinit var configuration: Configuration @Mock private lateinit var pagedTileLayout: PagedTileLayout + @Mock private lateinit var vibratorHelper: VibratorHelper private val sceneContainerFlags = FakeSceneContainerFlags() @@ -101,6 +103,7 @@ class QSPanelControllerTest : SysuiTestCase() { statusBarKeyguardViewManager, ResourcesSplitShadeStateController(), sceneContainerFlags, + vibratorHelper, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 2c1430844d12..71a9a8b3318f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.media.controls.ui.view.MediaHostState import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.customize.QSCustomizerController import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.util.leak.RotationUtils import org.junit.After @@ -59,6 +60,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var tile: QSTile @Mock private lateinit var tileLayout: TileLayout @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener> + @Mock private lateinit var vibratorHelper: VibratorHelper private val uiEventLogger = UiEventLoggerFake() private val dumpManager = DumpManager() @@ -89,7 +91,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() { metricsLogger, uiEventLogger, qsLogger, - dumpManager + dumpManager, + vibratorHelper, ) controller.init() @@ -157,7 +160,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() { metricsLogger: MetricsLogger, uiEventLogger: UiEventLoggerFake, qsLogger: QSLogger, - dumpManager: DumpManager + dumpManager: DumpManager, + vibratorHelper: VibratorHelper, ) : QuickQSPanelController( view, @@ -170,7 +174,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() { uiEventLogger, qsLogger, dumpManager, - ResourcesSplitShadeStateController() + ResourcesSplitShadeStateController(), + vibratorHelper, ) { private var rotation = RotationUtils.ROTATION_NONE 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/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index cdbbc9368db3..f9476400eb82 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -747,14 +747,16 @@ public class ScrimControllerTest extends SysuiTestCase { // Open the bouncer. mScrimController.setRawPanelExpansionFraction(0f); + when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true); mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE); finishAnimationsImmediately(); - // Only behind widget is visible. + // Only behind scrim is visible. assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, mNotificationsScrim, TRANSPARENT, mScrimBehind, OPAQUE)); + assertScrimTint(mScrimBehind, mSurfaceColor); // Bouncer is closed. mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN); @@ -773,8 +775,9 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB); // Open the shade. - mScrimController.transitionTo(SHADE_LOCKED); mScrimController.setQsPosition(1f, 0); + mScrimController.setRawPanelExpansionFraction(1); + mScrimController.setTransitionToFullShadeProgress(1, 0); finishAnimationsImmediately(); // Shade scrims are visible. @@ -782,8 +785,10 @@ public class ScrimControllerTest extends SysuiTestCase { mNotificationsScrim, OPAQUE, mScrimInFront, TRANSPARENT, mScrimBehind, OPAQUE)); + assertScrimTint(mScrimBehind, Color.BLACK); + assertScrimTint(mNotificationsScrim, Color.TRANSPARENT); - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB); + mScrimController.setTransitionToFullShadeProgress(0, 0); finishAnimationsImmediately(); // All scrims are transparent. @@ -813,14 +818,16 @@ public class ScrimControllerTest extends SysuiTestCase { // Open the bouncer. mScrimController.setRawPanelExpansionFraction(0f); + when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true); mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_VISIBLE); finishAnimationsImmediately(); - // Only behind widget is visible. + // Only behind scrim is visible. assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, mNotificationsScrim, TRANSPARENT, mScrimBehind, OPAQUE)); + assertScrimTint(mScrimBehind, mSurfaceColor); // Bouncer is closed. mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN); @@ -839,7 +846,6 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); // Open the shade. - mScrimController.transitionTo(SHADE_LOCKED); mScrimController.setQsPosition(1f, 0); mScrimController.setRawPanelExpansionFraction(1f); finishAnimationsImmediately(); @@ -849,8 +855,11 @@ public class ScrimControllerTest extends SysuiTestCase { mNotificationsScrim, OPAQUE, mScrimInFront, TRANSPARENT, mScrimBehind, OPAQUE)); + assertScrimTint(mScrimBehind, Color.BLACK); + assertScrimTint(mNotificationsScrim, Color.TRANSPARENT); - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); + mScrimController.setQsPosition(0f, 0); + mScrimController.setRawPanelExpansionFraction(0f); finishAnimationsImmediately(); // All scrims are transparent. 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/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 974e396fe280..5206db4aa13a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -95,6 +95,7 @@ import junit.framework.Assert; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -292,6 +293,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type); } + @Ignore("Causing breakages so ignoring to resolve, b/329099861") @Test @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER) public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt index 6dd8d07b356b..0660d0095c7d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt @@ -18,6 +18,7 @@ package com.android.systemui import com.android.systemui.classifier.FakeClassifierModule import com.android.systemui.data.FakeSystemUiDataLayerModule import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.FakeSystemPropertiesHelperModule import com.android.systemui.log.FakeUiEventLoggerModule import com.android.systemui.settings.FakeSettingsModule import com.android.systemui.statusbar.policy.FakeConfigurationControllerModule @@ -33,6 +34,7 @@ import dagger.Module FakeConfigurationControllerModule::class, FakeExecutorModule::class, FakeFeatureFlagsClassicModule::class, + FakeSystemPropertiesHelperModule::class, FakeSettingsModule::class, FakeSplitShadeStateControllerModule::class, FakeSystemClockModule::class, 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/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt index 69b769eb2321..bc0bf9dd069f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt @@ -27,6 +27,9 @@ import com.android.systemui.coroutines.collectValues import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor +import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor import com.android.systemui.scene.SceneContainerFrameworkModule import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneDataSource @@ -56,6 +59,7 @@ import kotlinx.coroutines.test.runTest CoroutineTestScopeModule::class, FakeSystemUiModule::class, SceneContainerFrameworkModule::class, + FaceWakeUpTriggersConfigModule::class, ] ) interface SysUITestModule { @@ -69,6 +73,11 @@ interface SysUITestModule { @Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor @Binds fun bindSceneDataSource(delegator: SceneDataSourceDelegator): SceneDataSource + @Binds + fun provideFaceAuthInteractor( + sysUIFaceAuthInteractor: SystemUIDeviceEntryFaceAuthInteractor + ): DeviceEntryFaceAuthInteractor + companion object { @Provides fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt index 62a1aa93f35a..3d84291292c9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt @@ -17,6 +17,7 @@ package com.android.systemui import android.app.ActivityManager import android.app.admin.DevicePolicyManager +import android.app.trust.TrustManager import android.os.UserManager import android.service.notification.NotificationListenerService import android.util.DisplayMetrics @@ -27,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardViewController import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager @@ -36,6 +38,7 @@ import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.log.LogBuffer import com.android.systemui.log.dagger.BiometricLog import com.android.systemui.log.dagger.BroadcastDispatcherLog +import com.android.systemui.log.dagger.FaceAuthLog import com.android.systemui.log.dagger.SceneFrameworkLog import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager import com.android.systemui.model.SysUiState @@ -65,10 +68,12 @@ import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.util.mockito.mock +import com.android.systemui.util.settings.GlobalSettings import com.android.wm.shell.bubbles.Bubbles import dagger.Binds import dagger.Module @@ -123,6 +128,10 @@ data class TestMocksModule( @get:Provides val deviceEntryIconTransitions: Set<DeviceEntryIconTransition> = emptySet(), @get:Provides val communalInteractor: CommunalInteractor = mock(), @get:Provides val sceneLogger: SceneLogger = mock(), + @get:Provides val trustManager: TrustManager = mock(), + @get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(), + @get:Provides val keyguardStateController: KeyguardStateController = mock(), + @get:Provides val globalSettings: GlobalSettings = mock(), // log buffers @get:[Provides BroadcastDispatcherLog] @@ -131,6 +140,8 @@ data class TestMocksModule( val sceneLogBuffer: LogBuffer = mock(), @get:[Provides BiometricLog] val biometricLogger: LogBuffer = mock(), + @get:[Provides FaceAuthLog] + val faceAuthLogger: LogBuffer = mock(), @get:Provides val lsShadeTransitionLogger: LSShadeTransitionLogger = mock(), // framework mocks diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt index 68ef55573dc8..8a951365ecea 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt @@ -19,10 +19,15 @@ package com.android.systemui.biometrics.data.repository import android.graphics.Point import com.android.systemui.biometrics.shared.model.LockoutMode +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -class FakeFacePropertyRepository : FacePropertyRepository { +@SysUISingleton +class FakeFacePropertyRepository @Inject constructor() : FacePropertyRepository { private val faceSensorInfo = MutableStateFlow<FaceSensorInfo?>(null) override val sensorInfo: StateFlow<FaceSensorInfo?> get() = faceSensorInfo @@ -56,3 +61,8 @@ class FakeFacePropertyRepository : FacePropertyRepository { currentCameraInfo.value = value } } + +@Module +interface FakeFacePropertyRepositoryModule { + @Binds fun bindFake(fake: FakeFacePropertyRepository): FacePropertyRepository +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt index bd30fb4cac2b..60d61ac4dcef 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt @@ -68,6 +68,16 @@ class FakeFingerprintPropertyRepository @Inject constructor() : FingerprintPrope ) } + /** setProperties as if the device supports POWER_BUTTON fingerprint sensor. */ + fun supportsSideFps() { + setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.POWER_BUTTON, + sensorLocations = emptyMap(), + ) + } + /** setProperties as if the device supports the rear fingerprint sensor. */ fun supportsRearFps() { setProperties( 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/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt index 1c8190eee773..fbb8ea22d615 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.deviceentry.data import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepositoryModule +import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepositoryModule import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule import com.android.systemui.display.data.repository.FakeDisplayRepositoryModule @@ -35,6 +36,7 @@ import dagger.Module FakeDisplayRepositoryModule::class, FakeDisplayStateRepositoryModule::class, FakeFingerprintPropertyRepositoryModule::class, + FakeFacePropertyRepositoryModule::class, FakeTrustRepositoryModule::class, ] ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt index 66c6f86c452d..ebed922c423e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.deviceentry.domain.interactor +import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.kosmos.Kosmos import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -25,6 +26,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.deviceEntryFingerprintAuthInteractor by Kosmos.Fixture { DeviceEntryFingerprintAuthInteractor( + biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor, repository = deviceEntryFingerprintAuthRepository, + fingerprintPropertyRepository = fingerprintPropertyRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt index 0d1a31f9605e..e73e2950bbb9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt @@ -18,8 +18,8 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.deviceentry.data.repository.deviceEntryRepository -import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository -import com.android.systemui.keyguard.data.repository.trustRepository +import com.android.systemui.flags.fakeSystemPropertiesHelper +import com.android.systemui.keyguard.domain.interactor.trustInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -34,9 +34,12 @@ val Kosmos.deviceEntryInteractor by repository = deviceEntryRepository, authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, - deviceEntryFaceAuthRepository = deviceEntryFaceAuthRepository, - trustRepository = trustRepository, + faceAuthInteractor = deviceEntryFaceAuthInteractor, + trustInteractor = trustInteractor, flags = sceneContainerFlags, deviceUnlockedInteractor = deviceUnlockedInteractor, + fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, + biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor, + systemPropertiesHelper = fakeSystemPropertiesHelper, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt new file mode 100644 index 000000000000..2f30d3431a1e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeSystemPropertiesHelper.kt @@ -0,0 +1,60 @@ +/* + * 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.flags + +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject + +@SysUISingleton +class FakeSystemPropertiesHelper @Inject constructor() : SystemPropertiesHelper() { + private val fakeProperties = mutableMapOf<String, Any>() + + override fun get(name: String): String { + return fakeProperties[name] as String + } + + override fun get(name: String, def: String?): String { + return checkNotNull(fakeProperties[name] as String? ?: def) + } + + override fun getBoolean(name: String, default: Boolean): Boolean { + return fakeProperties[name] as Boolean? ?: default + } + + override fun setBoolean(name: String, value: Boolean) { + fakeProperties[name] = value + } + + override fun set(name: String, value: String) { + fakeProperties[name] = value + } + + override fun set(name: String, value: Int) { + fakeProperties[name] = value + } + + override fun erase(name: String) { + fakeProperties.remove(name) + } +} + +@Module +interface FakeSystemPropertiesHelperModule { + @Binds fun bindFake(fake: FakeSystemPropertiesHelper): SystemPropertiesHelper +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt index 365d97f3ac15..d6f2f77ca67a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt @@ -62,6 +62,7 @@ val Kosmos.featureFlagsClassicRelease by } val Kosmos.systemPropertiesHelper by Kosmos.Fixture { SystemPropertiesHelper() } +val Kosmos.fakeSystemPropertiesHelper by Kosmos.Fixture { FakeSystemPropertiesHelper() } var Kosmos.serverFlagReader: ServerFlagReader by Kosmos.Fixture { serverFlagReaderFake } val Kosmos.serverFlagReaderFake by Kosmos.Fixture { ServerFlagReaderFake() } var Kosmos.restarter: Restarter by Kosmos.Fixture { mock() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt new file mode 100644 index 000000000000..0ebf1642478e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/TrustInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.trustRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.trustInteractor by Fixture { TrustInteractor(repository = trustRepository) } 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 10e6ed4542c7..004f37c16757 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -59,6 +59,8 @@ import android.hardware.camera2.extension.Request; import android.hardware.camera2.extension.SizeList; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.PhysicalCaptureResultInfo; +import android.hardware.camera2.params.ColorSpaceProfiles; +import android.hardware.camera2.params.DynamicRangeProfiles; import android.hardware.camera2.utils.SurfaceUtils; import android.media.Image; import android.media.ImageReader; @@ -526,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() { @@ -711,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(); @@ -1228,7 +1230,6 @@ public class CameraExtensionsProxyService extends Service { return null; } - } private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback { @@ -1585,11 +1586,13 @@ public class CameraExtensionsProxyService extends Service { Camera2SessionConfigImpl sessionConfig; if (LATENCY_IMPROVEMENTS_SUPPORTED) { + int outputsColorSpace = getColorSpaceFromOutputSurfaces(previewSurface, + imageCaptureSurface, postviewSurface); OutputSurfaceConfigurationImplStub outputSurfaceConfigs = new OutputSurfaceConfigurationImplStub(mOutputPreviewSurfaceImpl, // Image Analysis Output is currently only supported in CameraX mOutputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/, - mOutputPostviewSurfaceImpl); + mOutputPostviewSurfaceImpl, outputsColorSpace); sessionConfig = mSessionProcessor.initSession(cameraId, getCharacteristicsMap(charsMapNative), @@ -1616,6 +1619,11 @@ public class CameraExtensionsProxyService extends Service { } ret.outputConfigs.add(entry); } + if (Flags.extension10Bit() && EFV_SUPPORTED) { + ret.colorSpace = sessionConfig.getColorSpace(); + } else { + ret.colorSpace = ColorSpaceProfiles.UNSPECIFIED; + } ret.sessionTemplateId = sessionConfig.getSessionTemplateId(); ret.sessionType = -1; if (LATENCY_IMPROVEMENTS_SUPPORTED) { @@ -1720,6 +1728,24 @@ public class CameraExtensionsProxyService extends Service { public void binderDied() { mSessionProcessor.deInitSession(); } + + // Get the color space of the output configurations. All of the OutputSurfaces + // can be assumed to have the same color space so return the color space + // of any non-null OutputSurface + private int getColorSpaceFromOutputSurfaces(OutputSurface previewSurface, + OutputSurface imageCaptureSurface, OutputSurface postviewSurface) { + int colorSpace = ColorSpaceProfiles.UNSPECIFIED; + + if (previewSurface.surface != null) { + colorSpace = previewSurface.colorSpace; + } else if (imageCaptureSurface.surface != null) { + colorSpace = imageCaptureSurface.colorSpace; + } else if (postviewSurface.surface != null) { + colorSpace = postviewSurface.colorSpace; + } + + return colorSpace; + } } private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl { @@ -1727,6 +1753,17 @@ public class CameraExtensionsProxyService extends Service { private OutputSurfaceImpl mOutputImageCaptureSurfaceImpl; private OutputSurfaceImpl mOutputImageAnalysisSurfaceImpl; private OutputSurfaceImpl mOutputPostviewSurfaceImpl; + private int mColorSpace; + + public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput, + OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput, + OutputSurfaceImpl postviewOutput, int colorSpace) { + mOutputPreviewSurfaceImpl = previewOutput; + mOutputImageCaptureSurfaceImpl = imageCaptureOutput; + mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput; + mOutputPostviewSurfaceImpl = postviewOutput; + mColorSpace = colorSpace; + } public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput, OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput, @@ -1735,6 +1772,7 @@ public class CameraExtensionsProxyService extends Service { mOutputImageCaptureSurfaceImpl = imageCaptureOutput; mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput; mOutputPostviewSurfaceImpl = postviewOutput; + mColorSpace = ColorSpaceProfiles.UNSPECIFIED; } @Override @@ -1756,6 +1794,11 @@ public class CameraExtensionsProxyService extends Service { public OutputSurfaceImpl getPostviewOutputSurface() { return mOutputPostviewSurfaceImpl; } + + @Override + public int getColorSpace() { + return mColorSpace; + } } private class OutputSurfaceImplStub implements OutputSurfaceImpl { @@ -1764,11 +1807,10 @@ public class CameraExtensionsProxyService extends Service { private final int mImageFormat; private final int mDataspace; private final long mUsage; + private final long mDynamicRangeProfile; public OutputSurfaceImplStub(OutputSurface outputSurface) { mSurface = outputSurface.surface; - mSize = new Size(outputSurface.size.width, outputSurface.size.height); - mImageFormat = outputSurface.imageFormat; if (mSurface != null) { mDataspace = SurfaceUtils.getSurfaceDataspace(mSurface); mUsage = SurfaceUtils.getSurfaceUsage(mSurface); @@ -1776,6 +1818,9 @@ public class CameraExtensionsProxyService extends Service { mDataspace = -1; mUsage = 0; } + mDynamicRangeProfile = outputSurface.dynamicRangeProfile; + mSize = new Size(outputSurface.size.width, outputSurface.size.height); + mImageFormat = outputSurface.imageFormat; } @Override @@ -1802,6 +1847,12 @@ public class CameraExtensionsProxyService extends Service { public long getUsage() { return mUsage; } + + @Override + public long getDynamicRangeProfile() { + return mDynamicRangeProfile; + } + } private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements @@ -2531,6 +2582,11 @@ public class CameraExtensionsProxyService extends Service { private static CameraOutputConfig getCameraOutputConfig(Camera2OutputConfigImpl output) { CameraOutputConfig ret = new CameraOutputConfig(); + if (Flags.extension10Bit() && EFV_SUPPORTED) { + ret.dynamicRangeProfile = output.getDynamicRangeProfile(); + } else { + ret.dynamicRangeProfile = DynamicRangeProfiles.STANDARD; + } ret.outputId = new OutputConfigId(); ret.outputId.id = output.getId(); ret.physicalCameraId = output.getPhysicalCameraId(); 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/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index ca2a3ddc49bc..a55b8d008a68 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -171,6 +171,7 @@ import android.util.SparseArray; import android.util.TimeUtils; import android.view.KeyEvent; import android.view.autofill.AutofillId; +import android.view.autofill.AutofillFeatureFlags; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager.AutofillCommitReason; import android.view.autofill.AutofillManager.SmartSuggestionMode; @@ -1498,7 +1499,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid); mSaveEventLogger = SaveEventLogger.forSessionId(sessionId); mIsPrimaryCredential = isPrimaryCredential; - mIgnoreViewStateResetToEmpty = Flags.ignoreViewStateResetToEmpty(); + mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty(); synchronized (mLock) { mSessionFlags = new SessionFlags(); diff --git a/services/backup/BACKUP_OWNERS b/services/backup/BACKUP_OWNERS index f8f4f4f4bf2e..29ae2027fc3a 100644 --- a/services/backup/BACKUP_OWNERS +++ b/services/backup/BACKUP_OWNERS @@ -2,9 +2,10 @@ jstemmer@google.com martinoh@google.com -millmore@google.com niamhfw@google.com piee@google.com philippov@google.com rthakohov@google.com -sarpm@google.com
\ No newline at end of file +sarpm@google.com +beatricemarch@google.com +azilio@google.com
\ No newline at end of file diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING index 82ab0980a22b..340bc327fc7f 100644 --- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING +++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING @@ -38,7 +38,8 @@ { "exclude-annotation": "androidx.test.filters.FlakyTest" } - ] + ], + "keywords": ["primary-device"] }, { "name": "CtsHardwareTestCases", diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java index 7d8aad7ab490..ecd14ce67d7e 100644 --- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java +++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java @@ -24,6 +24,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; import android.media.projection.MediaProjectionManager; @@ -79,7 +80,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic Trace.beginSection( "SensitiveContentProtectionManagerService.onProjectionStart"); try { - onProjectionStart(info); + onProjectionStart(info.getPackageName()); } finally { Trace.endSection(); } @@ -124,14 +125,6 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } } - // These packages are exempted from screen share protection. - private ArraySet<String> getExemptedPackages() { - final ArraySet<String> exemptedPackages = - SystemConfig.getInstance().getBugreportWhitelistedPackages(); - // TODO(b/323361046) - Add sys ui recorder package. - return exemptedPackages; - } - @VisibleForTesting void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager, ArraySet<String> exemptedPackages) { @@ -179,9 +172,22 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } } - private void onProjectionStart(MediaProjectionInfo info) { - if (mExemptedPackages != null && mExemptedPackages.contains(info.getPackageName())) { - Log.w(TAG, info.getPackageName() + " is exempted from screen share protection."); + private boolean canRecordSensitiveContent(@NonNull String packageName) { + return getContext().getPackageManager() + .checkPermission(android.Manifest.permission.RECORD_SENSITIVE_CONTENT, + packageName) == PackageManager.PERMISSION_GRANTED; + } + + // These packages are exempted from screen share protection. + private ArraySet<String> getExemptedPackages() { + return SystemConfig.getInstance().getBugreportWhitelistedPackages(); + } + + private void onProjectionStart(String packageName) { + // exempt on device screen recorder as well. + if ((mExemptedPackages != null && mExemptedPackages.contains(packageName)) + || canRecordSensitiveContent(packageName)) { + Log.w(TAG, packageName + " is exempted from screen share protection."); return; } // TODO(b/324447419): move GlobalSettings lookup to background thread 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/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index e0c24256f5b1..14428c41ef26 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -157,7 +157,7 @@ public class AudioDeviceInventory { * corresponding peers in case of BLE */ void addAudioDeviceInInventoryIfNeeded(int deviceType, String address, String peerAddress, - @AudioDeviceCategory int category) { + @AudioDeviceCategory int category, boolean userDefined) { if (!isBluetoothOutDevice(deviceType)) { return; } @@ -167,7 +167,11 @@ public class AudioDeviceInventory { ads = findBtDeviceStateForAddress(peerAddress, deviceType); } if (ads != null) { - if (ads.getAudioDeviceCategory() != category) { + // if category is user defined allow to change back to unknown otherwise + // do not reset the category back to unknown since it might have been set + // before by the user + if (ads.getAudioDeviceCategory() != category && (userDefined + || category != AUDIO_DEVICE_CATEGORY_UNKNOWN)) { ads.setAudioDeviceCategory(category); mDeviceBroker.postUpdatedAdiDeviceState(ads); mDeviceBroker.postPersistAudioDeviceSettings(); @@ -220,9 +224,9 @@ public class AudioDeviceInventory { void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address, @AudioDeviceCategory int btAudioDeviceCategory) { addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLE_HEADSET, - address, "", btAudioDeviceCategory); + address, "", btAudioDeviceCategory, /*userDefined=*/true); addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_BLUETOOTH_A2DP, - address, "", btAudioDeviceCategory); + address, "", btAudioDeviceCategory, /*userDefined=*/true); } @AudioDeviceCategory @@ -1733,7 +1737,7 @@ public class AudioDeviceInventory { purgeDevicesRoles_l(); } else { addAudioDeviceInInventoryIfNeeded(device, address, "", - BtHelper.getBtDeviceCategory(address)); + BtHelper.getBtDeviceCategory(address), /*userDefined=*/false); } AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "SCO " + (AudioSystem.isInputDevice(device) ? "source" : "sink") @@ -2023,7 +2027,7 @@ public class AudioDeviceInventory { updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/); addAudioDeviceInInventoryIfNeeded(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, "", - BtHelper.getBtDeviceCategory(address)); + BtHelper.getBtDeviceCategory(address), /*userDefined=*/false); } static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER, @@ -2357,7 +2361,7 @@ public class AudioDeviceInventory { DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); setCurrentAudioRouteNameIfPossible(name, false /*fromA2dp*/); addAudioDeviceInInventoryIfNeeded(DEVICE_OUT_HEARING_AID, address, "", - BtHelper.getBtDeviceCategory(address)); + BtHelper.getBtDeviceCategory(address), /*userDefined=*/false); new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable") .set(MediaMetrics.Property.ADDRESS, address != null ? address : "") .set(MediaMetrics.Property.DEVICE, @@ -2488,7 +2492,7 @@ public class AudioDeviceInventory { mDeviceBroker.postAccessoryPlugMediaUnmute(device); setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false); addAudioDeviceInInventoryIfNeeded(device, address, peerAddress, - BtHelper.getBtDeviceCategory(address)); + BtHelper.getBtDeviceCategory(address), /*userDefined=*/false); } if (streamType == AudioSystem.STREAM_DEFAULT) { 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/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 3b05b47eb542..a7748f4fae98 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -44,6 +44,15 @@ import java.io.PrintWriter; * </p> */ abstract class DisplayDevice { + /** + * Maximum acceptable anisotropy for the output image. + * + * Necessary to avoid unnecessary scaling when pixels are almost square, as they are non ideal + * anyway. For external displays, we expect an anisotropy of about 2% even if the pixels + * are, in fact, square due to the imprecision of the display's actual size (parsed from edid + * and rounded to the nearest cm). + */ + static final float MAX_ANISOTROPY = 1.025f; private static final String TAG = "DisplayDevice"; private static final Display.Mode EMPTY_DISPLAY_MODE = new Display.Mode.Builder().build(); @@ -69,13 +78,21 @@ abstract class DisplayDevice { // Do not use for any other purpose. DisplayDeviceInfo mDebugLastLoggedDeviceInfo; - public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId, + private final boolean mIsAnisotropyCorrectionEnabled; + + DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId, Context context) { + this(displayAdapter, displayToken, uniqueId, context, false); + } + + DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId, + Context context, boolean isAnisotropyCorrectionEnabled) { mDisplayAdapter = displayAdapter; mDisplayToken = displayToken; mUniqueId = uniqueId; mDisplayDeviceConfig = null; mContext = context; + mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled; } /** @@ -143,8 +160,17 @@ abstract class DisplayDevice { DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked(); final boolean isRotated = mCurrentOrientation == ROTATION_90 || mCurrentOrientation == ROTATION_270; - return isRotated ? new Point(displayDeviceInfo.height, displayDeviceInfo.width) - : new Point(displayDeviceInfo.width, displayDeviceInfo.height); + var width = displayDeviceInfo.width; + var height = displayDeviceInfo.height; + if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0 + && displayDeviceInfo.xDpi > 0) { + if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * MAX_ANISOTROPY) { + height = (int) (height * displayDeviceInfo.xDpi / displayDeviceInfo.yDpi + 0.5); + } else if (displayDeviceInfo.xDpi * MAX_ANISOTROPY < displayDeviceInfo.yDpi) { + width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi + 0.5); + } + } + return isRotated ? new Point(height, width) : new Point(width, height); } /** diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 88c24e0a7eff..b2fd9edf61fe 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -257,7 +257,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { SurfaceControl.DynamicDisplayInfo dynamicInfo, SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isFirstDisplay) { super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId, - getContext()); + getContext(), + getFeatureFlags().isPixelAnisotropyCorrectionInLogicalDisplayEnabled()); mPhysicalDisplayId = physicalDisplayId; mIsFirstDisplay = isFirstDisplay; updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs); diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index db636d619bd3..5eaaf3504e85 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -35,7 +35,6 @@ import android.view.SurfaceControl; import com.android.server.display.layout.Layout; import com.android.server.display.mode.DisplayModeDirector; -import com.android.server.wm.utils.DisplayInfoOverrides; import com.android.server.wm.utils.InsetUtils; import java.io.PrintWriter; @@ -204,7 +203,28 @@ final class LogicalDisplay { private SparseArray<SurfaceControl.RefreshRateRange> mThermalRefreshRateThrottling = new SparseArray<>(); + /** + * If the aspect ratio of the resolution of the display does not match the physical aspect + * ratio of the display, then without this feature enabled, picture would appear stretched to + * the user. This is because applications assume that they are rendered on square pixels + * (meaning density of pixels in x and y directions are equal). This would result into circles + * appearing as ellipses to the user. + * To compensate for non-square (anisotropic) pixels, if this feature is enabled: + * 1. LogicalDisplay will add more pixels for the applications to render on, as if the pixels + * were square and occupied the full display. + * 2. SurfaceFlinger will squeeze this taller/wider surface into the available number of + * physical pixels in the current display resolution. + * 3. If a setting on the display itself is set to "fill the entire display panel" then the + * display will stretch the pixels to fill the display fully. + */ + private final boolean mIsAnisotropyCorrectionEnabled; + LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) { + this(displayId, layerStack, primaryDisplayDevice, false); + } + + LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice, + boolean isAnisotropyCorrectionEnabled) { mDisplayId = displayId; mLayerStack = layerStack; mPrimaryDisplayDevice = primaryDisplayDevice; @@ -215,6 +235,7 @@ final class LogicalDisplay { mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId; + mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled; } public void setDevicePositionLocked(int position) { @@ -453,6 +474,14 @@ final class LogicalDisplay { int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right; int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom; + if (mIsAnisotropyCorrectionEnabled && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) { + if (deviceInfo.xDpi > deviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) { + maskedHeight = (int) (maskedHeight * deviceInfo.xDpi / deviceInfo.yDpi + 0.5); + } else if (deviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY < deviceInfo.yDpi) { + maskedWidth = (int) (maskedWidth * deviceInfo.yDpi / deviceInfo.xDpi + 0.5); + } + } + mBaseDisplayInfo.type = deviceInfo.type; mBaseDisplayInfo.address = deviceInfo.address; mBaseDisplayInfo.deviceProductInfo = deviceInfo.deviceProductInfo; @@ -666,6 +695,31 @@ final class LogicalDisplay { physWidth -= maskingInsets.left + maskingInsets.right; physHeight -= maskingInsets.top + maskingInsets.bottom; + var displayLogicalWidth = displayInfo.logicalWidth; + var displayLogicalHeight = displayInfo.logicalHeight; + + if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.xDpi > 0 + && displayDeviceInfo.yDpi > 0) { + if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) { + var scalingFactor = displayDeviceInfo.yDpi / displayDeviceInfo.xDpi; + if (rotated) { + displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5); + } else { + displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor + + 0.5); + } + } else if (displayDeviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY + < displayDeviceInfo.yDpi) { + var scalingFactor = displayDeviceInfo.xDpi / displayDeviceInfo.yDpi; + if (rotated) { + displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor + + 0.5); + } else { + displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5); + } + } + } + // Determine whether the width or height is more constrained to be scaled. // physWidth / displayInfo.logicalWidth => letter box // or physHeight / displayInfo.logicalHeight => pillar box @@ -675,16 +729,16 @@ final class LogicalDisplay { // comparing them. int displayRectWidth, displayRectHeight; if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0 || mDisplayScalingDisabled) { - displayRectWidth = displayInfo.logicalWidth; - displayRectHeight = displayInfo.logicalHeight; - } else if (physWidth * displayInfo.logicalHeight - < physHeight * displayInfo.logicalWidth) { + displayRectWidth = displayLogicalWidth; + displayRectHeight = displayLogicalHeight; + } else if (physWidth * displayLogicalHeight + < physHeight * displayLogicalWidth) { // Letter box. displayRectWidth = physWidth; - displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth; + displayRectHeight = displayLogicalHeight * physWidth / displayLogicalWidth; } else { // Pillar box. - displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight; + displayRectWidth = displayLogicalWidth * physHeight / displayLogicalHeight; displayRectHeight = physHeight; } int displayRectTop = (physHeight - displayRectHeight) / 2; diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 3452e0f188c3..e092fdae7cc7 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -1151,7 +1151,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { */ private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) { final int layerStack = assignLayerStackLocked(displayId); - final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); + final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device, + mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled()); display.updateLocked(mDisplayDeviceRepo); final DisplayInfo info = display.getDisplayInfoLocked(); diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 3c98ee453913..15ee9372b46a 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -121,6 +121,11 @@ public class DisplayManagerFlags { Flags::refreshRateVotingTelemetry ); + private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState( + Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION, + Flags::enablePixelAnisotropyCorrection + ); + private final FlagState mSensorBasedBrightnessThrottling = new FlagState( Flags.FLAG_SENSOR_BASED_BRIGHTNESS_THROTTLING, Flags::sensorBasedBrightnessThrottling @@ -259,6 +264,10 @@ public class DisplayManagerFlags { return mRefreshRateVotingTelemetry.isEnabled(); } + public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() { + return mPixelAnisotropyCorrectionEnabled.isEnabled(); + } + public boolean isSensorBasedBrightnessThrottlingEnabled() { return mSensorBasedBrightnessThrottling.isEnabled(); } @@ -290,6 +299,7 @@ public class DisplayManagerFlags { pw.println(" " + mAutoBrightnessModesFlagState); pw.println(" " + mFastHdrTransitions); pw.println(" " + mRefreshRateVotingTelemetry); + pw.println(" " + mPixelAnisotropyCorrectionEnabled); pw.println(" " + mSensorBasedBrightnessThrottling); pw.println(" " + mRefactorDisplayPowerController); } diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 34045273c93a..9bf36e4e605f 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -186,6 +186,14 @@ flag { } flag { + name: "enable_pixel_anisotropy_correction" + namespace: "display_manager" + description: "Feature flag for enabling display anisotropy correction through LogicalDisplay upscaling" + bug: "317363416" + is_fixed_read_only: true +} + +flag { name: "sensor_based_brightness_throttling" namespace: "display_manager" description: "Feature flag for enabling brightness throttling using sensor from config." diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java index 0ef23e903b6a..e6bf2c968350 100644 --- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java +++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java @@ -61,7 +61,6 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; /** @@ -133,7 +132,11 @@ public class GrammaticalInflectionService extends SystemService { @Override public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) { - return canGetSystemGrammaticalGender(attributionSource) + if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) { + throw new SecurityException("AttributionSource: " + attributionSource + + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission."); + } + return checkSystemTermsOfAddressIsEnabled() ? GrammaticalInflectionService.this.getSystemGrammaticalGender( attributionSource, userId) : GRAMMATICAL_GENDER_NOT_SPECIFIED; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index fef56610b406..4b06c06827f6 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -867,7 +867,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!mSystemReady) { return; } - buildInputMethodListLocked(true); + mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(), + AdditionalSubtypeMapRepository.get(mSettings.getUserId()), + DirectBootAwareness.AUTO); + postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */); // If the locale is changed, needs to reset the default ime resetDefaultImeLocked(mContext); updateFromSettingsLocked(true); @@ -1114,12 +1117,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub settings.getMethodMap()); } - if (!isCurrentUser - || !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) { + if (!isCurrentUser) { return; } - buildInputMethodListLocked(false /* resetDefaultEnabledIme */); + if (!(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) { + return; + } + mSettings = queryInputMethodServicesInternal(mContext, userId, + newAdditionalSubtypeMap, DirectBootAwareness.AUTO); + postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); boolean changed = false; @@ -1278,12 +1285,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (userId != currentUserId) { return; } - mSettings = InputMethodSettings.createEmptyMap(userId); - if (mSystemReady) { - // We need to rebuild IMEs. - buildInputMethodListLocked(false /* resetDefaultEnabledIme */); - updateInputMethodsFromSettingsLocked(true /* enabledChanged */); + if (!mSystemReady) { + return; } + mSettings = queryInputMethodServicesInternal(mContext, userId, + AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO); + // We need to rebuild IMEs. + postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); + updateInputMethodsFromSettingsLocked(true /* enabledChanged */); } } @@ -1515,7 +1524,10 @@ 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); + + mSettings = queryInputMethodServicesInternal(mContext, newUserId, + AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO); + 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 +1608,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final String defaultImiId = mSettings.getSelectedInputMethod(); final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); - buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */); + mSettings = queryInputMethodServicesInternal(mContext, currentUserId, + AdditionalSubtypeMapRepository.get(mSettings.getUserId()), + DirectBootAwareness.AUTO); + postInputMethodSettingUpdatedLocked( + !imeSelectedOnBoot /* resetDefaultEnabledIme */); updateFromSettingsLocked(true); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, currentUserId), @@ -4041,7 +4057,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (isCurrentUser) { final long ident = Binder.clearCallingIdentity(); try { - buildInputMethodListLocked(false /* resetDefaultEnabledIme */); + mSettings = queryInputMethodServicesInternal(mContext, + mSettings.getUserId(), + AdditionalSubtypeMapRepository.get(mSettings.getUserId()), + DirectBootAwareness.AUTO); + postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); } finally { Binder.restoreCallingIdentity(ident); } @@ -4969,7 +4989,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 +5001,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. 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/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index c2f74a8895cb..c9fd2610bfb7 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -155,7 +155,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_CONFIG_DEFAULT_APPS, UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, UserManager.DISALLOW_SIM_GLOBALLY, - UserManager.DISALLOW_ASSIST_CONTENT + UserManager.DISALLOW_ASSIST_CONTENT, + UserManager.DISALLOW_THREAD_NETWORK }); public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet( @@ -206,7 +207,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_ADD_WIFI_CONFIG, UserManager.DISALLOW_CELLULAR_2G, UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, - UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO + UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, + UserManager.DISALLOW_THREAD_NETWORK ); /** @@ -252,7 +254,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_ADD_WIFI_CONFIG, UserManager.DISALLOW_CELLULAR_2G, UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, - UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO + UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, + UserManager.DISALLOW_THREAD_NETWORK ); /** 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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index c3efcb14f223..885baf65013f 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2691,6 +2691,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } float maxDimAmount = getHighestDimAmountFromMap(wallpaper.mUidToDimAmount); + if (wallpaper.mWallpaperDimAmount == maxDimAmount) return; wallpaper.mWallpaperDimAmount = maxDimAmount; // Also set the dim amount to the lock screen wallpaper if the lock and home screen // do not share the same wallpaper 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/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index a914c0712319..b616d24cfebb 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -107,7 +107,9 @@ final class ContentRecorder implements WindowContainerListener { ContentRecorder(@NonNull DisplayContent displayContent) { this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId), - new DisplayManagerFlags().isConnectedDisplayManagementEnabled()); + new DisplayManagerFlags().isConnectedDisplayManagementEnabled() + && !new DisplayManagerFlags() + .isPixelAnisotropyCorrectionInLogicalDisplayEnabled()); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java index 877378cac06a..a29cb60ff545 100644 --- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -173,18 +173,25 @@ public class DeferredDisplayUpdater implements DisplayUpdater { mDisplayContent.mInitialDisplayHeight); final int fromRotation = mDisplayContent.getRotation(); - onStartCollect.run(); - - ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, - "DeferredDisplayUpdater: applied DisplayInfo after deferring"); - - if (physicalDisplayUpdated) { - onDisplayUpdated(transition, fromRotation, startBounds); - } else { - final TransitionRequestInfo.DisplayChange displayChange = - getCurrentDisplayChange(fromRotation, startBounds); - mDisplayContent.mTransitionController.requestStartTransition(transition, - /* startTask= */ null, /* remoteTransition= */ null, displayChange); + mDisplayContent.mAtmService.deferWindowLayout(); + try { + onStartCollect.run(); + + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, + "DeferredDisplayUpdater: applied DisplayInfo after deferring"); + + if (physicalDisplayUpdated) { + onDisplayUpdated(transition, fromRotation, startBounds); + } else { + final TransitionRequestInfo.DisplayChange displayChange = + getCurrentDisplayChange(fromRotation, startBounds); + mDisplayContent.mTransitionController.requestStartTransition(transition, + /* startTask= */ null, /* remoteTransition= */ null, displayChange); + } + } finally { + // Run surface placement after requestStartTransition, so shell side can receive + // the transition request before handling task info changes. + mDisplayContent.mAtmService.continueWindowLayout(); } }); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 282ecc77bd50..837d08b33756 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6210,7 +6210,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * @param onDisplayChangeApplied callback that is called when the changes are applied */ void requestDisplayUpdate(@NonNull Runnable onDisplayChangeApplied) { - mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied); + mAtmService.deferWindowLayout(); + try { + mDisplayUpdater.updateDisplayInfo(onDisplayChangeApplied); + } finally { + mAtmService.continueWindowLayout(); + } } void onDisplayInfoUpdated(@NonNull DisplayInfo newDisplayInfo) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c37946b4d750..ee72db036bbf 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3395,7 +3395,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (shouldMigrateV1ToDevicePolicyEngine()) { migrateV1PoliciesToDevicePolicyEngine(); } + maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked(); migratePoliciesToPolicyEngineLocked(); + } maybeStartSecurityLogMonitorOnActivityManagerReady(); break; @@ -16877,6 +16879,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private int checkDeviceOwnerProvisioningPreCondition(@UserIdInt int callingUserId) { synchronized (getLockObject()) { final int deviceOwnerUserId = mInjector.userManagerIsHeadlessSystemUserMode() + && (!Flags.headlessDeviceOwnerProvisioningFixEnabled() + || getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED) ? UserHandle.USER_SYSTEM : callingUserId; Slogf.i(LOG_TAG, "Calling user %d, device owner will be set on user %d", @@ -21549,10 +21553,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime()); setLocale(provisioningParams.getLocale()); + + + boolean isSingleUserMode; + if (Flags.headlessDeviceOwnerProvisioningFixEnabled()) { + DeviceAdminInfo adminInfo = findAdmin( + deviceAdmin, caller.getUserId(), /* throwForMissingPermission= */ false); + isSingleUserMode = (adminInfo != null && adminInfo.getHeadlessDeviceOwnerMode() + == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER); + } else { + isSingleUserMode = + (getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER); + } int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled() - && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER - ? mUserManagerInternal.getMainUserId() - : UserHandle.USER_SYSTEM; + && isSingleUserMode + ? mUserManagerInternal.getMainUserId() : UserHandle.USER_SYSTEM; if (!removeNonRequiredAppsForManagedDevice( deviceOwnerUserId, @@ -23736,7 +23751,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) { return false; } - return migrateV1PoliciesToDevicePolicyEngine(); + boolean migrated = migrateV1PoliciesToDevicePolicyEngine(); + migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked(); + return migrated; }); } @@ -23765,6 +23782,30 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Migrates the initial set of policies to use policy engine. + * [b/318497672] Migrate policies that weren't migrated properly in the initial migration on + * update from Android T to Android U + */ + private void maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked() { + if (!mOwners.isMigratedToPolicyEngine() || mOwners.isMigratedPostUpdate()) { + return; + } + migratePoliciesPostUpgradeToDevicePolicyEngineLocked(); + mOwners.markPostUpgradeMigration(); + } + + private boolean migratePoliciesPostUpgradeToDevicePolicyEngineLocked() { + try { + migrateScreenCapturePolicyLocked(); + migrateLockTaskPolicyLocked(); + return true; + } catch (Exception e) { + Slogf.e(LOG_TAG, e, "Error occurred during post upgrade migration to the device " + + "policy engine."); + return false; + } + } + + /** * @return {@code true} if policies were migrated successfully, {@code false} otherwise. */ private boolean migrateV1PoliciesToDevicePolicyEngine() { @@ -23777,7 +23818,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { migrateAutoTimezonePolicy(); migratePermissionGrantStatePolicies(); } - migrateScreenCapturePolicyLocked(); migratePermittedInputMethodsPolicyLocked(); migrateAccountManagementDisabledPolicyLocked(); migrateUserControlDisabledPackagesLocked(); @@ -23858,14 +23898,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void migrateScreenCapturePolicyLocked() { Binder.withCleanCallingIdentity(() -> { - if (mPolicyCache.getScreenCaptureDisallowedUser() == UserHandle.USER_NULL) { - return; - } ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); if (admin != null && ((isDeviceOwner(admin) && admin.disableScreenCapture) || (admin.getParentActiveAdmin() != null && admin.getParentActiveAdmin().disableScreenCapture))) { + EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin( admin.info.getComponent(), admin.getUserHandle().getIdentifier(), @@ -23894,6 +23932,48 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } + private void migrateLockTaskPolicyLocked() { + Binder.withCleanCallingIdentity(() -> { + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + if (deviceOwner != null) { + int doUserId = deviceOwner.getUserHandle().getIdentifier(); + DevicePolicyData policies = getUserData(doUserId); + List<String> packages = policies.mLockTaskPackages; + int features = policies.mLockTaskFeatures; + // TODO: find out about persistent preferred activities + if (!packages.isEmpty()) { + setLockTaskPolicyInPolicyEngine(deviceOwner, doUserId, packages, features); + } + } + + for (int userId : mUserManagerInternal.getUserIds()) { + ActiveAdmin profileOwner = getProfileOwnerLocked(userId); + if (profileOwner != null && canDPCManagedUserUseLockTaskLocked(userId)) { + DevicePolicyData policies = getUserData(userId); + List<String> packages = policies.mLockTaskPackages; + int features = policies.mLockTaskFeatures; + if (!packages.isEmpty()) { + setLockTaskPolicyInPolicyEngine(profileOwner, userId, packages, features); + } + } + } + }); + } + + private void setLockTaskPolicyInPolicyEngine( + ActiveAdmin admin, int userId, List<String> packages, int features) { + EnforcingAdmin enforcingAdmin = + EnforcingAdmin.createEnterpriseEnforcingAdmin( + admin.info.getComponent(), + userId, + admin); + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.LOCK_TASK, + enforcingAdmin, + new LockTaskPolicy(new HashSet<>(packages), features), + userId); + } + private void migratePermittedInputMethodsPolicyLocked() { Binder.withCleanCallingIdentity(() -> { List<UserInfo> users = mUserManager.getUsers(); @@ -24256,4 +24336,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mDevicePolicyEngine.getMaxPolicyStorageLimit(); } + + @Override + public int getHeadlessDeviceOwnerMode(String callerPackageName) { + final CallerIdentity caller = getCallerIdentity(callerPackageName); + enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(), + caller.getUserId()); + + return Binder.withCleanCallingIdentity(() -> getHeadlessDeviceOwnerMode()); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index c5a98880ec84..7912cbce554f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -623,12 +623,25 @@ class Owners { } } + void markPostUpgradeMigration() { + synchronized (mData) { + mData.mPoliciesMigratedPostUpdate = true; + mData.writeDeviceOwner(); + } + } + boolean isSecurityLoggingMigrated() { synchronized (mData) { return mData.mSecurityLoggingMigrated; } } + boolean isMigratedPostUpdate() { + synchronized (mData) { + return mData.mPoliciesMigratedPostUpdate; + } + } + @GuardedBy("mData") void pushToAppOpsLocked() { if (!mSystemReady) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java index 9d73ed0070c8..42ac998bf96c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java @@ -89,6 +89,8 @@ class OwnersData { private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine"; private static final String ATTR_SECURITY_LOG_MIGRATED = "securityLogMigrated"; + private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade"; + // Internal state for the device owner package. OwnerInfo mDeviceOwner; int mDeviceOwnerUserId = UserHandle.USER_NULL; @@ -117,6 +119,8 @@ class OwnersData { boolean mMigratedToPolicyEngine = false; boolean mSecurityLoggingMigrated = false; + boolean mPoliciesMigratedPostUpdate = false; + OwnersData(PolicyPathProvider pathProvider) { mPathProvider = pathProvider; } @@ -400,6 +404,7 @@ class OwnersData { out.startTag(null, TAG_POLICY_ENGINE_MIGRATION); out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine); + out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate); if (Flags.securityLogV2Enabled()) { out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated); } @@ -463,8 +468,11 @@ class OwnersData { case TAG_POLICY_ENGINE_MIGRATION: mMigratedToPolicyEngine = parser.getAttributeBoolean( null, ATTR_MIGRATED_TO_POLICY_ENGINE, false); + mPoliciesMigratedPostUpdate = parser.getAttributeBoolean( + null, ATTR_MIGRATED_POST_UPGRADE, false); mSecurityLoggingMigrated = Flags.securityLogV2Enabled() && parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false); + break; default: Slog.e(TAG, "Unexpected tag: " + tag); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 71facab99fce..e713a827fc76 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -506,6 +506,10 @@ final class PolicyDefinition<V> { UserManager.DISALLOW_SIM_GLOBALLY, POLICY_FLAG_GLOBAL_ONLY_POLICY); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ASSIST_CONTENT, /* flags= */ 0); + if (com.android.net.thread.platform.flags.Flags.threadUserRestrictionEnabled()) { + USER_RESTRICTION_FLAGS.put( + UserManager.DISALLOW_THREAD_NETWORK, POLICY_FLAG_GLOBAL_ONLY_POLICY); + } for (String key : USER_RESTRICTION_FLAGS.keySet()) { createAndAddUserRestrictionPolicyDefinition(key, USER_RESTRICTION_FLAGS.get(key)); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 73d830dcde33..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)) { @@ -3335,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/VpnTests/Android.bp b/services/tests/VpnTests/Android.bp index 64a9a3b4f119..a5011a8d8b00 100644 --- a/services/tests/VpnTests/Android.bp +++ b/services/tests/VpnTests/Android.bp @@ -17,8 +17,7 @@ android_test { "java/**/*.java", "java/**/*.kt", ], - - defaults: ["framework-connectivity-test-defaults"], + sdk_version: "core_platform", // tests can use @CorePlatformApi's test_suites: ["device-tests"], static_libs: [ "androidx.test.rules", @@ -32,6 +31,13 @@ android_test { "service-connectivity-tiramisu-pre-jarjar", ], libs: [ + // order matters: classes in framework-connectivity are resolved before framework, + // meaning @hide APIs in framework-connectivity are resolved before @SystemApi + // stubs in framework + "framework-connectivity.impl", + "framework-connectivity-t.impl", + "framework", + "framework-res", "android.test.runner", "android.test.base", "android.test.mock", diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java index dc6abf1981c0..1c71abc984df 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java @@ -52,7 +52,9 @@ public class DisplayDeviceTest { private static final int WIDTH = 500; private static final int HEIGHT = 900; private static final Point PORTRAIT_SIZE = new Point(WIDTH, HEIGHT); + private static final Point PORTRAIT_DOUBLE_WIDTH = new Point(2 * WIDTH, HEIGHT); private static final Point LANDSCAPE_SIZE = new Point(HEIGHT, WIDTH); + private static final Point LANDSCAPE_DOUBLE_HEIGHT = new Point(HEIGHT, 2 * WIDTH); @Mock private SurfaceControl.Transaction mMockTransaction; @@ -69,6 +71,16 @@ public class DisplayDeviceTest { } @Test + public void testGetDisplaySurfaceDefaultSizeLocked_notRotated_anisotropyCorrection() { + mDisplayDeviceInfo.xDpi = 0.5f; + mDisplayDeviceInfo.yDpi = 1.0f; + DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo, + mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true); + assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo( + PORTRAIT_DOUBLE_WIDTH); + } + + @Test public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() { DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo, mMockDisplayAdapter); @@ -84,6 +96,17 @@ public class DisplayDeviceTest { } @Test + public void testGetDisplaySurfaceDefaultSizeLocked_rotation90_anisotropyCorrection() { + mDisplayDeviceInfo.xDpi = 0.5f; + mDisplayDeviceInfo.yDpi = 1.0f; + DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo, + mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true); + displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect()); + assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo( + LANDSCAPE_DOUBLE_HEIGHT); + } + + @Test public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() { DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo, mMockDisplayAdapter); @@ -111,8 +134,14 @@ public class DisplayDeviceTest { private final DisplayDeviceInfo mDisplayDeviceInfo; FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter) { + this(displayDeviceInfo, displayAdapter, /*isAnisotropyCorrectionEnabled=*/ false); + } + + FakeDisplayDevice(DisplayDeviceInfo displayDeviceInfo, DisplayAdapter displayAdapter, + boolean isAnisotropyCorrectionEnabled) { super(displayAdapter, /* displayToken= */ null, /* uniqueId= */ "", - InstrumentationRegistry.getInstrumentation().getContext()); + InstrumentationRegistry.getInstrumentation().getContext(), + isAnisotropyCorrectionEnabled); mDisplayDeviceInfo = displayDeviceInfo; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java index 1c43418f9276..549f0d74b67b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java @@ -106,8 +106,184 @@ public class LogicalDisplayTest { } @Test + public void testLetterbox() { + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, + /*isAnisotropyCorrectionEnabled=*/ false); + mDisplayDeviceInfo.xDpi = 0.5f; + mDisplayDeviceInfo.yDpi = 1.0f; + + mLogicalDisplay.updateLocked(mDeviceRepo); + var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked(); + assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth); + assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight); + + SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition()); + + /* + * Content is too wide, should become letterboxed + * ______DISPLAY_WIDTH________ + * | | + * |________________________| + * | | + * | CONTENT | + * | | + * |________________________| + * | | + * |________________________| + */ + // Make a wide application content, by reducing its height. + DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = DISPLAY_WIDTH; + displayInfo.logicalHeight = DISPLAY_HEIGHT / 2; + mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo); + + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + assertEquals(new Point(0, DISPLAY_HEIGHT / 4), mLogicalDisplay.getDisplayPosition()); + } + + @Test + public void testNoLetterbox_anisotropyCorrection() { + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, + /*isAnisotropyCorrectionEnabled=*/ true); + + // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust + // to using the whole screen. This is because display will rescale it back to fill the + // screen (in case the display menu setting is set to stretch the pixels across the display) + mDisplayDeviceInfo.xDpi = 0.5f; + mDisplayDeviceInfo.yDpi = 1.0f; + + mLogicalDisplay.updateLocked(mDeviceRepo); + var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked(); + // Content width re-scaled + assertEquals(DISPLAY_WIDTH * 2, originalDisplayInfo.logicalWidth); + assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight); + + SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + + // Applications need to think that they are shown on a display with square pixels. + // as applications can be displayed on multiple displays simultaneously (mirrored). + // Content is too wide, should have become letterboxed - but it won't because of anisotropy + // correction + assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition()); + } + + @Test + public void testLetterbox_anisotropyCorrectionYDpi() { + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, + /*isAnisotropyCorrectionEnabled=*/ true); + + DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = DISPLAY_WIDTH; + displayInfo.logicalHeight = DISPLAY_HEIGHT / 2; + mDisplayDeviceInfo.xDpi = 1.0f; + mDisplayDeviceInfo.yDpi = 0.5f; + mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo); + mLogicalDisplay.updateLocked(mDeviceRepo); + + SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + + assertEquals(new Point(0, 75), mLogicalDisplay.getDisplayPosition()); + } + + @Test + public void testPillarbox() { + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, + /*isAnisotropyCorrectionEnabled=*/ false); + mDisplayDeviceInfo.xDpi = 0.5f; + mDisplayDeviceInfo.yDpi = 1.0f; + + DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.rotation = Surface.ROTATION_90; + displayInfo.logicalWidth = DISPLAY_WIDTH; + displayInfo.logicalHeight = DISPLAY_HEIGHT; + mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; + mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo); + mLogicalDisplay.updateLocked(mDeviceRepo); + + var updatedDisplayInfo = mLogicalDisplay.getDisplayInfoLocked(); + assertEquals(Surface.ROTATION_90, updatedDisplayInfo.rotation); + assertEquals(DISPLAY_WIDTH, updatedDisplayInfo.logicalWidth); + assertEquals(DISPLAY_HEIGHT, updatedDisplayInfo.logicalHeight); + + /* + * Content is too tall, should become pillarboxed + * ______DISPLAY_WIDTH________ + * | | | | + * | | | | + * | | | | + * | | CONTENT | | + * | | | | + * | | | | + * |____|________________|____| + */ + + SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + + assertEquals(new Point(75, 0), mLogicalDisplay.getDisplayPosition()); + } + + @Test + public void testPillarbox_anisotropyCorrection() { + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, + /*isAnisotropyCorrectionEnabled=*/ true); + + DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = DISPLAY_WIDTH; + displayInfo.logicalHeight = DISPLAY_HEIGHT; + displayInfo.rotation = Surface.ROTATION_90; + mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT; + // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust + // to using the whole screen. This is because display will rescale it back to fill the + // screen (in case the display menu setting is set to stretch the pixels across the display) + mDisplayDeviceInfo.xDpi = 0.5f; + mDisplayDeviceInfo.yDpi = 1.0f; + mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo); + mLogicalDisplay.updateLocked(mDeviceRepo); + + SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + + // Applications need to think that they are shown on a display with square pixels. + // as applications can be displayed on multiple displays simultaneously (mirrored). + // Content is a bit wider than in #testPillarbox, due to content added stretching + assertEquals(new Point(50, 0), mLogicalDisplay.getDisplayPosition()); + } + + @Test + public void testNoPillarbox_anisotropyCorrectionYDpi() { + mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice, + /*isAnisotropyCorrectionEnabled=*/ true); + + // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust + // to using the whole screen. This is because display will rescale it back to fill the + // screen (in case the display menu setting is set to stretch the pixels across the display) + mDisplayDeviceInfo.xDpi = 1.0f; + mDisplayDeviceInfo.yDpi = 0.5f; + + mLogicalDisplay.updateLocked(mDeviceRepo); + var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked(); + // Content width re-scaled + assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth); + assertEquals(DISPLAY_HEIGHT * 2, originalDisplayInfo.logicalHeight); + + SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); + + // Applications need to think that they are shown on a display with square pixels. + // as applications can be displayed on multiple displays simultaneously (mirrored). + // Content is too tall, should have occupy the whole screen - but it won't because of + // anisotropy correction + assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition()); + } + + @Test public void testGetDisplayPosition() { - Point expectedPosition = new Point(); + Point expectedPosition = new Point(0, 0); SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); 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/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java index 1dd64ffa5dde..5582e13cbb4d 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -145,6 +145,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { @SmallTest @Test + @Ignore("b/277916462") public void testCompMigrationUnAffiliated_skipped() throws Exception { prepareAdmin1AsDo(); prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID); @@ -216,6 +217,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { @SmallTest @Test + @Ignore("b/277916462") public void testCompMigration_keepSuspendedAppsWhenDpcIsRPlus() throws Exception { prepareAdmin1AsDo(); prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R); @@ -249,6 +251,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { @SmallTest @Test + @Ignore("b/277916462") public void testCompMigration_unsuspendAppsWhenDpcNotRPlus() throws Exception { prepareAdmin1AsDo(); prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q); 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/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 ) |