Merge "Never show the alternate bouncer if the primary bouncer is showing" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 7552b5c0..173cf6c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -19286,11 +19286,11 @@
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 @@
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;
+ 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") public final class ExtensionCaptureResult {
+ @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 @@
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 @@
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 @@
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 4b04d10..a551f0d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2190,14 +2190,6 @@
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 @@
}
@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 @@
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 @@
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 @@
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 @@
field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3
field public static final int STATUS_SUCCESS = 1; // 0x1
field public static final int STATUS_UNKNOWN = 0; // 0x0
- field public static final int STATUS_UNSUPPORTED = 2; // 0x2
+ field @Deprecated public static final int STATUS_UNSUPPORTED = 2; // 0x2
field @FlaggedApi("android.app.wearable.enable_data_request_observer_api") public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8; // 0x8
field @FlaggedApi("android.app.wearable.enable_unsupported_operation_status_code") public static final int STATUS_UNSUPPORTED_OPERATION = 6; // 0x6
field public static final int STATUS_WEARABLE_UNAVAILABLE = 4; // 0x4
@@ -12956,38 +12945,26 @@
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 1e72a06..62fc67b 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1885,8 +1885,6 @@
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 892567c6..bc45a76 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -42,6 +42,7 @@
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/Notification.java b/core/java/android/app/Notification.java
index cd4c0bc..dbdb70f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3586,12 +3586,15 @@
* Sets the token used for background operations for the pending intents associated with this
* notification.
*
+ * This token is automatically set during deserialization for you, you usually won't need to
+ * call this unless you want to change the existing token, if any.
+ *
* @hide
*/
- public void overrideAllowlistToken(IBinder token) {
- mAllowlistToken = token;
+ public void clearAllowlistToken() {
+ mAllowlistToken = null;
if (publicVersion != null) {
- publicVersion.overrideAllowlistToken(token);
+ publicVersion.clearAllowlistToken();
}
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d01626e..fa4a400 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -34,6 +34,8 @@
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 @@
return new WearableSensingManager(ctx.getOuterContext(), manager);
}});
+ registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class,
+ new CachedServiceFetcher<OnDeviceIntelligenceManager>() {
+ @Override
+ public OnDeviceIntelligenceManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder iBinder = ServiceManager.getServiceOrThrow(
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE);
+ IOnDeviceIntelligenceManager manager =
+ IOnDeviceIntelligenceManager.Stub.asInterface(iBinder);
+ return new OnDeviceIntelligenceManager(ctx.getOuterContext(), manager);
+ }
+ });
+
registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class,
new CachedServiceFetcher<GrammaticalInflectionManager>() {
@Override
diff --git a/core/java/android/app/ondeviceintelligence/Content.java b/core/java/android/app/ondeviceintelligence/Content.java
deleted file mode 100644
index 51bd156..0000000
--- 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 684c71f..30c6e19 100644
--- a/core/java/android/app/ondeviceintelligence/DownloadCallback.java
+++ b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
@@ -105,7 +105,7 @@
}
/**
- * 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 4a38c92..fd0379a 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -34,7 +34,6 @@
@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 f3cbd26..44930f2 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -60,6 +60,9 @@
/** 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 aba563f..8fc269e 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 360a809..0dbe181 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 0adf305..45963d2 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 @@
* @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 132e53e..671abe3 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 @@
* @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 0000000..03ff563a
--- /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 d195c4d..a465e3c 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -28,6 +28,7 @@
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.PersistableBundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.system.OsConstants;
import androidx.annotation.IntDef;
@@ -63,7 +65,7 @@
@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 @@
@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 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 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureReceiver.onError(
- new OnDeviceIntelligenceManagerException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -173,7 +174,7 @@
@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 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureListReceiver.onError(
- new OnDeviceIntelligenceManagerException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -211,7 +212,7 @@
@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 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> featureDetailsReceiver.onError(
- new OnDeviceIntelligenceManagerException(errorCode,
+ new OnDeviceIntelligenceException(errorCode,
errorMessage, errorParams))));
}
};
@@ -243,9 +244,8 @@
*
* 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 @@
@Override
public void onDownloadCompleted(PersistableBundle downloadParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> onDownloadCompleted(downloadParams)));
+ () -> callback.onDownloadCompleted(downloadParams)));
}
};
@@ -305,7 +305,8 @@
* 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 @@
* @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 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> outcomeReceiver.onError(
- new OnDeviceIntelligenceManagerProcessingException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams))));
}
};
@@ -357,30 +358,30 @@
* 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 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 @@
* 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 @@
* @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 @@
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 @@
}
- /** 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 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 @interface RequestType {
}
-
/**
- * Exception type to be populated in callbacks to the methods under
- * {@link OnDeviceIntelligenceManager}.
+ * {@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 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} .
- */
- 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 0000000..4d936ea
--- /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 b0b6e19..0000000
--- 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
similarity index 65%
rename from core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
rename to core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
index ac2b032..41f1807 100644
--- a/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
+++ b/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
@@ -21,19 +21,21 @@
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 fd72c49..9573e6f 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -95,11 +95,12 @@
/**
* 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 @@
* 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 8852705..bd04634 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -333,10 +333,9 @@
}
/**
- * 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 8e5a0d8..c328b7c 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/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index 09e59d3..47edba6 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -72,14 +72,20 @@
flag {
namespace: "credential_manager"
- name: "clear_credentials_api_fix_enabled"
+ name: "clear_credentials_fix_enabled"
description: "Fixes bug in clearCredential API that causes indefinite suspension"
bug: "314926460"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
namespace: "credential_manager"
- name: "hybrid_filter_fix_enabled"
+ name: "hybrid_filter_opt_fix_enabled"
description: "Removes capability check from hybrid implementation"
bug: "323923403"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index dc8f4b4..238c381 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -6098,7 +6098,7 @@
* @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 083d49f..6962811 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -142,7 +142,7 @@
/**
* 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;
/**
@@ -227,14 +227,18 @@
private static List<Size> generateSupportedSizes(List<SizeList> sizesList,
Integer format,
StreamConfigurationMap streamMap) {
- // Per API contract it is assumed that the extension is able to support all
- // camera advertised sizes for a given format in case it doesn't return
- // a valid non-empty size list.
ArrayList<Size> ret = getSupportedSizes(sizesList, format);
- Size[] supportedSizes = streamMap.getOutputSizes(format);
- if ((ret.isEmpty()) && (supportedSizes != null)) {
- ret.addAll(Arrays.asList(supportedSizes));
+
+ if (format == ImageFormat.JPEG || format == ImageFormat.YUV_420_888) {
+ // Per API contract it is assumed that the extension is able to support all
+ // camera advertised sizes for JPEG and YUV_420_888 in case it doesn't return
+ // a valid non-empty size list.
+ Size[] supportedSizes = streamMap.getOutputSizes(format);
+ if ((ret.isEmpty()) && (supportedSizes != null)) {
+ ret.addAll(Arrays.asList(supportedSizes));
+ }
}
+
return ret;
}
@@ -559,7 +563,7 @@
public ExtensionConnectionManager() {
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
extensionList.addAll(EXTENSION_LIST);
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
}
@@ -762,7 +766,7 @@
IntArray extensionList = new IntArray(EXTENSION_LIST.length);
extensionList.addAll(EXTENSION_LIST);
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
extensionList.add(EXTENSION_EYES_FREE_VIDEOGRAPHY);
}
@@ -1544,7 +1548,7 @@
@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 9fb561b..7754e32 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3905,7 +3905,7 @@
* @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 @@
* @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 @@
* @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 c0db77c..13d5c7e 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -4335,7 +4335,7 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 a01c23d..7145501 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5940,7 +5940,7 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 32039c6..c33956b 100644
--- a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
+++ b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java
@@ -40,7 +40,7 @@
* @see CaptureRequest
* @see CameraExtensionSession
*/
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public final class ExtensionCaptureRequest {
/**
@@ -74,7 +74,7 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
* <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 @@
* 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 5c99909..95feb2f 100644
--- a/core/java/android/hardware/camera2/ExtensionCaptureResult.java
+++ b/core/java/android/hardware/camera2/ExtensionCaptureResult.java
@@ -42,7 +42,7 @@
* @see CaptureRequest
* @see CameraExtensionSession
*/
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
+@FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public final class ExtensionCaptureResult {
/**
@@ -66,7 +66,7 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@PublicKey
@NonNull
@ExtensionKey
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
+ @FlaggedApi(Flags.FLAG_CONCERT_MODE_API)
public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureResult.EFV_MAX_PADDING_ZOOM_FACTOR;
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
index c4f653c..9d46b55 100644
--- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
@@ -25,5 +25,5 @@
CameraMetadataNative sessionParameter;
int sessionTemplateId;
int sessionType;
- int colorSpace;
+ int colorSpace = -1;
}
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 5b7f8bb..6d9b51cb 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -348,7 +348,23 @@
cameraOutput.setTimestampBase(OutputConfiguration.TIMESTAMP_BASE_SENSOR);
cameraOutput.setReadoutTimestampEnabled(false);
cameraOutput.setPhysicalCameraId(output.physicalCameraId);
- cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
+ if (Flags.extension10Bit()) {
+ boolean validDynamicRangeProfile = false;
+ for (long profile = DynamicRangeProfiles.STANDARD;
+ profile < DynamicRangeProfiles.PUBLIC_MAX; profile <<= 1) {
+ if (output.dynamicRangeProfile == profile) {
+ validDynamicRangeProfile = true;
+ break;
+ }
+ }
+ if (validDynamicRangeProfile) {
+ cameraOutput.setDynamicRangeProfile(output.dynamicRangeProfile);
+ } else {
+ Log.e(TAG, "Extension configured dynamic range profile "
+ + output.dynamicRangeProfile
+ + " is not valid, using default DynamicRangeProfile.STANDARD");
+ }
+ }
outputList.add(cameraOutput);
mCameraConfigMap.put(cameraOutput.getSurface(), output);
}
@@ -363,9 +379,15 @@
SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType,
outputList, new CameraExtensionUtils.HandlerExecutor(mHandler),
new SessionStateHandler());
- if (sessionConfig.colorSpace != ColorSpaceProfiles.UNSPECIFIED) {
- sessionConfiguration.setColorSpace(
- ColorSpace.Named.values()[sessionConfig.colorSpace]);
+ if (Flags.extension10Bit()) {
+ if (sessionConfig.colorSpace >= 0
+ && sessionConfig.colorSpace < ColorSpace.Named.values().length) {
+ sessionConfiguration.setColorSpace(
+ ColorSpace.Named.values()[sessionConfig.colorSpace]);
+ } else {
+ Log.e(TAG, "Extension configured color space " + sessionConfig.colorSpace
+ + " is not valid, using default unspecified color space");
+ }
}
if ((sessionConfig.sessionParameter != null) &&
(!sessionConfig.sessionParameter.isEmpty())) {
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index 73257ed..799c7545 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.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 @@
*/
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 fce3689..27e8628 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.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 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 @@
* @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 void onFailure(int errorCode, String errorMessage) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> statusReceiver.onError(
- new OnDeviceUpdateProcessingException(
+ new OnDeviceIntelligenceException(
errorCode, errorMessage))));
}
});
@@ -265,7 +261,7 @@
}
private OutcomeReceiver<Feature,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback(
+ OnDeviceIntelligenceException> wrapFeatureCallback(
IFeatureCallback featureCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -279,7 +275,7 @@
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -291,7 +287,7 @@
}
private OutcomeReceiver<List<Feature>,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapListFeaturesCallback(
+ OnDeviceIntelligenceException> wrapListFeaturesCallback(
IListFeaturesCallback listFeaturesCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -305,7 +301,7 @@
@Override
public void onError(
- @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ @NonNull OnDeviceIntelligenceException exception) {
try {
listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -317,7 +313,7 @@
}
private OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureDetailsCallback(
+ OnDeviceIntelligenceException> wrapFeatureDetailsCallback(
IFeatureDetailsCallback featureStatusCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -331,7 +327,7 @@
@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 void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
@NonNull OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
+ OnDeviceIntelligenceException> featureDetailsCallback);
/**
@@ -455,7 +451,7 @@
*/
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 @@
* @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 @@
* @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 7f7f9c2..d943c80 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -27,20 +27,21 @@
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.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 @@
}
@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 @@
}
@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 @@
}
@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 @@
@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 @@
@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 @@
@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 @@
* @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 @@
/**
* 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 @@
});
}
- 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 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
callback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -404,8 +403,8 @@
}
@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 @@
};
}
- 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 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
callback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -449,8 +448,8 @@
}
@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 @@
}
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 @@
});
}
- private OutcomeReceiver<TokenInfo,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback(
+ private OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> wrapTokenInfoCallback(
ITokenInfoCallback tokenInfoCallback) {
return new OutcomeReceiver<>() {
@Override
@@ -491,7 +489,7 @@
@Override
public void onError(
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ OnDeviceIntelligenceException exception) {
try {
tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
@@ -503,7 +501,7 @@
}
@NonNull
- private static OutcomeReceiver<PersistableBundle, OnDeviceUpdateProcessingException> wrapOutcomeReceiver(
+ private static OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> wrapOutcomeReceiver(
IProcessingUpdateStatusCallback callback) {
return new OutcomeReceiver<>() {
@Override
@@ -518,7 +516,7 @@
@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 a08264e..ccc17ec 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -819,19 +819,15 @@
/**
* 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 60e9de7..937aecc 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -363,9 +363,11 @@
* {@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 @@
* 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/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 9db8aa1..e95b216 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -28,6 +28,7 @@
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.text.LineBreakConfig;
+import android.os.Trace;
import android.text.style.ParagraphStyle;
/**
@@ -567,49 +568,59 @@
public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
@Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics) {
- final int textLength = text.length();
- if (hasAnyInterestingChars(text, textLength)) {
- return null; // There are some interesting characters. Not boring.
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("BoringLayout#isBoring");
+ Trace.setCounter("BoringLayout#textLength", text.length());
}
- if (textDir != null && textDir.isRtl(text, 0, textLength)) {
- return null; // The heuristic considers the whole text RTL. Not boring.
- }
- if (text instanceof Spanned) {
- Spanned sp = (Spanned) text;
- Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
- if (styles.length > 0) {
- return null; // There are some ParagraphStyle spans. Not boring.
+ try {
+ final int textLength = text.length();
+ if (hasAnyInterestingChars(text, textLength)) {
+ return null; // There are some interesting characters. Not boring.
+ }
+ if (textDir != null && textDir.isRtl(text, 0, textLength)) {
+ return null; // The heuristic considers the whole text RTL. Not boring.
+ }
+ if (text instanceof Spanned) {
+ Spanned sp = (Spanned) text;
+ Object[] styles = sp.getSpans(0, textLength, ParagraphStyle.class);
+ if (styles.length > 0) {
+ return null; // There are some ParagraphStyle spans. Not boring.
+ }
+ }
+
+ Metrics fm = metrics;
+ if (fm == null) {
+ fm = new Metrics();
+ } else {
+ fm.reset();
+ }
+
+ if (ClientFlags.fixLineHeightForLocale()) {
+ if (minimumFontMetrics != null) {
+ fm.set(minimumFontMetrics);
+ // Because the font metrics is provided by public APIs, adjust the top/bottom
+ // with ascent/descent: top must be smaller than ascent, bottom must be larger
+ // than descent.
+ fm.top = Math.min(fm.top, fm.ascent);
+ fm.bottom = Math.max(fm.bottom, fm.descent);
+ }
+ }
+
+ TextLine line = TextLine.obtain();
+ line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
+ Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
+ 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
+ 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
+ useFallbackLineSpacing);
+ fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
+ TextLine.recycle(line);
+
+ return fm;
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
}
}
-
- Metrics fm = metrics;
- if (fm == null) {
- fm = new Metrics();
- } else {
- fm.reset();
- }
-
- if (ClientFlags.fixLineHeightForLocale()) {
- if (minimumFontMetrics != null) {
- fm.set(minimumFontMetrics);
- // Because the font metrics is provided by public APIs, adjust the top/bottom with
- // ascent/descent: top must be smaller than ascent, bottom must be larger than
- // descent.
- fm.top = Math.min(fm.top, fm.ascent);
- fm.bottom = Math.max(fm.bottom, fm.descent);
- }
- }
-
- TextLine line = TextLine.obtain();
- line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
- Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
- 0 /* ellipsisStart, 0 since text has not been ellipsized at this point */,
- 0 /* ellipsisEnd, 0 since text has not been ellipsized at this point */,
- useFallbackLineSpacing);
- fm.width = (int) Math.ceil(line.metrics(fm, fm.mDrawingBounds, false, null));
- TextLine.recycle(line);
-
- return fm;
}
@Override
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index cce4f7b..99ce0ef 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.os.Build;
+import android.os.Trace;
import android.text.method.OffsetMapping;
import android.text.style.ReplacementSpan;
import android.text.style.UpdateLayout;
@@ -636,207 +637,224 @@
/** @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public void reflow(CharSequence s, int where, int before, int after) {
- if (s != mBase)
- return;
-
- CharSequence text = mDisplay;
- int len = text.length();
-
- // seek back to the start of the paragraph
-
- int find = TextUtils.lastIndexOf(text, '\n', where - 1);
- if (find < 0)
- find = 0;
- else
- find = find + 1;
-
- {
- int diff = where - find;
- before += diff;
- after += diff;
- where -= diff;
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("DynamicLayout#reflow");
}
-
- // seek forward to the end of the paragraph
-
- int look = TextUtils.indexOf(text, '\n', where + after);
- if (look < 0)
- look = len;
- else
- look++; // we want the index after the \n
-
- int change = look - (where + after);
- before += change;
- after += change;
-
- // seek further out to cover anything that is forced to wrap together
-
- if (text instanceof Spanned) {
- Spanned sp = (Spanned) text;
- boolean again;
-
- do {
- again = false;
-
- Object[] force = sp.getSpans(where, where + after,
- WrapTogetherSpan.class);
-
- for (int i = 0; i < force.length; i++) {
- int st = sp.getSpanStart(force[i]);
- int en = sp.getSpanEnd(force[i]);
-
- if (st < where) {
- again = true;
-
- int diff = where - st;
- before += diff;
- after += diff;
- where -= diff;
- }
-
- if (en > where + after) {
- again = true;
-
- int diff = en - (where + after);
- before += diff;
- after += diff;
- }
- }
- } while (again);
- }
-
- // find affected region of old layout
-
- int startline = getLineForOffset(where);
- int startv = getLineTop(startline);
-
- int endline = getLineForOffset(where + before);
- if (where + after == len)
- endline = getLineCount();
- int endv = getLineTop(endline);
- boolean islast = (endline == getLineCount());
-
- // generate new layout for affected text
-
- StaticLayout reflowed;
- StaticLayout.Builder b;
-
- synchronized (sLock) {
- reflowed = sStaticLayout;
- b = sBuilder;
- sStaticLayout = null;
- sBuilder = null;
- }
-
- if (b == null) {
- b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth());
- }
-
- b.setText(text, where, where + after)
- .setPaint(getPaint())
- .setWidth(getWidth())
- .setTextDirection(getTextDirectionHeuristic())
- .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
- .setUseLineSpacingFromFallbacks(mFallbackLineSpacing)
- .setEllipsizedWidth(mEllipsizedWidth)
- .setEllipsize(mEllipsizeAt)
- .setBreakStrategy(mBreakStrategy)
- .setHyphenationFrequency(mHyphenationFrequency)
- .setJustificationMode(mJustificationMode)
- .setLineBreakConfig(mLineBreakConfig)
- .setAddLastLineLineSpacing(!islast)
- .setIncludePad(false)
- .setUseBoundsForWidth(mUseBoundsForWidth)
- .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
- .setMinimumFontMetrics(mMinimumFontMetrics)
- .setCalculateBounds(true);
-
- reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
- int n = reflowed.getLineCount();
- // If the new layout has a blank line at the end, but it is not
- // the very end of the buffer, then we already have a line that
- // starts there, so disregard the blank line.
-
- if (where + after != len && reflowed.getLineStart(n - 1) == where + after)
- n--;
-
- // remove affected lines from old layout
- mInts.deleteAt(startline, endline - startline);
- mObjects.deleteAt(startline, endline - startline);
-
- // adjust offsets in layout for new height and offsets
-
- int ht = reflowed.getLineTop(n);
- int toppad = 0, botpad = 0;
-
- if (mIncludePad && startline == 0) {
- toppad = reflowed.getTopPadding();
- mTopPadding = toppad;
- ht -= toppad;
- }
- if (mIncludePad && islast) {
- botpad = reflowed.getBottomPadding();
- mBottomPadding = botpad;
- ht += botpad;
- }
-
- mInts.adjustValuesBelow(startline, START, after - before);
- mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
-
- // insert new layout
-
- int[] ints;
-
- if (mEllipsize) {
- ints = new int[COLUMNS_ELLIPSIZE];
- ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
- } else {
- ints = new int[COLUMNS_NORMAL];
- }
-
- Directions[] objects = new Directions[1];
-
- for (int i = 0; i < n; i++) {
- final int start = reflowed.getLineStart(i);
- ints[START] = start;
- ints[DIR] |= reflowed.getParagraphDirection(i) << DIR_SHIFT;
- ints[TAB] |= reflowed.getLineContainsTab(i) ? TAB_MASK : 0;
-
- int top = reflowed.getLineTop(i) + startv;
- if (i > 0)
- top -= toppad;
- ints[TOP] = top;
-
- int desc = reflowed.getLineDescent(i);
- if (i == n - 1)
- desc += botpad;
-
- ints[DESCENT] = desc;
- ints[EXTRA] = reflowed.getLineExtra(i);
- objects[0] = reflowed.getLineDirections(i);
-
- final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
- ints[HYPHEN] = StaticLayout.packHyphenEdit(
- reflowed.getStartHyphenEdit(i), reflowed.getEndHyphenEdit(i));
- ints[MAY_PROTRUDE_FROM_TOP_OR_BOTTOM] |=
- contentMayProtrudeFromLineTopOrBottom(text, start, end) ?
- MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK : 0;
-
- if (mEllipsize) {
- ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
- ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
+ try {
+ if (s != mBase) {
+ return;
}
- mInts.insertAt(startline + i, ints);
- mObjects.insertAt(startline + i, objects);
- }
+ CharSequence text = mDisplay;
+ int len = text.length();
- updateBlocks(startline, endline - 1, n);
+ // seek back to the start of the paragraph
- b.finish();
- synchronized (sLock) {
- sStaticLayout = reflowed;
- sBuilder = b;
+ int find = TextUtils.lastIndexOf(text, '\n', where - 1);
+ if (find < 0) {
+ find = 0;
+ } else {
+ find = find + 1;
+ }
+
+ {
+ int diff = where - find;
+ before += diff;
+ after += diff;
+ where -= diff;
+ }
+
+ // seek forward to the end of the paragraph
+
+ int look = TextUtils.indexOf(text, '\n', where + after);
+ if (look < 0) {
+ look = len;
+ } else {
+ look++; // we want the index after the \n
+ }
+
+ int change = look - (where + after);
+ before += change;
+ after += change;
+
+ // seek further out to cover anything that is forced to wrap together
+
+ if (text instanceof Spanned) {
+ Spanned sp = (Spanned) text;
+ boolean again;
+
+ do {
+ again = false;
+
+ Object[] force = sp.getSpans(where, where + after,
+ WrapTogetherSpan.class);
+
+ for (int i = 0; i < force.length; i++) {
+ int st = sp.getSpanStart(force[i]);
+ int en = sp.getSpanEnd(force[i]);
+
+ if (st < where) {
+ again = true;
+
+ int diff = where - st;
+ before += diff;
+ after += diff;
+ where -= diff;
+ }
+
+ if (en > where + after) {
+ again = true;
+
+ int diff = en - (where + after);
+ before += diff;
+ after += diff;
+ }
+ }
+ } while (again);
+ }
+
+ // find affected region of old layout
+
+ int startline = getLineForOffset(where);
+ int startv = getLineTop(startline);
+
+ int endline = getLineForOffset(where + before);
+ if (where + after == len) {
+ endline = getLineCount();
+ }
+ int endv = getLineTop(endline);
+ boolean islast = (endline == getLineCount());
+
+ // generate new layout for affected text
+
+ StaticLayout reflowed;
+ StaticLayout.Builder b;
+
+ synchronized (sLock) {
+ reflowed = sStaticLayout;
+ b = sBuilder;
+ sStaticLayout = null;
+ sBuilder = null;
+ }
+
+ if (b == null) {
+ b = StaticLayout.Builder.obtain(text, where, where + after, getPaint(), getWidth());
+ }
+
+ b.setText(text, where, where + after)
+ .setPaint(getPaint())
+ .setWidth(getWidth())
+ .setTextDirection(getTextDirectionHeuristic())
+ .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
+ .setUseLineSpacingFromFallbacks(mFallbackLineSpacing)
+ .setEllipsizedWidth(mEllipsizedWidth)
+ .setEllipsize(mEllipsizeAt)
+ .setBreakStrategy(mBreakStrategy)
+ .setHyphenationFrequency(mHyphenationFrequency)
+ .setJustificationMode(mJustificationMode)
+ .setLineBreakConfig(mLineBreakConfig)
+ .setAddLastLineLineSpacing(!islast)
+ .setIncludePad(false)
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setShiftDrawingOffsetForStartOverhang(mShiftDrawingOffsetForStartOverhang)
+ .setMinimumFontMetrics(mMinimumFontMetrics)
+ .setCalculateBounds(true);
+
+ reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */,
+ reflowed);
+ int n = reflowed.getLineCount();
+ // If the new layout has a blank line at the end, but it is not
+ // the very end of the buffer, then we already have a line that
+ // starts there, so disregard the blank line.
+
+ if (where + after != len && reflowed.getLineStart(n - 1) == where + after) {
+ n--;
+ }
+
+ // remove affected lines from old layout
+ mInts.deleteAt(startline, endline - startline);
+ mObjects.deleteAt(startline, endline - startline);
+
+ // adjust offsets in layout for new height and offsets
+
+ int ht = reflowed.getLineTop(n);
+ int toppad = 0, botpad = 0;
+
+ if (mIncludePad && startline == 0) {
+ toppad = reflowed.getTopPadding();
+ mTopPadding = toppad;
+ ht -= toppad;
+ }
+ if (mIncludePad && islast) {
+ botpad = reflowed.getBottomPadding();
+ mBottomPadding = botpad;
+ ht += botpad;
+ }
+
+ mInts.adjustValuesBelow(startline, START, after - before);
+ mInts.adjustValuesBelow(startline, TOP, startv - endv + ht);
+
+ // insert new layout
+
+ int[] ints;
+
+ if (mEllipsize) {
+ ints = new int[COLUMNS_ELLIPSIZE];
+ ints[ELLIPSIS_START] = ELLIPSIS_UNDEFINED;
+ } else {
+ ints = new int[COLUMNS_NORMAL];
+ }
+
+ Directions[] objects = new Directions[1];
+
+ for (int i = 0; i < n; i++) {
+ final int start = reflowed.getLineStart(i);
+ ints[START] = start;
+ ints[DIR] |= reflowed.getParagraphDirection(i) << DIR_SHIFT;
+ ints[TAB] |= reflowed.getLineContainsTab(i) ? TAB_MASK : 0;
+
+ int top = reflowed.getLineTop(i) + startv;
+ if (i > 0) {
+ top -= toppad;
+ }
+ ints[TOP] = top;
+
+ int desc = reflowed.getLineDescent(i);
+ if (i == n - 1) {
+ desc += botpad;
+ }
+
+ ints[DESCENT] = desc;
+ ints[EXTRA] = reflowed.getLineExtra(i);
+ objects[0] = reflowed.getLineDirections(i);
+
+ final int end = (i == n - 1) ? where + after : reflowed.getLineStart(i + 1);
+ ints[HYPHEN] = StaticLayout.packHyphenEdit(
+ reflowed.getStartHyphenEdit(i), reflowed.getEndHyphenEdit(i));
+ ints[MAY_PROTRUDE_FROM_TOP_OR_BOTTOM] |=
+ contentMayProtrudeFromLineTopOrBottom(text, start, end)
+ ? MAY_PROTRUDE_FROM_TOP_OR_BOTTOM_MASK : 0;
+
+ if (mEllipsize) {
+ ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
+ ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
+ }
+
+ mInts.insertAt(startline + i, ints);
+ mObjects.insertAt(startline + i, objects);
+ }
+
+ updateBlocks(startline, endline - 1, n);
+
+ b.finish();
+ synchronized (sLock) {
+ sStaticLayout = reflowed;
+ sBuilder = b;
+ }
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
}
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8dee4b1..ce238a7 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -39,6 +39,7 @@
import android.graphics.text.LineBreakConfig;
import android.graphics.text.LineBreaker;
import android.os.Build;
+import android.os.Trace;
import android.text.method.TextKeyListener;
import android.text.style.AlignmentSpan;
import android.text.style.LeadingMarginSpan;
@@ -70,6 +71,11 @@
* For text that will not change, use a {@link StaticLayout}.
*/
public abstract class Layout {
+
+ /** @hide */
+ protected static final boolean TRACE_LAYOUT = Build.isDebuggable();
+
+
/** @hide */
@IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
LineBreaker.BREAK_STRATEGY_SIMPLE,
@@ -472,40 +478,51 @@
@Nullable Path selectionPath,
@Nullable Paint selectionPaint,
int cursorOffsetVertical) {
- float leftShift = 0;
- if (mUseBoundsForWidth && mShiftDrawingOffsetForStartOverhang) {
- RectF drawingRect = computeDrawingBoundingBox();
- if (drawingRect.left < 0) {
- leftShift = -drawingRect.left;
- canvas.translate(leftShift, 0);
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("Layout#draw");
+ }
+ try {
+ float leftShift = 0;
+ if (mUseBoundsForWidth && mShiftDrawingOffsetForStartOverhang) {
+ RectF drawingRect = computeDrawingBoundingBox();
+ if (drawingRect.left < 0) {
+ leftShift = -drawingRect.left;
+ canvas.translate(leftShift, 0);
+ }
}
- }
- final long lineRange = getLineRangeForDraw(canvas);
- int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
- int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
- if (lastLine < 0) return;
+ final long lineRange = getLineRangeForDraw(canvas);
+ int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
+ int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
+ if (lastLine < 0) return;
- if (shouldDrawHighlightsOnTop(canvas)) {
- drawBackground(canvas, firstLine, lastLine);
- } else {
- drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
- cursorOffsetVertical, firstLine, lastLine);
- }
+ if (shouldDrawHighlightsOnTop(canvas)) {
+ drawBackground(canvas, firstLine, lastLine);
+ } else {
+ drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath,
+ selectionPaint,
+ cursorOffsetVertical, firstLine, lastLine);
+ }
- drawText(canvas, firstLine, lastLine);
+ drawText(canvas, firstLine, lastLine);
- // Since high contrast text draws a solid rectangle background behind the text, it covers up
- // the highlights and selections. In this case we draw over the top of the text with a
- // blend mode that ensures the text stays high-contrast.
- if (shouldDrawHighlightsOnTop(canvas)) {
- drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
- cursorOffsetVertical, firstLine, lastLine);
- }
+ // Since high contrast text draws a solid rectangle background behind the text, it
+ // covers up the highlights and selections. In this case we draw over the top of the
+ // text with a blend mode that ensures the text stays high-contrast.
+ if (shouldDrawHighlightsOnTop(canvas)) {
+ drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath,
+ selectionPaint,
+ cursorOffsetVertical, firstLine, lastLine);
+ }
- if (leftShift != 0) {
- // Manually translate back to the original position because of b/324498002, using
- // save/restore disappears the toggle switch drawables.
- canvas.translate(-leftShift, 0);
+ if (leftShift != 0) {
+ // Manually translate back to the original position because of b/324498002, using
+ // save/restore disappears the toggle switch drawables.
+ canvas.translate(-leftShift, 0);
+ }
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
}
}
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index 5f6a9bd..14401a6 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -25,6 +25,8 @@
import android.graphics.Rect;
import android.graphics.text.LineBreakConfig;
import android.graphics.text.MeasuredText;
+import android.os.Build;
+import android.os.Trace;
import android.text.style.MetricAffectingSpan;
import com.android.internal.util.Preconditions;
@@ -78,6 +80,8 @@
public class PrecomputedText implements Spannable {
private static final char LINE_FEED = '\n';
+ private static final boolean TRACE_PCT = Build.isDebuggable();
+
/**
* The information required for building {@link PrecomputedText}.
*
@@ -447,35 +451,47 @@
private static ParagraphInfo[] createMeasuredParagraphsFromPrecomputedText(
@NonNull PrecomputedText pct, @NonNull Params params, boolean computeLayout) {
- final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
- && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
- final int hyphenationMode;
- if (needHyphenation) {
- hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
- ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
- MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
- } else {
- hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+ if (TRACE_PCT) {
+ Trace.beginSection("PrecomputedText#createMeasuredParagraphsFromPrecomputedText");
+ Trace.setCounter("PrecomputedText#textCharCount", pct.length());
}
- LineBreakConfig config = params.getLineBreakConfig();
- if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
- && pct.getParagraphCount() != 1) {
- // If the text has multiple paragraph, resolve line break word style auto to none.
- config = new LineBreakConfig.Builder()
- .merge(config)
- .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
- .build();
+ try {
+ final boolean needHyphenation =
+ params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
+ && params.getHyphenationFrequency()
+ != Layout.HYPHENATION_FREQUENCY_NONE;
+ final int hyphenationMode;
+ if (needHyphenation) {
+ hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+ ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+ MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
+ } else {
+ hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+ }
+ LineBreakConfig config = params.getLineBreakConfig();
+ if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+ && pct.getParagraphCount() != 1) {
+ // If the text has multiple paragraph, resolve line break word style auto to none.
+ config = new LineBreakConfig.Builder()
+ .merge(config)
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .build();
+ }
+ ArrayList<ParagraphInfo> result = new ArrayList<>();
+ for (int i = 0; i < pct.getParagraphCount(); ++i) {
+ final int paraStart = pct.getParagraphStart(i);
+ final int paraEnd = pct.getParagraphEnd(i);
+ result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
+ params.getTextPaint(), config, pct, paraStart, paraEnd,
+ params.getTextDirection(), hyphenationMode, computeLayout, true,
+ pct.getMeasuredParagraph(i), null /* no recycle */)));
+ }
+ return result.toArray(new ParagraphInfo[result.size()]);
+ } finally {
+ if (TRACE_PCT) {
+ Trace.endSection();
+ }
}
- ArrayList<ParagraphInfo> result = new ArrayList<>();
- for (int i = 0; i < pct.getParagraphCount(); ++i) {
- final int paraStart = pct.getParagraphStart(i);
- final int paraEnd = pct.getParagraphEnd(i);
- result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
- params.getTextPaint(), config, pct, paraStart, paraEnd,
- params.getTextDirection(), hyphenationMode, computeLayout, true,
- pct.getMeasuredParagraph(i), null /* no recycle */)));
- }
- return result.toArray(new ParagraphInfo[result.size()]);
}
/** @hide */
@@ -483,53 +499,65 @@
@NonNull CharSequence text, @NonNull Params params,
@IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean computeLayout,
boolean computeBounds) {
- ArrayList<ParagraphInfo> result = new ArrayList<>();
-
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(params);
- final boolean needHyphenation = params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
- && params.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE;
- final int hyphenationMode;
- if (needHyphenation) {
- hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
- ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
- MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
- } else {
- hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
+ if (TRACE_PCT) {
+ Trace.beginSection("PrecomputedText#createMeasuredParagraphs");
+ Trace.setCounter("PrecomputedText#textCharCount", text.length());
}
+ try {
+ ArrayList<ParagraphInfo> result = new ArrayList<>();
- LineBreakConfig config = null;
- int paraEnd = 0;
- for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
- paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
- if (paraEnd < 0) {
- // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
- // end.
- paraEnd = end;
+ Preconditions.checkNotNull(text);
+ Preconditions.checkNotNull(params);
+ final boolean needHyphenation =
+ params.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE
+ && params.getHyphenationFrequency()
+ != Layout.HYPHENATION_FREQUENCY_NONE;
+ final int hyphenationMode;
+ if (needHyphenation) {
+ hyphenationMode = isFastHyphenation(params.getHyphenationFrequency())
+ ? MeasuredText.Builder.HYPHENATION_MODE_FAST :
+ MeasuredText.Builder.HYPHENATION_MODE_NORMAL;
} else {
- paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
+ hyphenationMode = MeasuredText.Builder.HYPHENATION_MODE_NONE;
}
- if (config == null) {
- config = params.getLineBreakConfig();
- if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
- && !(paraStart == start && paraEnd == end)) {
- // If the text has multiple paragraph, resolve line break word style auto to
- // none.
- config = new LineBreakConfig.Builder()
- .merge(config)
- .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
- .build();
+ LineBreakConfig config = null;
+ int paraEnd = 0;
+ for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
+ paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
+ if (paraEnd < 0) {
+ // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph
+ // end.
+ paraEnd = end;
+ } else {
+ paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
}
- }
- result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
- params.getTextPaint(), config, text, paraStart, paraEnd,
- params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
- null /* no hint */,
- null /* no recycle */)));
+ if (config == null) {
+ config = params.getLineBreakConfig();
+ if (config.getLineBreakWordStyle() == LineBreakConfig.LINE_BREAK_WORD_STYLE_AUTO
+ && !(paraStart == start && paraEnd == end)) {
+ // If the text has multiple paragraph, resolve line break word style auto to
+ // none.
+ config = new LineBreakConfig.Builder()
+ .merge(config)
+ .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)
+ .build();
+ }
+ }
+
+ result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout(
+ params.getTextPaint(), config, text, paraStart, paraEnd,
+ params.getTextDirection(), hyphenationMode, computeLayout, computeBounds,
+ null /* no hint */,
+ null /* no recycle */)));
+ }
+ return result.toArray(new ParagraphInfo[result.size()]);
+ } finally {
+ if (TRACE_PCT) {
+ Trace.endSection();
+ }
}
- return result.toArray(new ParagraphInfo[result.size()]);
}
// Use PrecomputedText.create instead.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 3dd3a9e..d1b14d1 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -542,10 +542,20 @@
*/
@NonNull
public StaticLayout build() {
- StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
- ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
- Builder.recycle(this);
- return result;
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#build");
+ Trace.setCounter("StaticLayout#textLength", mText.length());
+ }
+ try {
+ StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
+ ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
+ Builder.recycle(this);
+ return result;
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
}
/**
@@ -562,16 +572,21 @@
*/
/* package */ @NonNull StaticLayout buildPartialStaticLayoutForDynamicLayout(
boolean trackpadding, StaticLayout recycle) {
- if (recycle == null) {
- recycle = new StaticLayout();
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#forDynamicLayout");
+ Trace.setCounter("StaticLayout#textLength", mText.length());
}
- Trace.beginSection("Generating StaticLayout For DynamicLayout");
try {
+ if (recycle == null) {
+ recycle = new StaticLayout();
+ }
recycle.generate(this, mIncludePad, trackpadding);
+ return recycle;
} finally {
- Trace.endSection();
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
}
- return recycle;
}
private CharSequence mText;
@@ -727,12 +742,7 @@
mLeftIndents = b.mLeftIndents;
mRightIndents = b.mRightIndents;
- Trace.beginSection("Constructing StaticLayout");
- try {
- generate(b, b.mIncludePad, trackPadding);
- } finally {
- Trace.endSection();
- }
+ generate(b, b.mIncludePad, trackPadding);
}
private static int getBaseHyphenationFrequency(int frequency) {
@@ -842,14 +852,23 @@
case PrecomputedText.Params.UNUSABLE:
break;
case PrecomputedText.Params.NEED_RECOMPUTE:
- final PrecomputedText.Params newParams =
- new PrecomputedText.Params.Builder(paint)
- .setBreakStrategy(b.mBreakStrategy)
- .setHyphenationFrequency(b.mHyphenationFrequency)
- .setTextDirection(textDir)
- .setLineBreakConfig(b.mLineBreakConfig)
- .build();
- precomputed = PrecomputedText.create(precomputed, newParams);
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#recomputePct");
+ }
+ try {
+ final PrecomputedText.Params newParams =
+ new PrecomputedText.Params.Builder(paint)
+ .setBreakStrategy(b.mBreakStrategy)
+ .setHyphenationFrequency(b.mHyphenationFrequency)
+ .setTextDirection(textDir)
+ .setLineBreakConfig(b.mLineBreakConfig)
+ .build();
+ precomputed = PrecomputedText.create(precomputed, newParams);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
paragraphInfo = precomputed.getParagraphInfo();
break;
case PrecomputedText.Params.USABLE:
@@ -860,232 +879,261 @@
}
if (paragraphInfo == null) {
- final PrecomputedText.Params param = new PrecomputedText.Params(paint,
- b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
- paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
- bufEnd, false /* computeLayout */, b.mCalculateBounds);
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#computePct");
+ }
+ try {
+ final PrecomputedText.Params param = new PrecomputedText.Params(paint,
+ b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency);
+ paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart,
+ bufEnd, false /* computeLayout */, b.mCalculateBounds);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
}
for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) {
- final int paraStart = paraIndex == 0
- ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
- final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#processParagraph");
+ Trace.setCounter("StaticLayout#paragraph", paraIndex);
+ }
+ try {
+ final int paraStart = paraIndex == 0
+ ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd;
+ final int paraEnd = paragraphInfo[paraIndex].paragraphEnd;
- int firstWidthLineCount = 1;
- int firstWidth = outerWidth;
- int restWidth = outerWidth;
+ int firstWidthLineCount = 1;
+ int firstWidth = outerWidth;
+ int restWidth = outerWidth;
- LineHeightSpan[] chooseHt = null;
- if (spanned != null) {
- LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
- LeadingMarginSpan.class);
- for (int i = 0; i < sp.length; i++) {
- LeadingMarginSpan lms = sp[i];
- firstWidth -= sp[i].getLeadingMargin(true);
- restWidth -= sp[i].getLeadingMargin(false);
+ LineHeightSpan[] chooseHt = null;
+ if (spanned != null) {
+ LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+ LeadingMarginSpan.class);
+ for (int i = 0; i < sp.length; i++) {
+ LeadingMarginSpan lms = sp[i];
+ firstWidth -= sp[i].getLeadingMargin(true);
+ restWidth -= sp[i].getLeadingMargin(false);
- // LeadingMarginSpan2 is odd. The count affects all
- // leading margin spans, not just this particular one
- if (lms instanceof LeadingMarginSpan2) {
- LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
- firstWidthLineCount = Math.max(firstWidthLineCount,
- lms2.getLeadingMarginLineCount());
+ // LeadingMarginSpan2 is odd. The count affects all
+ // leading margin spans, not just this particular one
+ if (lms instanceof LeadingMarginSpan2) {
+ LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+ firstWidthLineCount = Math.max(firstWidthLineCount,
+ lms2.getLeadingMarginLineCount());
+ }
+ }
+
+ chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+
+ if (chooseHt.length == 0) {
+ chooseHt = null; // So that out() would not assume it has any contents
+ } else {
+ if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+ chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+ }
+
+ for (int i = 0; i < chooseHt.length; i++) {
+ int o = spanned.getSpanStart(chooseHt[i]);
+
+ if (o < paraStart) {
+ // starts in this layout, before the
+ // current paragraph
+
+ chooseHtv[i] = getLineTop(getLineForOffset(o));
+ } else {
+ // starts in this paragraph
+
+ chooseHtv[i] = v;
+ }
+ }
+ }
+ }
+ // tab stop locations
+ float[] variableTabStops = null;
+ if (spanned != null) {
+ TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+ paraEnd, TabStopSpan.class);
+ if (spans.length > 0) {
+ float[] stops = new float[spans.length];
+ for (int i = 0; i < spans.length; i++) {
+ stops[i] = (float) spans[i].getTabStop();
+ }
+ Arrays.sort(stops, 0, stops.length);
+ variableTabStops = stops;
}
}
- chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+ final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
+ final char[] chs = measuredPara.getChars();
+ final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
+ final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
- if (chooseHt.length == 0) {
- chooseHt = null; // So that out() would not assume it has any contents
- } else {
- if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
- chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+ constraints.setWidth(restWidth);
+ constraints.setIndent(firstWidth, firstWidthLineCount);
+ constraints.setTabStops(variableTabStops, TAB_INCREMENT);
+
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("LineBreaker#computeLineBreaks");
+ }
+ LineBreaker.Result res;
+ try {
+ res = lineBreaker.computeLineBreaks(
+ measuredPara.getMeasuredText(), constraints, mLineCount);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
}
+ }
+ int breakCount = res.getLineCount();
+ if (lineBreakCapacity < breakCount) {
+ lineBreakCapacity = breakCount;
+ breaks = new int[lineBreakCapacity];
+ lineWidths = new float[lineBreakCapacity];
+ ascents = new float[lineBreakCapacity];
+ descents = new float[lineBreakCapacity];
+ hasTabs = new boolean[lineBreakCapacity];
+ hyphenEdits = new int[lineBreakCapacity];
+ }
- for (int i = 0; i < chooseHt.length; i++) {
- int o = spanned.getSpanStart(chooseHt[i]);
+ for (int i = 0; i < breakCount; ++i) {
+ breaks[i] = res.getLineBreakOffset(i);
+ lineWidths[i] = res.getLineWidth(i);
+ ascents[i] = res.getLineAscent(i);
+ descents[i] = res.getLineDescent(i);
+ hasTabs[i] = res.hasLineTab(i);
+ hyphenEdits[i] =
+ packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
+ }
- if (o < paraStart) {
- // starts in this layout, before the
- // current paragraph
-
- chooseHtv[i] = getLineTop(getLineForOffset(o));
+ final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+ final boolean ellipsisMayBeApplied = ellipsize != null
+ && (ellipsize == TextUtils.TruncateAt.END
+ || (mMaximumVisibleLineCount == 1
+ && ellipsize != TextUtils.TruncateAt.MARQUEE));
+ if (0 < remainingLineCount && remainingLineCount < breakCount
+ && ellipsisMayBeApplied) {
+ // Calculate width
+ float width = 0;
+ boolean hasTab = false; // XXX May need to also have starting hyphen edit
+ for (int i = remainingLineCount - 1; i < breakCount; i++) {
+ if (i == breakCount - 1) {
+ width += lineWidths[i];
} else {
- // starts in this paragraph
+ for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+ width += measuredPara.getCharWidthAt(j);
+ }
+ }
+ hasTab |= hasTabs[i];
+ }
+ // Treat the last line and overflowed lines as a single line.
+ breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+ lineWidths[remainingLineCount - 1] = width;
+ hasTabs[remainingLineCount - 1] = hasTab;
- chooseHtv[i] = v;
+ breakCount = remainingLineCount;
+ }
+
+ // here is the offset of the starting character of the line we are currently
+ // measuring
+ int here = paraStart;
+
+ int fmTop = defaultTop;
+ int fmBottom = defaultBottom;
+ int fmAscent = defaultAscent;
+ int fmDescent = defaultDescent;
+ int fmCacheIndex = 0;
+ int spanEndCacheIndex = 0;
+ int breakIndex = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ // retrieve end of span
+ spanEnd = spanEndCache[spanEndCacheIndex++];
+
+ // retrieve cached metrics, order matches above
+ fm.top = fmCache[fmCacheIndex * 4 + 0];
+ fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+ fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+ fm.descent = fmCache[fmCacheIndex * 4 + 3];
+ fmCacheIndex++;
+
+ if (fm.top < fmTop) {
+ fmTop = fm.top;
+ }
+ if (fm.ascent < fmAscent) {
+ fmAscent = fm.ascent;
+ }
+ if (fm.descent > fmDescent) {
+ fmDescent = fm.descent;
+ }
+ if (fm.bottom > fmBottom) {
+ fmBottom = fm.bottom;
+ }
+
+ // skip breaks ending before current span range
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+ breakIndex++;
+ }
+
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+ int endPos = paraStart + breaks[breakIndex];
+
+ boolean moreChars = (endPos < bufEnd);
+
+ final int ascent = isFallbackLineSpacing
+ ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+ : fmAscent;
+ final int descent = isFallbackLineSpacing
+ ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+ : fmDescent;
+
+ // The fallback ascent/descent may be larger than top/bottom of the default
+ // font metrics. Adjust top/bottom with ascent/descent for avoiding
+ // unexpected clipping.
+ if (isFallbackLineSpacing) {
+ if (ascent < fmTop) {
+ fmTop = ascent;
+ }
+ if (descent > fmBottom) {
+ fmBottom = descent;
+ }
+ }
+
+ v = out(source, here, endPos,
+ ascent, descent, fmTop, fmBottom,
+ v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+ hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
+ measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
+ paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
+ paint, moreChars);
+
+ if (endPos < spanEnd) {
+ // preserve metrics for current span
+ fmTop = Math.min(defaultTop, fm.top);
+ fmBottom = Math.max(defaultBottom, fm.bottom);
+ fmAscent = Math.min(defaultAscent, fm.ascent);
+ fmDescent = Math.max(defaultDescent, fm.descent);
+ } else {
+ fmTop = fmBottom = fmAscent = fmDescent = 0;
+ }
+
+ here = endPos;
+ breakIndex++;
+
+ if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+ return;
}
}
}
- }
- // tab stop locations
- float[] variableTabStops = null;
- if (spanned != null) {
- TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
- paraEnd, TabStopSpan.class);
- if (spans.length > 0) {
- float[] stops = new float[spans.length];
- for (int i = 0; i < spans.length; i++) {
- stops[i] = (float) spans[i].getTabStop();
- }
- Arrays.sort(stops, 0, stops.length);
- variableTabStops = stops;
+
+ if (paraEnd == bufEnd) {
+ break;
}
- }
-
- final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured;
- final char[] chs = measuredPara.getChars();
- final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
- final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
-
- constraints.setWidth(restWidth);
- constraints.setIndent(firstWidth, firstWidthLineCount);
- constraints.setTabStops(variableTabStops, TAB_INCREMENT);
-
- LineBreaker.Result res = lineBreaker.computeLineBreaks(
- measuredPara.getMeasuredText(), constraints, mLineCount);
- int breakCount = res.getLineCount();
- if (lineBreakCapacity < breakCount) {
- lineBreakCapacity = breakCount;
- breaks = new int[lineBreakCapacity];
- lineWidths = new float[lineBreakCapacity];
- ascents = new float[lineBreakCapacity];
- descents = new float[lineBreakCapacity];
- hasTabs = new boolean[lineBreakCapacity];
- hyphenEdits = new int[lineBreakCapacity];
- }
-
- for (int i = 0; i < breakCount; ++i) {
- breaks[i] = res.getLineBreakOffset(i);
- lineWidths[i] = res.getLineWidth(i);
- ascents[i] = res.getLineAscent(i);
- descents[i] = res.getLineDescent(i);
- hasTabs[i] = res.hasLineTab(i);
- hyphenEdits[i] =
- packHyphenEdit(res.getStartLineHyphenEdit(i), res.getEndLineHyphenEdit(i));
- }
-
- final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
- final boolean ellipsisMayBeApplied = ellipsize != null
- && (ellipsize == TextUtils.TruncateAt.END
- || (mMaximumVisibleLineCount == 1
- && ellipsize != TextUtils.TruncateAt.MARQUEE));
- if (0 < remainingLineCount && remainingLineCount < breakCount
- && ellipsisMayBeApplied) {
- // Calculate width
- float width = 0;
- boolean hasTab = false; // XXX May need to also have starting hyphen edit
- for (int i = remainingLineCount - 1; i < breakCount; i++) {
- if (i == breakCount - 1) {
- width += lineWidths[i];
- } else {
- for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
- width += measuredPara.getCharWidthAt(j);
- }
- }
- hasTab |= hasTabs[i];
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
}
- // Treat the last line and overflowed lines as a single line.
- breaks[remainingLineCount - 1] = breaks[breakCount - 1];
- lineWidths[remainingLineCount - 1] = width;
- hasTabs[remainingLineCount - 1] = hasTab;
-
- breakCount = remainingLineCount;
- }
-
- // here is the offset of the starting character of the line we are currently
- // measuring
- int here = paraStart;
-
- int fmTop = defaultTop;
- int fmBottom = defaultBottom;
- int fmAscent = defaultAscent;
- int fmDescent = defaultDescent;
- int fmCacheIndex = 0;
- int spanEndCacheIndex = 0;
- int breakIndex = 0;
- for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
- // retrieve end of span
- spanEnd = spanEndCache[spanEndCacheIndex++];
-
- // retrieve cached metrics, order matches above
- fm.top = fmCache[fmCacheIndex * 4 + 0];
- fm.bottom = fmCache[fmCacheIndex * 4 + 1];
- fm.ascent = fmCache[fmCacheIndex * 4 + 2];
- fm.descent = fmCache[fmCacheIndex * 4 + 3];
- fmCacheIndex++;
-
- if (fm.top < fmTop) {
- fmTop = fm.top;
- }
- if (fm.ascent < fmAscent) {
- fmAscent = fm.ascent;
- }
- if (fm.descent > fmDescent) {
- fmDescent = fm.descent;
- }
- if (fm.bottom > fmBottom) {
- fmBottom = fm.bottom;
- }
-
- // skip breaks ending before current span range
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
- breakIndex++;
- }
-
- while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
- int endPos = paraStart + breaks[breakIndex];
-
- boolean moreChars = (endPos < bufEnd);
-
- final int ascent = isFallbackLineSpacing
- ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
- : fmAscent;
- final int descent = isFallbackLineSpacing
- ? Math.max(fmDescent, Math.round(descents[breakIndex]))
- : fmDescent;
-
- // The fallback ascent/descent may be larger than top/bottom of the default font
- // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected
- // clipping.
- if (isFallbackLineSpacing) {
- if (ascent < fmTop) {
- fmTop = ascent;
- }
- if (descent > fmBottom) {
- fmBottom = descent;
- }
- }
-
- v = out(source, here, endPos,
- ascent, descent, fmTop, fmBottom,
- v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
- hasTabs[breakIndex], hyphenEdits[breakIndex], needMultiply,
- measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs,
- paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
- paint, moreChars);
-
- if (endPos < spanEnd) {
- // preserve metrics for current span
- fmTop = Math.min(defaultTop, fm.top);
- fmBottom = Math.max(defaultBottom, fm.bottom);
- fmAscent = Math.min(defaultAscent, fm.ascent);
- fmDescent = Math.max(defaultDescent, fm.descent);
- } else {
- fmTop = fmBottom = fmAscent = fmDescent = 0;
- }
-
- here = endPos;
- breakIndex++;
-
- if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
- return;
- }
- }
- }
-
- if (paraEnd == bufEnd) {
- break;
}
}
@@ -1179,9 +1227,18 @@
(!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
ellipsize == TextUtils.TruncateAt.END);
if (doEllipsis) {
- calculateEllipsis(start, end, measured, widthStart,
- ellipsisWidth, ellipsize, j,
- textWidth, paint, forceEllipsis);
+ if (TRACE_LAYOUT) {
+ Trace.beginSection("StaticLayout#calculateEllipsis");
+ }
+ try {
+ calculateEllipsis(start, end, measured, widthStart,
+ ellipsisWidth, ellipsize, j,
+ textWidth, paint, forceEllipsis);
+ } finally {
+ if (TRACE_LAYOUT) {
+ Trace.endSection();
+ }
+ }
} else {
mLines[mColumns * j + ELLIPSIS_START] = 0;
mLines[mColumns * j + ELLIPSIS_COUNT] = 0;
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index bde9c77..a439478 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -28,6 +28,7 @@
import android.graphics.text.PositionedGlyphs;
import android.graphics.text.TextRunShaper;
import android.os.Build;
+import android.os.Trace;
import android.text.Layout.Directions;
import android.text.Layout.TabStops;
import android.text.style.CharacterStyle;
@@ -56,6 +57,8 @@
public class TextLine {
private static final boolean DEBUG = false;
+ private static final boolean TRACE_TEXTLINE = Build.isDebuggable();
+
private static final char TAB_CHAR = '\t';
private TextPaint mPaint;
@@ -430,28 +433,37 @@
* @param bottom the bottom of the line
*/
void draw(Canvas c, float x, int top, int y, int bottom) {
- float h = 0;
- final int runCount = mDirections.getRunCount();
- for (int runIndex = 0; runIndex < runCount; runIndex++) {
- final int runStart = mDirections.getRunStart(runIndex);
- if (runStart > mLen) break;
- final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
- final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ if (TRACE_TEXTLINE) {
+ Trace.beginSection("TextLine#draw");
+ }
+ try {
+ float h = 0;
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
+ final int runStart = mDirections.getRunStart(runIndex);
+ if (runStart > mLen) break;
+ final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+ final boolean runIsRtl = mDirections.isRunRtl(runIndex);
- final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
- int segStart = runStart;
- for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
- if (j == runLimit || charAt(j) == TAB_CHAR) {
- h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
- runIndex != (runCount - 1) || j != mLen, runFlag);
+ int segStart = runStart;
+ for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+ if (j == runLimit || charAt(j) == TAB_CHAR) {
+ h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
+ runIndex != (runCount - 1) || j != mLen, runFlag);
- if (j != runLimit) { // charAt(j) == TAB_CHAR
- h = mDir * nextTab(h * mDir);
+ if (j != runLimit) { // charAt(j) == TAB_CHAR
+ h = mDir * nextTab(h * mDir);
+ }
+ segStart = j + 1;
}
- segStart = j + 1;
}
}
+ } finally {
+ if (TRACE_TEXTLINE) {
+ Trace.endSection();
+ }
}
}
@@ -564,63 +576,76 @@
*/
public float measure(@IntRange(from = 0) int offset, boolean trailing,
@NonNull FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable LineInfo lineInfo) {
- if (offset > mLen) {
- throw new IndexOutOfBoundsException(
- "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+ if (TRACE_TEXTLINE) {
+ Trace.beginSection("TextLine#measure");
}
- if (lineInfo != null) {
- lineInfo.setClusterCount(0);
- }
- final int target = trailing ? offset - 1 : offset;
- if (target < 0) {
- return 0;
- }
+ try {
+ if (offset > mLen) {
+ throw new IndexOutOfBoundsException(
+ "offset(" + offset + ") should be less than line limit(" + mLen + ")");
+ }
+ if (lineInfo != null) {
+ lineInfo.setClusterCount(0);
+ }
+ final int target = trailing ? offset - 1 : offset;
+ if (target < 0) {
+ return 0;
+ }
- float h = 0;
- final int runCount = mDirections.getRunCount();
- for (int runIndex = 0; runIndex < runCount; runIndex++) {
- final int runStart = mDirections.getRunStart(runIndex);
- if (runStart > mLen) break;
- final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
- final boolean runIsRtl = mDirections.isRunRtl(runIndex);
- final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+ float h = 0;
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
+ final int runStart = mDirections.getRunStart(runIndex);
+ if (runStart > mLen) break;
+ final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
+ final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
- int segStart = runStart;
- for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
- if (j == runLimit || charAt(j) == TAB_CHAR) {
- final boolean targetIsInThisSegment = target >= segStart && target < j;
- final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+ int segStart = runStart;
+ for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
+ if (j == runLimit || charAt(j) == TAB_CHAR) {
+ final boolean targetIsInThisSegment = target >= segStart && target < j;
+ final boolean sameDirection =
+ (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
- if (targetIsInThisSegment && sameDirection) {
- return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null,
- 0, h, lineInfo, runFlag);
- }
-
- final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds,
- null, 0, h, lineInfo, runFlag);
- h += sameDirection ? segmentWidth : -segmentWidth;
-
- if (targetIsInThisSegment) {
- return h + measureRun(segStart, offset, j, runIsRtl, null, null, null, 0,
- h, lineInfo, runFlag);
- }
-
- if (j != runLimit) { // charAt(j) == TAB_CHAR
- if (offset == j) {
- return h;
+ if (targetIsInThisSegment && sameDirection) {
+ return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds,
+ null,
+ 0, h, lineInfo, runFlag);
}
- h = mDir * nextTab(h * mDir);
- if (target == j) {
- return h;
- }
- }
- segStart = j + 1;
+ final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi,
+ drawBounds,
+ null, 0, h, lineInfo, runFlag);
+ h += sameDirection ? segmentWidth : -segmentWidth;
+
+ if (targetIsInThisSegment) {
+ return h + measureRun(segStart, offset, j, runIsRtl, null, null, null,
+ 0,
+ h, lineInfo, runFlag);
+ }
+
+ if (j != runLimit) { // charAt(j) == TAB_CHAR
+ if (offset == j) {
+ return h;
+ }
+ h = mDir * nextTab(h * mDir);
+ if (target == j) {
+ return h;
+ }
+ }
+
+ segStart = j + 1;
+ }
}
}
- }
- return h;
+ return h;
+ } finally {
+ if (TRACE_TEXTLINE) {
+ Trace.endSection();
+ }
+ }
}
/**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f68fcab9..aff1d4a 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -119,3 +119,10 @@
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 a6724da..a4c3ed9 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -24,6 +24,7 @@
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.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 @@
*/
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 @@
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 66655fc..29c8350 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 @@
}
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 @@
} 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/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index e215950..614df7c 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -142,7 +142,7 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.STATUS_BAR_SERVICE,android.Manifest.permission.MANAGE_ACCESSIBILITY})")
oneway void notifyQuickSettingsTilesChanged(int userId, in List<ComponentName> tileComponentNames);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 1acfc1b..644a7a9 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -233,6 +233,28 @@
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 @@
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 1484bfb..e15baae 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -974,7 +974,7 @@
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 14c5348..d12eda3 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1203,7 +1203,11 @@
* 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 @@
*
* @return {@code true} if the database storage API is enabled
* @see #setDatabaseEnabled
+ * @deprecated WebSQL is deprecated and this method will become a no-op on all
+ * Android versions once support is removed in Chromium. See
+ * https://developer.chrome.com/blog/deprecating-web-sql for more information.
*/
+ @Deprecated
public abstract boolean getDatabaseEnabled();
/**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 139ebc3..d40eeda 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2112,6 +2112,17 @@
}
}
+ boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
+ && canvas.isHighContrastTextEnabled();
+
+ // If high contrast text is drawing background rectangles behind the text, those cover up
+ // the cursor and correction highlighter etc. So just draw the text first, then draw the
+ // others on top of the text. If high contrast text isn't enabled: draw text last, as usual.
+ if (shouldDrawHighlightsOnTop) {
+ drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+ }
+
if (mCorrectionHighlighter != null) {
mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
}
@@ -2136,9 +2147,19 @@
mInsertModeController.onDraw(canvas);
}
+ if (!shouldDrawHighlightsOnTop) {
+ drawLayout(canvas, layout, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, shouldDrawHighlightsOnTop);
+ }
+ }
+
+ private void drawLayout(Canvas canvas, Layout layout, List<Path> highlightPaths,
+ List<Paint> highlightPaints, Path selectionHighlight, Paint selectionHighlightPaint,
+ int cursorOffsetVertical, boolean shouldDrawHighlightsOnTop) {
if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints,
- selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
+ selectionHighlight, selectionHighlightPaint, cursorOffsetVertical,
+ shouldDrawHighlightsOnTop);
} else {
layout.draw(canvas, highlightPaths, highlightPaints, selectionHighlight,
selectionHighlightPaint, cursorOffsetVertical);
@@ -2147,14 +2168,13 @@
private void drawHardwareAccelerated(Canvas canvas, Layout layout,
List<Path> highlightPaths, List<Paint> highlightPaints,
- Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical) {
+ Path selectionHighlight, Paint selectionHighlightPaint, int cursorOffsetVertical,
+ boolean shouldDrawHighlightsOnTop) {
final long lineRange = layout.getLineRangeForDraw(canvas);
int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
if (lastLine < 0) return;
- boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
- && canvas.isHighContrastTextEnabled();
if (!shouldDrawHighlightsOnTop) {
layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight,
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index a2d8d80..e3caf70 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -5127,7 +5127,10 @@
* @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 1721462..0373539 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -15490,8 +15490,9 @@
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/InputTransferToken.java b/core/java/android/window/InputTransferToken.java
index e572853..c62eee4 100644
--- a/core/java/android/window/InputTransferToken.java
+++ b/core/java/android/window/InputTransferToken.java
@@ -18,7 +18,6 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
@@ -30,6 +29,8 @@
import com.android.window.flags.Flags;
+import libcore.util.NativeAllocationRegistry;
+
import java.util.Objects;
/**
@@ -51,28 +52,51 @@
*/
@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
public final class InputTransferToken implements Parcelable {
+ private static native long nativeCreate();
+ private static native long nativeCreate(IBinder token);
+ private static native void nativeWriteToParcel(long nativeObject, Parcel out);
+ private static native long nativeReadFromParcel(Parcel in);
+ private static native IBinder nativeGetBinderToken(long nativeObject);
+ private static native long nativeGetNativeInputTransferTokenFinalizer();
+ private static native boolean nativeEquals(long nativeObject1, long nativeObject2);
+
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(InputTransferToken.class.getClassLoader(),
+ nativeGetNativeInputTransferTokenFinalizer());
+
/**
* @hide
*/
- @NonNull
- public final IBinder mToken;
+ public final long mNativeObject;
+
+ private InputTransferToken(long nativeObject) {
+ mNativeObject = nativeObject;
+ sRegistry.registerNativeAllocation(this, nativeObject);
+ }
/**
* @hide
*/
public InputTransferToken(@NonNull IBinder token) {
- mToken = token;
+ this(nativeCreate(token));
}
/**
* @hide
*/
public InputTransferToken() {
- mToken = new Binder();
+ this(nativeCreate());
+ }
+
+ /**
+ * @hide
+ */
+ public IBinder getToken() {
+ return nativeGetBinderToken(mNativeObject);
}
private InputTransferToken(Parcel in) {
- mToken = in.readStrongBinder();
+ this(nativeReadFromParcel(in));
}
/**
@@ -88,7 +112,7 @@
*/
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeStrongBinder(mToken);
+ nativeWriteToParcel(mNativeObject, dest);
}
public static final @NonNull Creator<InputTransferToken> CREATOR = new Creator<>() {
@@ -101,13 +125,12 @@
}
};
-
/**
* @hide
*/
@Override
public int hashCode() {
- return Objects.hash(mToken);
+ return Objects.hash(getToken());
}
/**
@@ -118,7 +141,8 @@
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
InputTransferToken other = (InputTransferToken) obj;
- return other.mToken == mToken;
+ if (other.mNativeObject == mNativeObject) return true;
+ return nativeEquals(mNativeObject, other.mNativeObject);
}
}
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 6999e5b..50f5191 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -59,28 +59,35 @@
*/
public boolean playRevealAnimation;
- /** The mode is no need to defer removing the starting window for IME */
- public static final int DEFER_MODE_NONE = 0;
+ /** The mode is default defer removing the snapshot starting window. */
+ public static final int DEFER_MODE_DEFAULT = 0;
- /** The mode to defer removing the starting window until IME has drawn */
+ /** The mode to defer removing the snapshot starting window until IME has drawn. */
public static final int DEFER_MODE_NORMAL = 1;
- /** The mode to defer the starting window removal until IME drawn and finished the rotation */
+ /**
+ * The mode to defer the snapshot starting window removal until IME drawn and finished the
+ * rotation.
+ */
public static final int DEFER_MODE_ROTATION = 2;
+ /** The mode is no need to defer removing the snapshot starting window. */
+ public static final int DEFER_MODE_NONE = 3;
+
@IntDef(prefix = { "DEFER_MODE_" }, value = {
- DEFER_MODE_NONE,
+ DEFER_MODE_DEFAULT,
DEFER_MODE_NORMAL,
DEFER_MODE_ROTATION,
+ DEFER_MODE_NONE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeferMode {}
/**
- * Whether need to defer removing the starting window for IME.
+ * Whether need to defer removing the snapshot starting window.
* @hide
*/
- public @DeferMode int deferRemoveForImeMode;
+ public @DeferMode int deferRemoveMode;
/**
* The rounded corner radius
@@ -116,7 +123,7 @@
windowAnimationLeash = source.readTypedObject(SurfaceControl.CREATOR);
mainFrame = source.readTypedObject(Rect.CREATOR);
playRevealAnimation = source.readBoolean();
- deferRemoveForImeMode = source.readInt();
+ deferRemoveMode = source.readInt();
roundedCornerRadius = source.readFloat();
windowlessSurface = source.readBoolean();
removeImmediately = source.readBoolean();
@@ -128,7 +135,7 @@
dest.writeTypedObject(windowAnimationLeash, flags);
dest.writeTypedObject(mainFrame, flags);
dest.writeBoolean(playRevealAnimation);
- dest.writeInt(deferRemoveForImeMode);
+ dest.writeInt(deferRemoveMode);
dest.writeFloat(roundedCornerRadius);
dest.writeBoolean(windowlessSurface);
dest.writeBoolean(removeImmediately);
@@ -140,7 +147,7 @@
+ " frame=" + mainFrame
+ " playRevealAnimation=" + playRevealAnimation
+ " roundedCornerRadius=" + roundedCornerRadius
- + " deferRemoveForImeMode=" + deferRemoveForImeMode
+ + " deferRemoveMode=" + deferRemoveMode
+ " windowlessSurface=" + windowlessSurface
+ " removeImmediately=" + removeImmediately + "}";
}
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 7b8cdff..7e77f15 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -24,6 +24,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.SurfaceControl;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -111,7 +112,8 @@
/**
* Creates a decor surface in the parent Task of the TaskFragment. The created decor surface
* will be provided in {@link TaskFragmentTransaction#TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED}
- * event callback.
+ * event callback. The decor surface can be used to draw the divider between TaskFragments or
+ * other decorations.
*/
public static final int OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE = 14;
@@ -135,6 +137,15 @@
*/
public static final int OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH = 17;
+ /**
+ * Sets whether the decor surface will be boosted. When not boosted, the decor surface is placed
+ * below any TaskFragments in untrusted mode or any activities with uid different from the
+ * TaskFragmentOrganizer uid and just above its owner TaskFragment; when boosted, the decor
+ * surface is placed above all the non-boosted windows in the Task, the content of these
+ * non-boosted windows will be hidden and inputs are disabled.
+ */
+ public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -155,6 +166,7 @@
OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
OP_TYPE_SET_DIM_ON_TASK,
OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
+ OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
@@ -186,12 +198,18 @@
private final boolean mMoveToBottomIfClearWhenLaunch;
+ private final boolean mBooleanValue;
+
+ @Nullable
+ private final SurfaceControl.Transaction mSurfaceTransaction;
+
private TaskFragmentOperation(@OperationType int opType,
@Nullable TaskFragmentCreationParams taskFragmentCreationParams,
@Nullable IBinder activityToken, @Nullable Intent activityIntent,
@Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
@Nullable TaskFragmentAnimationParams animationParams,
- boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch) {
+ boolean isolatedNav, boolean dimOnTask, boolean moveToBottomIfClearWhenLaunch,
+ boolean booleanValue, @Nullable SurfaceControl.Transaction surfaceTransaction) {
mOpType = opType;
mTaskFragmentCreationParams = taskFragmentCreationParams;
mActivityToken = activityToken;
@@ -202,6 +220,8 @@
mIsolatedNav = isolatedNav;
mDimOnTask = dimOnTask;
mMoveToBottomIfClearWhenLaunch = moveToBottomIfClearWhenLaunch;
+ mBooleanValue = booleanValue;
+ mSurfaceTransaction = surfaceTransaction;
}
private TaskFragmentOperation(Parcel in) {
@@ -215,6 +235,8 @@
mIsolatedNav = in.readBoolean();
mDimOnTask = in.readBoolean();
mMoveToBottomIfClearWhenLaunch = in.readBoolean();
+ mBooleanValue = in.readBoolean();
+ mSurfaceTransaction = in.readTypedObject(SurfaceControl.Transaction.CREATOR);
}
@Override
@@ -229,6 +251,8 @@
dest.writeBoolean(mIsolatedNav);
dest.writeBoolean(mDimOnTask);
dest.writeBoolean(mMoveToBottomIfClearWhenLaunch);
+ dest.writeBoolean(mBooleanValue);
+ dest.writeTypedObject(mSurfaceTransaction, flags);
}
@NonNull
@@ -324,6 +348,22 @@
return mMoveToBottomIfClearWhenLaunch;
}
+ /** Returns the boolean value for this operation. */
+ public boolean getBooleanValue() {
+ return mBooleanValue;
+ }
+
+ /**
+ * Returns {@link SurfaceControl.Transaction} associated with this operation. Currently, this is
+ * only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to specify a
+ * {@link SurfaceControl.Transaction} that should be applied together with the transaction to
+ * change the decor surface layers.
+ */
+ @Nullable
+ public SurfaceControl.Transaction getSurfaceTransaction() {
+ return mSurfaceTransaction;
+ }
+
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -349,6 +389,10 @@
sb.append(", isolatedNav=").append(mIsolatedNav);
sb.append(", dimOnTask=").append(mDimOnTask);
sb.append(", moveToBottomIfClearWhenLaunch=").append(mMoveToBottomIfClearWhenLaunch);
+ sb.append(", booleanValue=").append(mBooleanValue);
+ if (mSurfaceTransaction != null) {
+ sb.append(", surfaceTransaction=").append(mSurfaceTransaction);
+ }
sb.append('}');
return sb.toString();
@@ -358,7 +402,7 @@
public int hashCode() {
return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask,
- mMoveToBottomIfClearWhenLaunch);
+ mMoveToBottomIfClearWhenLaunch, mBooleanValue, mSurfaceTransaction);
}
@Override
@@ -376,7 +420,9 @@
&& Objects.equals(mAnimationParams, other.mAnimationParams)
&& mIsolatedNav == other.mIsolatedNav
&& mDimOnTask == other.mDimOnTask
- && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch;
+ && mMoveToBottomIfClearWhenLaunch == other.mMoveToBottomIfClearWhenLaunch
+ && mBooleanValue == other.mBooleanValue
+ && Objects.equals(mSurfaceTransaction, other.mSurfaceTransaction);
}
@Override
@@ -414,6 +460,11 @@
private boolean mMoveToBottomIfClearWhenLaunch;
+ private boolean mBooleanValue;
+
+ @Nullable
+ private SurfaceControl.Transaction mSurfaceTransaction;
+
/**
* @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
*/
@@ -505,13 +556,37 @@
}
/**
+ * Sets the boolean value for this operation.
+ * TODO(b/327338038) migrate other boolean values to use shared mBooleanValue
+ */
+ @NonNull
+ public Builder setBooleanValue(boolean booleanValue) {
+ mBooleanValue = booleanValue;
+ return this;
+ }
+
+ /**
+ * Sets {@link SurfaceControl.Transaction} associated with this operation. Currently, this
+ * is only used by {@link TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED} to
+ * specify a {@link SurfaceControl.Transaction} that should be applied together with the
+ * transaction to change the decor surface layers.
+ */
+ @NonNull
+ public Builder setSurfaceTransaction(
+ @Nullable SurfaceControl.Transaction surfaceTransaction) {
+ mSurfaceTransaction = surfaceTransaction;
+ return this;
+ }
+
+ /**
* Constructs the {@link TaskFragmentOperation}.
*/
@NonNull
public TaskFragmentOperation build() {
return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
- mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch);
+ mIsolatedNav, mDimOnTask, mMoveToBottomIfClearWhenLaunch, mBooleanValue,
+ mSurfaceTransaction);
}
}
}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 63a2474..ed1d434 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -14,3 +14,10 @@
description: "Enables desktop windowing"
bug: "304778354"
}
+
+flag {
+ name: "enable_desktop_windowing_modals_policy"
+ namespace: "lse_desktop_experience"
+ description: "Enables policy for modals activities"
+ bug: "319492844"
+}
diff --git a/core/java/com/android/internal/accessibility/common/MagnificationConstants.java b/core/java/com/android/internal/accessibility/common/MagnificationConstants.java
index 2c49303..2db3e65 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 @@
/** 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/jni/Android.bp b/core/jni/Android.bp
index a0dc94f..ac961ee 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -266,6 +266,7 @@
"fd_utils.cpp",
"android_hardware_input_InputWindowHandle.cpp",
"android_hardware_input_InputApplicationHandle.cpp",
+ "android_window_InputTransferToken.cpp",
"android_window_WindowInfosListener.cpp",
"android_window_ScreenCapture.cpp",
"jni_common.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aa63f4f..9bbd191 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -221,6 +221,7 @@
extern int register_android_tracing_PerfettoDataSource(JNIEnv* env);
extern int register_android_tracing_PerfettoDataSourceInstance(JNIEnv* env);
extern int register_android_tracing_PerfettoProducer(JNIEnv* env);
+extern int register_android_window_InputTransferToken(JNIEnv* env);
// Namespace for Android Runtime flags applied during boot time.
static const char* RUNTIME_NATIVE_BOOT_NAMESPACE = "runtime_native_boot";
@@ -1678,6 +1679,7 @@
REG_JNI(register_android_tracing_PerfettoDataSource),
REG_JNI(register_android_tracing_PerfettoDataSourceInstance),
REG_JNI(register_android_tracing_PerfettoProducer),
+ REG_JNI(register_android_window_InputTransferToken),
};
/*
diff --git a/core/jni/android_window_InputTransferToken.cpp b/core/jni/android_window_InputTransferToken.cpp
new file mode 100644
index 0000000..60568e30
--- /dev/null
+++ b/core/jni/android_window_InputTransferToken.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "InputTransferToken"
+
+#include <android_runtime/android_window_InputTransferToken.h>
+#include <gui/InputTransferToken.h>
+#include <nativehelper/JNIHelp.h>
+
+#include "android_os_Parcel.h"
+#include "android_util_Binder.h"
+#include "core_jni_helpers.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+ jfieldID mNativeObject;
+ jmethodID ctor;
+} gInputTransferTokenClassInfo;
+
+static jlong nativeCreate(JNIEnv* env, jclass clazz) {
+ sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make();
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static jlong nativeCreateFromBinder(JNIEnv* env, jclass clazz, jobject tokenBinderObj) {
+ if (tokenBinderObj == nullptr) {
+ return 0;
+ }
+ sp<IBinder> token(ibinderForJavaObject(env, tokenBinderObj));
+ if (token == nullptr) {
+ return 0;
+ }
+ sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make(token);
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static void nativeWriteToParcel(JNIEnv* env, jclass clazz, jlong nativeObj, jobject parcelObj) {
+ InputTransferToken* inputTransferToken = reinterpret_cast<InputTransferToken*>(nativeObj);
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+ inputTransferToken->writeToParcel(parcel);
+}
+
+static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
+ sp<InputTransferToken> inputTransferToken = sp<InputTransferToken>::make();
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+ inputTransferToken->readFromParcel(parcel);
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(inputTransferToken.get());
+}
+
+static jobject nativeGetBinderToken(JNIEnv* env, jclass clazz, jlong nativeObj) {
+ sp<InputTransferToken> inputTransferToken = reinterpret_cast<InputTransferToken*>(nativeObj);
+ return javaObjectForIBinder(env, inputTransferToken->mToken);
+}
+
+InputTransferToken* android_window_InputTransferToken_getNativeInputTransferToken(
+ JNIEnv* env, jobject inputTransferTokenObj) {
+ if (inputTransferTokenObj != nullptr &&
+ env->IsInstanceOf(inputTransferTokenObj, gInputTransferTokenClassInfo.clazz)) {
+ return reinterpret_cast<InputTransferToken*>(
+ env->GetLongField(inputTransferTokenObj,
+ gInputTransferTokenClassInfo.mNativeObject));
+ } else {
+ return nullptr;
+ }
+}
+
+jobject android_window_InputTransferToken_getJavaInputTransferToken(
+ JNIEnv* env, const InputTransferToken* inputTransferToken) {
+ if (inputTransferToken == nullptr || env == nullptr) {
+ return nullptr;
+ }
+
+ inputTransferToken->incStrong((void*)nativeCreate);
+ return env->NewObject(gInputTransferTokenClassInfo.clazz, gInputTransferTokenClassInfo.ctor,
+ reinterpret_cast<jlong>(inputTransferToken));
+}
+
+static void release(InputTransferToken* inputTransferToken) {
+ inputTransferToken->decStrong((void*)nativeCreate);
+}
+
+static jlong nativeGetNativeInputTransferTokenFinalizer(JNIEnv* env, jclass clazz) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&release));
+}
+
+static bool nativeEquals(JNIEnv* env, jclass clazz, jlong inputTransferTokenObj1,
+ jlong inputTransferTokenObj2) {
+ sp<InputTransferToken> inputTransferToken1(
+ reinterpret_cast<InputTransferToken*>(inputTransferTokenObj1));
+ sp<InputTransferToken> inputTransferToken2(
+ reinterpret_cast<InputTransferToken*>(inputTransferTokenObj2));
+
+ return inputTransferToken1 == inputTransferToken2;
+}
+
+static const JNINativeMethod sInputTransferTokenMethods[] = {
+ // clang-format off
+ {"nativeCreate", "()J", (void*)nativeCreate},
+ {"nativeCreate", "(Landroid/os/IBinder;)J", (void*)nativeCreateFromBinder},
+ {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+ {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel},
+ {"nativeGetBinderToken", "(J)Landroid/os/IBinder;", (void*)nativeGetBinderToken},
+ {"nativeGetNativeInputTransferTokenFinalizer", "()J", (void*)nativeGetNativeInputTransferTokenFinalizer},
+ {"nativeEquals", "(JJ)Z", (void*) nativeEquals},
+ // clang-format on
+};
+
+int register_android_window_InputTransferToken(JNIEnv* env) {
+ int err = RegisterMethodsOrDie(env, "android/window/InputTransferToken",
+ sInputTransferTokenMethods, NELEM(sInputTransferTokenMethods));
+ jclass inputTransferTokenClass = FindClassOrDie(env, "android/window/InputTransferToken");
+ gInputTransferTokenClassInfo.clazz = MakeGlobalRefOrDie(env, inputTransferTokenClass);
+ gInputTransferTokenClassInfo.mNativeObject =
+ GetFieldIDOrDie(env, gInputTransferTokenClassInfo.clazz, "mNativeObject", "J");
+ gInputTransferTokenClassInfo.ctor =
+ GetMethodIDOrDie(env, gInputTransferTokenClassInfo.clazz, "<init>", "(J)V");
+ return err;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/include/android_runtime/android_window_InputTransferToken.h b/core/jni/include/android_runtime/android_window_InputTransferToken.h
new file mode 100644
index 0000000..75dbe37
--- /dev/null
+++ b/core/jni/include/android_runtime/android_window_InputTransferToken.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
+#define _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
+
+#include <gui/InputTransferToken.h>
+#include <jni.h>
+
+namespace android {
+
+extern InputTransferToken* android_window_InputTransferToken_getNativeInputTransferToken(
+ JNIEnv* env, jobject inputTransferTokenObj);
+
+extern jobject android_window_InputTransferToken_getJavaInputTransferToken(
+ JNIEnv* env, const InputTransferToken* inputTransferToken);
+
+} // namespace android
+
+#endif // _ANDROID_WINDOW_INPUTTRANSFERTOKEN_H
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1acdc75..8ea742d 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 6e804c0..9ad577a 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 @@
<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 d89f236..5e900f7 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 b60b806f..a5c9624 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.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 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 @@
// 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 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 @@
// 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 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 @@
// Handwriting is started for this view since the stylus down point is closest to this
// view.
verify(mHandwritingInitiator).startHandwriting(mTestView1);
- // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
- // sets the cursor position.
- verify(mTestView1).setSelection(4);
+ if (handwritingCursorPosition()) {
+ // Cursor is placed at the end of the text.
+ verify(mTestView1).setSelection(5);
+ } else {
+ // Since the stylus down point was outside the TextView's bounds, the handwriting
+ // initiator sets the cursor position.
+ verify(mTestView1).setSelection(4);
+ }
}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 245f216..701d145 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -12,3 +12,4 @@
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 0f12438..749f0e1 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -553,6 +553,8 @@
<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/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 0231d3a..1aa8af5 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2665,12 +2665,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/Task.java"
},
- "4446998544419008924": {
- "message": "Moving to RESUMED: %s (starting new instance) callers=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"4037728373502324767": {
"message": "resumeNextFocusableActivityWhenRootTaskIsEmpty: %s, go home",
"level": "DEBUG",
@@ -2767,12 +2761,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
- "2088177629189452176": {
- "message": "Activity config changed during resume: %s, new next: %s",
- "level": "INFO",
- "group": "WM_DEBUG_STATES",
- "at": "com\/android\/server\/wm\/TaskFragment.java"
- },
"-8483536760290526299": {
"message": "resumeTopActivity: Resumed %s",
"level": "DEBUG",
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 9585842..4455a3c 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 @@
* Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
* exists for this entry, and it is able to bubble, a new bubble will be created.
*
- * This is the method to use when opening a bubble via a notification or in a state where
+ * <p>This is the method to use when opening a bubble via a notification or in a state where
* the device might not be unlocked.
*
* @param entry the entry to use for the bubble.
*/
public void expandStackAndSelectBubble(BubbleEntry entry) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "opening bubble from notification key=%s mIsStatusBarShade=%b",
+ entry.getKey(), mIsStatusBarShade);
if (mIsStatusBarShade) {
mNotifEntryToExpandOnShadeUnlock = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 23bdd08..6524c96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -449,17 +449,21 @@
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
- @NonNull MagnetizedObject draggedObject) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ @NonNull MagnetizedObject<?> draggedObject) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
animateDismissBubble(view, true);
}
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
- @NonNull MagnetizedObject draggedObject,
+ @NonNull MagnetizedObject<?> draggedObject,
float velX, float velY, boolean wasFlungOut) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
animateDismissBubble(view, false);
if (wasFlungOut) {
@@ -474,7 +478,9 @@
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
@NonNull MagnetizedObject<?> draggedObject) {
- if (draggedObject.getUnderlyingObject() instanceof View view) {
+ Object underlyingObject = draggedObject.getUnderlyingObject();
+ if (underlyingObject instanceof View) {
+ View view = (View) underlyingObject;
mExpandedAnimationController.dismissDraggedOutBubble(
view /* bubble */,
mDismissView.getHeight() /* translationYBy */,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index fb0ed15..6a3c8d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -138,9 +138,9 @@
@WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
final Region region = new Region();
int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
- ? 2 * layout.stableInsets().top
- : mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
+ ? mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+ : 2 * layout.stableInsets().top;
// A thin, short Rect at the top of the screen.
if (windowingMode == WINDOWING_MODE_FREEFORM) {
int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index dd8c1a0..4a3130f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -78,6 +78,7 @@
import com.android.wm.shell.transition.OneShotRemoteHandler
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
+import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.io.PrintWriter
@@ -960,7 +961,8 @@
}
/**
- * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area.
+ * Perform checks required on drag end. If indicator indicates a windowing mode change, perform
+ * that change. Otherwise, ensure bounds are up to date.
*
* @param taskInfo the task being dragged.
* @param position position of surface when drag ends.
@@ -971,7 +973,8 @@
taskInfo: RunningTaskInfo,
position: Point,
inputCoordinate: PointF,
- taskBounds: Rect
+ taskBounds: Rect,
+ validDragArea: Rect
) {
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
@@ -994,10 +997,21 @@
releaseVisualIndicator()
snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
}
- DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
+ // If task bounds are outside valid drag area, snap them inward and perform a
+ // transaction to set bounds.
+ if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
+ taskBounds, validDragArea)) {
+ val wct = WindowContainerTransaction()
+ wct.setBounds(taskInfo.token, taskBounds)
+ transitions.startTransition(TRANSIT_CHANGE, wct, null)
+ }
releaseVisualIndicator()
}
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+ throw IllegalArgumentException("Should not be receiving TO_DESKTOP_INDICATOR for " +
+ "a freeform task.")
+ }
}
// A freeform drag-move ended, remove the indicator immediately.
releaseVisualIndicator()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 952e2d4..86c8f04 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 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 7f16c5e..af11ebc 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 @@
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 @@
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 @@
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 ec907fd..36368df9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1451,6 +1451,7 @@
mExitSplitScreenOnHide = exitSplitScreenOnHide;
}
+ /** Exits split screen with legacy transition */
void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: topTaskId=%d reason=%s active=%b",
toTopTaskId, exitReasonToString(exitReason), mMainStage.isActive());
@@ -1470,6 +1471,7 @@
applyExitSplitScreen(childrenToTop, wct, exitReason);
}
+ /** Exits split screen with legacy transition */
private void exitSplitScreen(@Nullable StageTaskListener childrenToTop,
@ExitReason int exitReason) {
ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "exitSplitScreen: mainStageToTop=%b reason=%s active=%b",
@@ -1547,6 +1549,14 @@
}
}
+ void dismissSplitScreen(int toTopTaskId, @ExitReason int exitReason) {
+ if (!mMainStage.isActive()) return;
+ final int stage = getStageOfTask(toTopTaskId);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ prepareExitSplitScreen(stage, wct);
+ mSplitTransitions.startDismissTransition(wct, this, stage, exitReason);
+ }
+
/**
* Overridden by child classes.
*/
@@ -1612,6 +1622,8 @@
// User has used a keyboard shortcut to go back to fullscreen from split
case EXIT_REASON_DESKTOP_MODE:
// One of the children enters desktop mode
+ case EXIT_REASON_UNKNOWN:
+ // Unknown reason
return true;
default:
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 1ce87ef..4465aef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NONE;
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_NORMAL;
import static android.window.StartingWindowRemovalInfo.DEFER_MODE_ROTATION;
@@ -270,21 +271,18 @@
@Override
public final boolean removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
- if (immediately) {
+ if (immediately
+ // Show the latest content as soon as possible for unlocking to home.
+ || mActivityType == ACTIVITY_TYPE_HOME
+ || info.deferRemoveMode == DEFER_MODE_NONE) {
removeImmediately();
- } else {
- scheduleRemove(info.deferRemoveForImeMode);
- return false;
+ return true;
}
- return true;
+ scheduleRemove(info.deferRemoveMode);
+ return false;
}
void scheduleRemove(@StartingWindowRemovalInfo.DeferMode int deferRemoveForImeMode) {
- // Show the latest content as soon as possible for unlocking to home.
- if (mActivityType == ACTIVITY_TYPE_HOME) {
- removeImmediately();
- return;
- }
mRemoveExecutor.removeCallbacks(mScheduledRunnable);
final long delayRemovalTime;
switch (deferRemoveForImeMode) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
index 7a50814..564e716 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt
@@ -31,42 +31,42 @@
companion object {
/** @see [com.android.internal.protolog.common.ProtoLog.d] */
fun d(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.d(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.v] */
fun v(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.v(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.i] */
fun i(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.i(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.w] */
fun w(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.w(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.e] */
fun e(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.e(group.tag, String.format(messageString, *args))
}
}
/** @see [com.android.internal.protolog.common.ProtoLog.wtf] */
fun wtf(group: IProtoLogGroup, messageString: String, vararg args: Any) {
- if (ProtoLog.isEnabled(group)) {
+ if (group.isLogToLogcat) {
Log.wtf(group.tag, String.format(messageString, *args))
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index b2eeea7..c59a1b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -19,9 +19,11 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
+import android.graphics.Rect;
import android.os.Handler;
import android.util.SparseArray;
import android.view.Choreographer;
@@ -186,7 +188,7 @@
final FluidResizeTaskPositioner taskPositioner =
new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration,
- mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */);
+ mDisplayController);
final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -286,8 +288,15 @@
mDragPointerId = e.getPointerId(0);
}
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDragPositioningCallback.onDragPositioningEnd(
+ final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(newTaskBounds,
+ mWindowDecorByTaskId.get(mTaskId).calculateValidDragArea());
+ if (newTaskBounds != taskInfo.configuration.windowConfiguration.getBounds()) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(taskInfo.token, newTaskBounds);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
+ }
final boolean wasDragging = mIsDragging;
mIsDragging = false;
return wasDragging;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 91e9601..9a48922 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -87,6 +88,7 @@
}
@Override
+ @NonNull
Rect calculateValidDragArea() {
final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
R.dimen.caption_left_buttons_width);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 98ff0ee..918cefb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -639,7 +639,7 @@
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds);
+ newTaskBounds, decoration.calculateValidDragArea());
if (touchingButton && !mHasLongClicked) {
// We need the input event to not be consumed here to end the ripple
// effect on the touched button. We will reset drag state in the ensuing
@@ -867,8 +867,9 @@
}
if (mTransitionDragActive) {
// Do not create an indicator at all if we're not past transition height.
- if (ev.getRawY() < mContext.getResources().getDimensionPixelSize(com.android
- .wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height)
+ DisplayLayout layout = mDisplayController
+ .getDisplayLayout(relevantDecor.mTaskInfo.displayId);
+ if (ev.getRawY() < 2 * layout.stableInsets().top
&& mMoveToDesktopAnimator == null) {
return;
}
@@ -1086,18 +1087,16 @@
windowDecoration.createResizeVeil();
final DragPositioningCallback dragPositioningCallback;
- final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_from_desktop_height);
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
dragPositioningCallback = new FluidResizeTaskPositioner(
mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
- mDragStartListener, mTransactionFactory, transitionAreaHeight);
+ mDragStartListener, mTransactionFactory);
windowDecoration.setTaskDragResizer(
(FluidResizeTaskPositioner) dragPositioningCallback);
} else {
dragPositioningCallback = new VeiledResizeTaskPositioner(
mTaskOrganizer, windowDecoration, mDisplayController,
- mDragStartListener, mTransitions, transitionAreaHeight);
+ mDragStartListener, mTransitions);
windowDecoration.setTaskDragResizer(
(VeiledResizeTaskPositioner) dragPositioningCallback);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index c9669a7..4c9e171 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -499,6 +500,7 @@
* Determine valid drag area for this task based on elements in the app chip.
*/
@Override
+ @NonNull
Rect calculateValidDragArea() {
final int appTextWidth = ((DesktopModeAppControlsWindowDecorationViewHolder)
mWindowDecorViewHolder).getAppNameTextWidth();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 5afbd54..82c399a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -131,7 +131,7 @@
t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top);
}
- private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
+ static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
PointF repositionStartPoint, float x, float y) {
final float deltaX = x - repositionStartPoint.x;
final float deltaY = y - repositionStartPoint.y;
@@ -140,49 +140,32 @@
}
/**
- * Calculates the new position of the top edge of the task and returns true if it is below the
- * disallowed area.
- *
- * @param disallowedAreaForEndBoundsHeight the height of the area that where the task positioner
- * should not finalize the bounds using WCT#setBounds
- * @param taskBoundsAtDragStart the bounds of the task on the first drag input event
- * @param repositionStartPoint initial input coordinate
- * @param y the y position of the motion event
- * @return true if the top of the task is below the disallowed area
+ * If task bounds are outside of provided drag area, snap the bounds to be just inside the
+ * drag area.
+ * @param repositionTaskBounds bounds determined by task positioner
+ * @param validDragArea the area that task must be positioned inside
+ * @return whether bounds were modified
*/
- static boolean isBelowDisallowedArea(int disallowedAreaForEndBoundsHeight,
- Rect taskBoundsAtDragStart, PointF repositionStartPoint, float y) {
- final float deltaY = y - repositionStartPoint.y;
- final float topPosition = taskBoundsAtDragStart.top + deltaY;
- return topPosition > disallowedAreaForEndBoundsHeight;
- }
-
- /**
- * Updates repositionTaskBounds to the final bounds of the task after the drag is finished. If
- * the bounds are outside of the valid drag area, the task is shifted back onto the edge of the
- * valid drag area.
- */
- static void onDragEnd(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
- PointF repositionStartPoint, float x, float y, Rect validDragArea) {
- updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint,
- x, y);
- snapTaskBoundsIfNecessary(repositionTaskBounds, validDragArea);
- }
-
- private static void snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
+ public static boolean snapTaskBoundsIfNecessary(Rect repositionTaskBounds, Rect validDragArea) {
// If we were never supplied a valid drag area, do not restrict movement.
// Otherwise, we restrict deltas to keep task position inside the Rect.
- if (validDragArea.width() == 0) return;
+ if (validDragArea.width() == 0) return false;
+ boolean result = false;
if (repositionTaskBounds.left < validDragArea.left) {
repositionTaskBounds.offset(validDragArea.left - repositionTaskBounds.left, 0);
+ result = true;
} else if (repositionTaskBounds.left > validDragArea.right) {
repositionTaskBounds.offset(validDragArea.right - repositionTaskBounds.left, 0);
+ result = true;
}
if (repositionTaskBounds.top < validDragArea.top) {
repositionTaskBounds.offset(0, validDragArea.top - repositionTaskBounds.top);
+ result = true;
} else if (repositionTaskBounds.top > validDragArea.bottom) {
repositionTaskBounds.offset(0, validDragArea.bottom - repositionTaskBounds.top);
+ result = true;
}
+ return result;
}
private static float getMinWidth(DisplayController displayController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 6bfc7cd..6f8b3d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -60,9 +60,6 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes with the positions y less than this value, do not
- // finalize the bounds there using WCT#setBounds
- private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
private boolean mIsResizingOrAnimatingResize;
private int mCtrlType;
@@ -70,11 +67,9 @@
@Surface.Rotation private int mRotation;
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, Transitions transitions,
- WindowDecoration windowDecoration, DisplayController displayController,
- int disallowedAreaForEndBoundsHeight) {
+ WindowDecoration windowDecoration, DisplayController displayController) {
this(taskOrganizer, transitions, windowDecoration, displayController,
- dragStartListener -> {}, SurfaceControl.Transaction::new,
- disallowedAreaForEndBoundsHeight);
+ dragStartListener -> {}, SurfaceControl.Transaction::new);
}
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
@@ -82,15 +77,13 @@
WindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier,
- int disallowedAreaForEndBoundsHeight) {
+ Supplier<SurfaceControl.Transaction> supplier) {
mTaskOrganizer = taskOrganizer;
mTransitions = transitions;
mWindowDecoration = windowDecoration;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
- mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -157,14 +150,10 @@
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
}
mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else if (mCtrlType == CTRL_TYPE_UNDEFINED
- && DragPositioningCallbackUtility.isBelowDisallowedArea(
- mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
- y)) {
+ } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
- mWindowDecoration.calculateValidDragArea());
+ DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 5c69d55..c12a93e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -54,9 +54,6 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
- // If a task move (not resize) finishes with the positions y less than this value, do not
- // finalize the bounds there using WCT#setBounds
- private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@@ -66,25 +63,22 @@
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Transitions transitions,
- int disallowedAreaForEndBoundsHeight) {
+ Transitions transitions) {
this(taskOrganizer, windowDecoration, displayController, dragStartListener,
- SurfaceControl.Transaction::new, transitions, disallowedAreaForEndBoundsHeight);
+ SurfaceControl.Transaction::new, transitions);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
- int disallowedAreaForEndBoundsHeight) {
+ Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
mDesktopWindowDecoration = windowDecoration;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
mTransitions = transitions;
- mDisallowedAreaForEndBoundsHeight = disallowedAreaForEndBoundsHeight;
}
@Override
@@ -151,13 +145,10 @@
// won't be called.
resetVeilIfVisible();
}
- } else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
- mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
- y)) {
+ } else {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
- mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
- mDesktopWindowDecoration.calculateValidDragArea());
+ DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
+ mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 9703dce..bd39aa6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -68,17 +68,17 @@
)
var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
assertThat(testRegion.bounds).isEqualTo(Rect(
DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
-50,
DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
- 2 * STABLE_INSETS.top))
+ transitionHeight))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
- assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index e60be71..e6fabcf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -189,8 +189,9 @@
DISPLAY_BOUNDS.right - 100,
DISPLAY_BOUNDS.bottom - 100)
- DragPositioningCallbackUtility.onDragEnd(repositionTaskBounds, STARTING_BOUNDS,
- startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat(),
+ DragPositioningCallbackUtility.updateTaskBounds(repositionTaskBounds, STARTING_BOUNDS,
+ startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat())
+ DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(repositionTaskBounds,
validDragArea)
assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index de6903d..ce7b633 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -125,8 +125,7 @@
mockWindowDecoration,
mockDisplayController,
mockDragStartListener,
- mockTransactionFactory,
- DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ mockTransactionFactory
)
}
@@ -576,31 +575,6 @@
})
}
- @Test
- fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.right.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.right.toFloat() + 5
- val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
-
- taskPositioner.onDragPositioningEnd(newX, newY)
-
- verify(mockTransitions, never()).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }}, eq(taskPositioner))
- }
-
private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
bounds == configuration.windowConfiguration.bounds
@@ -656,70 +630,6 @@
}
@Test
- fun testDragResize_drag_taskPositionedInStableBounds() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
- // but not in disallowed end bounds area.
- verify(mockTransitions).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top
- }}, eq(taskPositioner))
- }
-
- @Test
- fun testDragResize_drag_taskPositionedInValidDragArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = VALID_DRAG_AREA.left - 500f
- val newY = VALID_DRAG_AREA.bottom + 500f
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- verify(mockTransitions).startTransition(
- eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- VALID_DRAG_AREA.bottom &&
- change.configuration.windowConfiguration.bounds.left ==
- VALID_DRAG_AREA.left
- }}, eq(taskPositioner))
- }
-
- @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 86253f3..7f6e538 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -138,8 +138,7 @@
mockDisplayController,
mockDragStartListener,
mockTransactionFactory,
- mockTransitions,
- DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT
+ mockTransitions
)
}
@@ -355,68 +354,6 @@
}
@Test
- fun testDragResize_drag_taskPositionedInStableBounds() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
- // but not in disallowed end bounds area.
- verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top }},
- eq(taskPositioner))
- }
-
- @Test
- fun testDragResize_drag_taskPositionedInValidDragArea() {
- taskPositioner.onDragPositioningStart(
- CTRL_TYPE_UNDEFINED, // drag
- STARTING_BOUNDS.left.toFloat(),
- STARTING_BOUNDS.top.toFloat()
- )
-
- val newX = VALID_DRAG_AREA.left - 500f
- val newY = VALID_DRAG_AREA.bottom + 500f
- taskPositioner.onDragPositioningMove(
- newX,
- newY
- )
- verify(mockTransaction).setPosition(any(), eq(newX), eq(newY))
-
- taskPositioner.onDragPositioningEnd(
- newX,
- newY
- )
- verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds.top ==
- VALID_DRAG_AREA.bottom &&
- change.configuration.windowConfiguration.bounds.left ==
- VALID_DRAG_AREA.left }},
- eq(taskPositioner))
- }
-
- @Test
fun testDragResize_drag_updatesStableBoundsOnRotate() {
// Test landscape stable bounds
performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
diff --git a/libs/hwui/Mesh.cpp b/libs/hwui/Mesh.cpp
index 37a7d74..5ef7acd 100644
--- a/libs/hwui/Mesh.cpp
+++ b/libs/hwui/Mesh.cpp
@@ -21,6 +21,8 @@
#include "SafeMath.h"
+namespace android {
+
static size_t min_vcount_for_mode(SkMesh::Mode mode) {
switch (mode) {
case SkMesh::Mode::kTriangles:
@@ -28,6 +30,7 @@
case SkMesh::Mode::kTriangleStrip:
return 3;
}
+ return 1;
}
// Re-implementation of SkMesh::validate to validate user side that their mesh is valid.
@@ -36,29 +39,30 @@
if (!mMeshSpec) {
FAIL_MESH_VALIDATE("MeshSpecification is required.");
}
- if (mVertexBufferData.empty()) {
+ if (mBufferData->vertexData().empty()) {
FAIL_MESH_VALIDATE("VertexBuffer is required.");
}
- auto meshStride = mMeshSpec->stride();
- auto meshMode = SkMesh::Mode(mMode);
+ size_t vertexStride = mMeshSpec->stride();
+ size_t vertexCount = mBufferData->vertexCount();
+ size_t vertexOffset = mBufferData->vertexOffset();
SafeMath sm;
- size_t vsize = sm.mul(meshStride, mVertexCount);
- if (sm.add(vsize, mVertexOffset) > mVertexBufferData.size()) {
+ size_t vertexSize = sm.mul(vertexStride, vertexCount);
+ if (sm.add(vertexSize, vertexOffset) > mBufferData->vertexData().size()) {
FAIL_MESH_VALIDATE(
"The vertex buffer offset and vertex count reads beyond the end of the"
" vertex buffer.");
}
- if (mVertexOffset % meshStride != 0) {
+ if (vertexOffset % vertexStride != 0) {
FAIL_MESH_VALIDATE("The vertex offset (%zu) must be a multiple of the vertex stride (%zu).",
- mVertexOffset, meshStride);
+ vertexOffset, vertexStride);
}
if (size_t uniformSize = mMeshSpec->uniformSize()) {
- if (!mBuilder->fUniforms || mBuilder->fUniforms->size() < uniformSize) {
+ if (!mUniformBuilder.fUniforms || mUniformBuilder.fUniforms->size() < uniformSize) {
FAIL_MESH_VALIDATE("The uniform data is %zu bytes but must be at least %zu.",
- mBuilder->fUniforms->size(), uniformSize);
+ mUniformBuilder.fUniforms->size(), uniformSize);
}
}
@@ -69,29 +73,33 @@
case SkMesh::Mode::kTriangleStrip:
return "triangle-strip";
}
+ return "unknown";
};
- if (!mIndexBufferData.empty()) {
- if (mIndexCount < min_vcount_for_mode(meshMode)) {
+
+ size_t indexCount = mBufferData->indexCount();
+ size_t indexOffset = mBufferData->indexOffset();
+ if (!mBufferData->indexData().empty()) {
+ if (indexCount < min_vcount_for_mode(mMode)) {
FAIL_MESH_VALIDATE("%s mode requires at least %zu indices but index count is %zu.",
- modeToStr(meshMode), min_vcount_for_mode(meshMode), mIndexCount);
+ modeToStr(mMode), min_vcount_for_mode(mMode), indexCount);
}
- size_t isize = sm.mul(sizeof(uint16_t), mIndexCount);
- if (sm.add(isize, mIndexOffset) > mIndexBufferData.size()) {
+ size_t isize = sm.mul(sizeof(uint16_t), indexCount);
+ if (sm.add(isize, indexOffset) > mBufferData->indexData().size()) {
FAIL_MESH_VALIDATE(
"The index buffer offset and index count reads beyond the end of the"
" index buffer.");
}
// If we allow 32 bit indices then this should enforce 4 byte alignment in that case.
- if (!SkIsAlign2(mIndexOffset)) {
+ if (!SkIsAlign2(indexOffset)) {
FAIL_MESH_VALIDATE("The index offset must be a multiple of 2.");
}
} else {
- if (mVertexCount < min_vcount_for_mode(meshMode)) {
+ if (vertexCount < min_vcount_for_mode(mMode)) {
FAIL_MESH_VALIDATE("%s mode requires at least %zu vertices but vertex count is %zu.",
- modeToStr(meshMode), min_vcount_for_mode(meshMode), mVertexCount);
+ modeToStr(mMode), min_vcount_for_mode(mMode), vertexCount);
}
- LOG_ALWAYS_FATAL_IF(mIndexCount != 0);
- LOG_ALWAYS_FATAL_IF(mIndexOffset != 0);
+ LOG_ALWAYS_FATAL_IF(indexCount != 0);
+ LOG_ALWAYS_FATAL_IF(indexOffset != 0);
}
if (!sm.ok()) {
@@ -100,3 +108,5 @@
#undef FAIL_MESH_VALIDATE
return {true, {}};
}
+
+} // namespace android
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
index 69fda34..8c6ca97 100644
--- a/libs/hwui/Mesh.h
+++ b/libs/hwui/Mesh.h
@@ -25,6 +25,8 @@
#include <utility>
+namespace android {
+
class MeshUniformBuilder {
public:
struct MeshUniform {
@@ -103,32 +105,146 @@
sk_sp<SkMeshSpecification> fMeshSpec;
};
-class Mesh {
+// Storage for CPU and GPU copies of the vertex and index data of a mesh.
+class MeshBufferData {
public:
- Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
- std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
- std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
- : mMeshSpec(meshSpec)
- , mMode(mode)
- , mVertexBufferData(std::move(vertexBufferData))
- , mVertexCount(vertexCount)
+ MeshBufferData(std::vector<uint8_t> vertexData, int32_t vertexCount, int32_t vertexOffset,
+ std::vector<uint8_t> indexData, int32_t indexCount, int32_t indexOffset)
+ : mVertexCount(vertexCount)
, mVertexOffset(vertexOffset)
- , mBuilder(std::move(builder))
- , mBounds(bounds) {}
-
- Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
- std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
- std::vector<uint8_t>&& indexBuffer, jint indexCount, jint indexOffset,
- std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
- : mMeshSpec(meshSpec)
- , mMode(mode)
- , mVertexBufferData(std::move(vertexBufferData))
- , mVertexCount(vertexCount)
- , mVertexOffset(vertexOffset)
- , mIndexBufferData(std::move(indexBuffer))
, mIndexCount(indexCount)
, mIndexOffset(indexOffset)
- , mBuilder(std::move(builder))
+ , mVertexData(std::move(vertexData))
+ , mIndexData(std::move(indexData)) {}
+
+ void updateBuffers(GrDirectContext* context) const {
+ GrDirectContext::DirectContextID currentId = context == nullptr
+ ? GrDirectContext::DirectContextID()
+ : context->directContextID();
+ if (currentId == mSkiaBuffers.fGenerationId && mSkiaBuffers.fVertexBuffer != nullptr) {
+ // Nothing to update since the Android API does not support partial updates yet.
+ return;
+ }
+
+ mSkiaBuffers.fVertexBuffer =
+#ifdef __ANDROID__
+ SkMeshes::MakeVertexBuffer(context, mVertexData.data(), mVertexData.size());
+#else
+ SkMeshes::MakeVertexBuffer(mVertexData.data(), mVertexData.size());
+#endif
+ if (mIndexCount != 0) {
+ mSkiaBuffers.fIndexBuffer =
+#ifdef __ANDROID__
+ SkMeshes::MakeIndexBuffer(context, mIndexData.data(), mIndexData.size());
+#else
+ SkMeshes::MakeIndexBuffer(mIndexData.data(), mIndexData.size());
+#endif
+ }
+ mSkiaBuffers.fGenerationId = currentId;
+ }
+
+ SkMesh::VertexBuffer* vertexBuffer() const { return mSkiaBuffers.fVertexBuffer.get(); }
+
+ sk_sp<SkMesh::VertexBuffer> refVertexBuffer() const { return mSkiaBuffers.fVertexBuffer; }
+ int32_t vertexCount() const { return mVertexCount; }
+ int32_t vertexOffset() const { return mVertexOffset; }
+
+ sk_sp<SkMesh::IndexBuffer> refIndexBuffer() const { return mSkiaBuffers.fIndexBuffer; }
+ int32_t indexCount() const { return mIndexCount; }
+ int32_t indexOffset() const { return mIndexOffset; }
+
+ const std::vector<uint8_t>& vertexData() const { return mVertexData; }
+ const std::vector<uint8_t>& indexData() const { return mIndexData; }
+
+private:
+ struct CachedSkiaBuffers {
+ sk_sp<SkMesh::VertexBuffer> fVertexBuffer;
+ sk_sp<SkMesh::IndexBuffer> fIndexBuffer;
+ GrDirectContext::DirectContextID fGenerationId = GrDirectContext::DirectContextID();
+ };
+
+ mutable CachedSkiaBuffers mSkiaBuffers;
+ int32_t mVertexCount = 0;
+ int32_t mVertexOffset = 0;
+ int32_t mIndexCount = 0;
+ int32_t mIndexOffset = 0;
+ std::vector<uint8_t> mVertexData;
+ std::vector<uint8_t> mIndexData;
+};
+
+class Mesh {
+public:
+ // A snapshot of the mesh for use by the render thread.
+ //
+ // After a snapshot is taken, future uniform changes to the original Mesh will not modify the
+ // uniforms returned by makeSkMesh.
+ class Snapshot {
+ public:
+ Snapshot() = delete;
+ Snapshot(const Snapshot&) = default;
+ Snapshot(Snapshot&&) = default;
+ Snapshot& operator=(const Snapshot&) = default;
+ Snapshot& operator=(Snapshot&&) = default;
+ ~Snapshot() = default;
+
+ const SkMesh& getSkMesh() const {
+ SkMesh::VertexBuffer* vertexBuffer = mBufferData->vertexBuffer();
+ LOG_FATAL_IF(vertexBuffer == nullptr,
+ "Attempt to obtain SkMesh when vertexBuffer has not been created, did you "
+ "forget to call MeshBufferData::updateBuffers with a GrDirectContext?");
+ if (vertexBuffer != mMesh.vertexBuffer()) mMesh = makeSkMesh();
+ return mMesh;
+ }
+
+ private:
+ friend class Mesh;
+
+ Snapshot(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode,
+ std::shared_ptr<const MeshBufferData> bufferData, sk_sp<const SkData> uniforms,
+ const SkRect& bounds)
+ : mMeshSpec(std::move(meshSpec))
+ , mMode(mode)
+ , mBufferData(std::move(bufferData))
+ , mUniforms(std::move(uniforms))
+ , mBounds(bounds) {}
+
+ SkMesh makeSkMesh() const {
+ const MeshBufferData& d = *mBufferData;
+ if (d.indexCount() != 0) {
+ return SkMesh::MakeIndexed(mMeshSpec, mMode, d.refVertexBuffer(), d.vertexCount(),
+ d.vertexOffset(), d.refIndexBuffer(), d.indexCount(),
+ d.indexOffset(), mUniforms,
+ SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
+ .mesh;
+ }
+ return SkMesh::Make(mMeshSpec, mMode, d.refVertexBuffer(), d.vertexCount(),
+ d.vertexOffset(), mUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
+ mBounds)
+ .mesh;
+ }
+
+ mutable SkMesh mMesh;
+ sk_sp<SkMeshSpecification> mMeshSpec;
+ SkMesh::Mode mMode;
+ std::shared_ptr<const MeshBufferData> mBufferData;
+ sk_sp<const SkData> mUniforms;
+ SkRect mBounds;
+ };
+
+ Mesh(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode, std::vector<uint8_t> vertexData,
+ int32_t vertexCount, int32_t vertexOffset, const SkRect& bounds)
+ : Mesh(std::move(meshSpec), mode, std::move(vertexData), vertexCount, vertexOffset,
+ /* indexData = */ {}, /* indexCount = */ 0, /* indexOffset = */ 0, bounds) {}
+
+ Mesh(sk_sp<SkMeshSpecification> meshSpec, SkMesh::Mode mode, std::vector<uint8_t> vertexData,
+ int32_t vertexCount, int32_t vertexOffset, std::vector<uint8_t> indexData,
+ int32_t indexCount, int32_t indexOffset, const SkRect& bounds)
+ : mMeshSpec(std::move(meshSpec))
+ , mMode(mode)
+ , mBufferData(std::make_shared<MeshBufferData>(std::move(vertexData), vertexCount,
+ vertexOffset, std::move(indexData),
+ indexCount, indexOffset))
+ , mUniformBuilder(mMeshSpec)
, mBounds(bounds) {}
Mesh(Mesh&&) = default;
@@ -137,77 +253,22 @@
[[nodiscard]] std::tuple<bool, SkString> validate();
- void updateSkMesh(GrDirectContext* context) const {
- GrDirectContext::DirectContextID genId = GrDirectContext::DirectContextID();
- if (context) {
- genId = context->directContextID();
- }
+ std::shared_ptr<const MeshBufferData> refBufferData() const { return mBufferData; }
- if (mIsDirty || genId != mGenerationId) {
- auto vertexData = reinterpret_cast<const void*>(mVertexBufferData.data());
-#ifdef __ANDROID__
- auto vb = SkMeshes::MakeVertexBuffer(context,
- vertexData,
- mVertexBufferData.size());
-#else
- auto vb = SkMeshes::MakeVertexBuffer(vertexData,
- mVertexBufferData.size());
-#endif
- auto meshMode = SkMesh::Mode(mMode);
- if (!mIndexBufferData.empty()) {
- auto indexData = reinterpret_cast<const void*>(mIndexBufferData.data());
-#ifdef __ANDROID__
- auto ib = SkMeshes::MakeIndexBuffer(context,
- indexData,
- mIndexBufferData.size());
-#else
- auto ib = SkMeshes::MakeIndexBuffer(indexData,
- mIndexBufferData.size());
-#endif
- mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
- ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
- SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
- .mesh;
- } else {
- mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
- mBuilder->fUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
- mBounds)
- .mesh;
- }
- mIsDirty = false;
- mGenerationId = genId;
- }
+ Snapshot takeSnapshot() const {
+ return Snapshot(mMeshSpec, mMode, mBufferData, mUniformBuilder.fUniforms, mBounds);
}
- SkMesh& getSkMesh() const {
- LOG_FATAL_IF(mIsDirty,
- "Attempt to obtain SkMesh when Mesh is dirty, did you "
- "forget to call updateSkMesh with a GrDirectContext? "
- "Defensively creating a CPU mesh");
- return mMesh;
- }
-
- void markDirty() { mIsDirty = true; }
-
- MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); }
+ MeshUniformBuilder* uniformBuilder() { return &mUniformBuilder; }
private:
sk_sp<SkMeshSpecification> mMeshSpec;
- int mMode = 0;
-
- std::vector<uint8_t> mVertexBufferData;
- size_t mVertexCount = 0;
- size_t mVertexOffset = 0;
-
- std::vector<uint8_t> mIndexBufferData;
- size_t mIndexCount = 0;
- size_t mIndexOffset = 0;
-
- std::unique_ptr<MeshUniformBuilder> mBuilder;
- SkRect mBounds{};
-
- mutable SkMesh mMesh{};
- mutable bool mIsDirty = true;
- mutable GrDirectContext::DirectContextID mGenerationId = GrDirectContext::DirectContextID();
+ SkMesh::Mode mMode;
+ std::shared_ptr<MeshBufferData> mBufferData;
+ MeshUniformBuilder mUniformBuilder;
+ SkRect mBounds;
};
+
+} // namespace android
+
#endif // MESH_H_
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 54aef55..d026379 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -573,9 +573,9 @@
struct DrawMesh final : Op {
static const auto kType = Type::DrawMesh;
DrawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const SkPaint& paint)
- : mesh(mesh), blender(std::move(blender)), paint(paint) {}
+ : mesh(mesh.takeSnapshot()), blender(std::move(blender)), paint(paint) {}
- const Mesh& mesh;
+ Mesh::Snapshot mesh;
sk_sp<SkBlender> blender;
SkPaint paint;
@@ -1296,14 +1296,5 @@
fDL->drawWebView(drawable);
}
-[[nodiscard]] const SkMesh& DrawMeshPayload::getSkMesh() const {
- LOG_FATAL_IF(!meshWrapper && !mesh, "One of Mesh or Mesh must be non-null");
- if (meshWrapper) {
- return meshWrapper->getSkMesh();
- } else {
- return *mesh;
- }
-}
-
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 965264f..f867852 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -41,11 +41,12 @@
enum class SkBlendMode;
class SkRRect;
-class Mesh;
namespace android {
-namespace uirenderer {
+class Mesh;
+
+namespace uirenderer {
namespace skiapipeline {
class FunctorDrawable;
}
@@ -68,18 +69,6 @@
static_assert(sizeof(DisplayListOp) == 4);
-class DrawMeshPayload {
-public:
- explicit DrawMeshPayload(const SkMesh* mesh) : mesh(mesh) {}
- explicit DrawMeshPayload(const Mesh* meshWrapper) : meshWrapper(meshWrapper) {}
-
- [[nodiscard]] const SkMesh& getSkMesh() const;
-
-private:
- const SkMesh* mesh = nullptr;
- const Mesh* meshWrapper = nullptr;
-};
-
struct DrawImagePayload {
explicit DrawImagePayload(Bitmap& bitmap)
: image(bitmap.makeImage()), palette(bitmap.palette()) {
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 0b739c3..72e83af 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -596,8 +596,8 @@
if (recordingContext) {
context = recordingContext->asDirectContext();
}
- mesh.updateSkMesh(context);
- mCanvas->drawMesh(mesh.getSkMesh(), blender, paint);
+ mesh.refBufferData()->updateBuffers(context);
+ mCanvas->drawMesh(mesh.takeSnapshot().getSkMesh(), blender, paint);
}
// ----------------------------------------------------------------------------
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 14b4f58..4eb6918 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -34,7 +34,6 @@
class SkRRect;
class SkRuntimeShaderBuilder;
class SkVertices;
-class Mesh;
namespace minikin {
class Font;
@@ -61,6 +60,7 @@
class AnimatedImageDrawable;
class Bitmap;
+class Mesh;
class Paint;
struct Typeface;
diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp
index 5cb43e5..3109de5 100644
--- a/libs/hwui/jni/android_graphics_Mesh.cpp
+++ b/libs/hwui/jni/android_graphics_Mesh.cpp
@@ -38,8 +38,8 @@
return 0;
}
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto meshPtr = new Mesh(skMeshSpec, mode, std::move(buffer), vertexCount, vertexOffset,
- std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+ auto meshPtr = new Mesh(skMeshSpec, static_cast<SkMesh::Mode>(mode), std::move(buffer),
+ vertexCount, vertexOffset, skRect);
auto [valid, msg] = meshPtr->validate();
if (!valid) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
@@ -63,9 +63,9 @@
return 0;
}
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto meshPtr = new Mesh(skMeshSpec, mode, std::move(vBuf), vertexCount, vertexOffset,
- std::move(iBuf), indexCount, indexOffset,
- std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
+ auto meshPtr =
+ new Mesh(skMeshSpec, static_cast<SkMesh::Mode>(mode), std::move(vBuf), vertexCount,
+ vertexOffset, std::move(iBuf), indexCount, indexOffset, skRect);
auto [valid, msg] = meshPtr->validate();
if (!valid) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
@@ -133,7 +133,6 @@
ScopedUtfChars name(env, uniformName);
const float values[4] = {value1, value2, value3, value4};
nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count, false);
- wrapper->markDirty();
}
static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
@@ -143,7 +142,6 @@
AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
nativeUpdateFloatUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
autoValues.length(), isColor);
- wrapper->markDirty();
}
static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
@@ -166,7 +164,6 @@
ScopedUtfChars name(env, uniformName);
const int values[4] = {value1, value2, value3, value4};
nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), values, count);
- wrapper->markDirty();
}
static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
@@ -176,7 +173,6 @@
AutoJavaIntArray autoValues(env, values, 0);
nativeUpdateIntUniforms(env, wrapper->uniformBuilder(), name.c_str(), autoValues.ptr(),
autoValues.length());
- wrapper->markDirty();
}
static void MeshWrapper_destroy(Mesh* wrapper) {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 5c8285a..e0216b6 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -111,8 +111,8 @@
}
auto grContext = info.canvasContext.getGrContext();
- for (auto mesh : mMeshes) {
- mesh->updateSkMesh(grContext);
+ for (const auto& bufferData : mMeshBufferData) {
+ bufferData->updateBuffers(grContext);
}
#endif
@@ -181,7 +181,7 @@
mDisplayList.reset();
- mMeshes.clear();
+ mMeshBufferData.clear();
mMutableImages.clear();
mVectorDrawables.clear();
mAnimatedImages.clear();
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index b9dc1c4..071a4e8 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -17,6 +17,7 @@
#pragma once
#include <deque>
+#include <memory>
#include "Mesh.h"
#include "RecordingCanvas.h"
@@ -172,7 +173,7 @@
std::deque<RenderNodeDrawable> mChildNodes;
std::deque<FunctorDrawable*> mChildFunctors;
std::vector<SkImage*> mMutableImages;
- std::vector<const Mesh*> mMeshes;
+ std::vector<std::shared_ptr<const MeshBufferData>> mMeshBufferData;
private:
std::vector<Pair<VectorDrawableRoot*, SkMatrix>> mVectorDrawables;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index e917f9a..45bfe1c 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -342,7 +342,7 @@
}
void SkiaRecordingCanvas::drawMesh(const Mesh& mesh, sk_sp<SkBlender> blender, const Paint& paint) {
- mDisplayList->mMeshes.push_back(&mesh);
+ mDisplayList->mMeshBufferData.push_back(mesh.refBufferData());
mRecorder.drawMesh(mesh, blender, paint);
}
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 752ebdf..4812685 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -58,6 +58,7 @@
"configuration.cpp",
"hardware_buffer_jni.cpp",
"input.cpp",
+ "input_transfer_token.cpp",
"looper.cpp",
"native_activity.cpp",
"native_window_jni.cpp",
diff --git a/native/android/input_transfer_token.cpp b/native/android/input_transfer_token.cpp
new file mode 100644
index 0000000..501e1d3
--- /dev/null
+++ b/native/android/input_transfer_token.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "InputTransferToken"
+
+#include <android/input_transfer_token_jni.h>
+#include <android_runtime/android_window_InputTransferToken.h>
+#include <gui/InputTransferToken.h>
+#include <log/log_main.h>
+
+using namespace android;
+
+#define CHECK_NOT_NULL(name) \
+ LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument");
+
+void InputTransferToken_acquire(InputTransferToken* inputTransferToken) {
+ // incStrong/decStrong token must be the same, doesn't matter what it is
+ inputTransferToken->incStrong((void*)InputTransferToken_acquire);
+}
+
+void InputTransferToken_release(InputTransferToken* inputTransferToken) {
+ // incStrong/decStrong token must be the same, doesn't matter what it is
+ inputTransferToken->decStrong((void*)InputTransferToken_acquire);
+}
+
+AInputTransferToken* AInputTransferToken_fromJava(JNIEnv* env, jobject inputTransferTokenObj) {
+ CHECK_NOT_NULL(env);
+ CHECK_NOT_NULL(inputTransferTokenObj);
+ InputTransferToken* inputTransferToken =
+ android_window_InputTransferToken_getNativeInputTransferToken(env,
+ inputTransferTokenObj);
+ CHECK_NOT_NULL(inputTransferToken);
+ InputTransferToken_acquire(inputTransferToken);
+ return reinterpret_cast<AInputTransferToken*>(inputTransferToken);
+}
+
+jobject AInputTransferToken_toJava(JNIEnv* _Nonnull env,
+ const AInputTransferToken* aInputTransferToken) {
+ CHECK_NOT_NULL(env);
+ CHECK_NOT_NULL(aInputTransferToken);
+ const InputTransferToken* inputTransferToken =
+ reinterpret_cast<const InputTransferToken*>(aInputTransferToken);
+ return android_window_InputTransferToken_getJavaInputTransferToken(env, inputTransferToken);
+}
+
+void AInputTransferToken_release(AInputTransferToken* aInputTransferToken) {
+ CHECK_NOT_NULL(aInputTransferToken);
+ InputTransferToken* inputTransferToken =
+ reinterpret_cast<InputTransferToken*>(aInputTransferToken);
+ InputTransferToken_release(inputTransferToken);
+}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 35e37b2..b2925bf 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -98,6 +98,9 @@
AInputQueue_getEvent;
AInputQueue_hasEvents;
AInputQueue_preDispatchEvent;
+ AInputTransferToken_fromJava; # introduced=35
+ AInputTransferToken_release; # introduced=35
+ AInputTransferToken_toJava; # introduced=35
AKeyEvent_getAction;
AKeyEvent_getDownTime;
AKeyEvent_getEventTime;
diff --git a/packages/CrashRecovery/OWNERS b/packages/CrashRecovery/OWNERS
index daa0211..8337fd2 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 0fcec26..d0fee44 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 @@
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
similarity index 77%
rename from packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenImagePathManager.kt
rename to packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/CredentialManagerGoldenPathManager.kt
index 6aef24d..9cfdffd 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 @@
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 28d83ee..b843213 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 @@
@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 e79176b..56b1c2e 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 @@
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 5e52008..814d08a 100644
--- a/packages/PackageInstaller/res/values-watch/themes.xml
+++ b/packages/PackageInstaller/res/values-watch/themes.xml
@@ -16,5 +16,11 @@
-->
<resources>
- <style name="DialogWhenLarge" parent="@android:style/Theme.DeviceDefault.NoActionBar"/>
+ <style name="Theme.AlertDialogActivity"
+ parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+ <item name="alertDialogStyle">@style/AlertDialog</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
</resources>
diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md
new file mode 100644
index 0000000..30cb993
--- /dev/null
+++ b/packages/SettingsLib/DataStore/README.md
@@ -0,0 +1,164 @@
+# Datastore library
+
+This library aims to manage datastore in a consistent way.
+
+## Overview
+
+A datastore is required to extend the `BackupRestoreStorage` class and implement
+either `Observable` or `KeyedObservable` interface, which enforces:
+
+- Backup and restore: Datastore should support
+ [data backup](https://developer.android.com/guide/topics/data/backup) to
+ preserve user experiences on a new device.
+- Observer pattern: The
+ [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to
+ monitor data change in the datastore and
+ - trigger
+ [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\))
+ automatically.
+ - track data change event to log metrics.
+ - update internal state and take action.
+
+### Backup and restore
+
+The Android backup framework provides
+[BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper)
+and
+[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)
+to back up a datastore. However, there are several caveats when implement
+`BackupHelper`:
+
+- performBackup: The data is updated incrementally but it is not well
+ documented. The `ParcelFileDescriptor` state parameters are normally ignored
+ and data is updated even there is no change.
+- restoreEntity: The implementation must take care not to seek or close the
+ underlying data source, nor read more than size() bytes from the stream when
+ restore (see
+ [BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)).
+ It is possible a `BackupHelper` prevents other `BackupHelper`s from
+ restoring data.
+- writeNewStateDescription: Existing implementations rarely notice that this
+ callback is invoked after all entities are restored, and check if necessary
+ data are all restored in `restoreEntity` (e.g.
+ [BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)),
+ which is not robust sometimes.
+
+This library provides more clear API and offers some improvements:
+
+- The implementation only needs to focus on the `BackupRestoreEntity`
+ interface. The `InputStream` of restore will ensure bounded data are read,
+ and close the stream will be no-op.
+- The library computes checksum of the backup data automatically, so that
+ unchanged data will not be sent to Android backup system.
+- Data compression is supported:
+ - ZIP best compression is enabled by default, no extra effort needs to be
+ taken.
+ - It is safe to switch between compression and no compression in future,
+ the backup data will add 1 byte header to recognize the codec.
+ - To support other compression algorithms, simply wrap over the
+ `InputStream` and `OutputStream`. Actually, the checksum is computed in
+ this way by
+ [CheckedInputStream](https://developer.android.com/reference/java/util/zip/CheckedInputStream)
+ and
+ [CheckedOutputStream](https://developer.android.com/reference/java/util/zip/CheckedOutputStream),
+ see `BackupRestoreStorage` implementation for more details.
+- Enhanced forward compatibility for file is enabled: If a backup includes
+ data that didn't exist in earlier versions of the app, the data can still be
+ successfully restored in those older versions. This is achieved by extending
+ the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will
+ treat each file as an entity and do the backup / restore.
+- Manual `BackupManager.dataChanged` call is unnecessary now, the library will
+ do the invocation (see next section).
+
+### Observer pattern
+
+Manual `BackupManager.dataChanged` call is required by current backup framework.
+In practice, it is found that `SharedPreferences` usages foget to invoke the
+API. Besides, there are common use cases to log metrics when data is changed.
+Consequently, observer pattern is employed to resolve the issues.
+
+If the datastore is key-value based (e.g. `SharedPreferences`), implements the
+`KeyedObservable` interface to offer fine-grained observer. Otherwise,
+implements `Observable`. The library provides thread-safe implementations
+(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be
+helpful.
+
+Keep in mind that the implementation should call `KeyedObservable.notifyChange`
+/ `Observable.notifyChange` whenever internal data is changed, so that the
+registered observer will be notified properly.
+
+## Usage and example
+
+For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To
+back up other file based storage, extend the `BackupRestoreFileStorage` class.
+
+Here is an example of customized datastore, which has a string to back up:
+
+```kotlin
+class MyDataStore : ObservableBackupRestoreStorage() {
+ // Another option is make it a StringEntity type and maintain a String field inside StringEntity
+ @Volatile // backup/restore happens on Binder thread
+ var data: String? = null
+ private set
+
+ fun setData(data: String?) {
+ this.data = data
+ notifyChange(ChangeReason.UPDATE)
+ }
+
+ override val name: String
+ get() = "MyData"
+
+ override fun createBackupRestoreEntities(): List<BackupRestoreEntity> =
+ listOf(StringEntity("data"))
+
+ private inner class StringEntity(override val key: String) : BackupRestoreEntity {
+ override fun backup(
+ backupContext: BackupContext,
+ outputStream: OutputStream,
+ ) =
+ if (data != null) {
+ outputStream.write(data!!.toByteArray(UTF_8))
+ EntityBackupResult.UPDATE
+ } else {
+ EntityBackupResult.DELETE
+ }
+
+ override fun restore(restoreContext: RestoreContext, inputStream: InputStream) {
+ data = String(inputStream.readAllBytes(), UTF_8)
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+ }
+ }
+
+ override fun onRestoreFinished() {
+ // TODO: Update state with the restored data. Use this callback instead "restore()" in case
+ // the restore action involves several entities.
+ // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you
+ }
+}
+```
+
+In the application class:
+
+```kotlin
+class MyApplication : Application() {
+ override fun onCreate() {
+ super.onCreate();
+ BackupRestoreStorageManager.getInstance(this).add(MyDataStore());
+ }
+}
+```
+
+In the custom `BackupAgentHelper` class:
+
+```kotlin
+class MyBackupAgentHelper : BackupAgentHelper() {
+ override fun onCreate() {
+ BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
+ }
+
+ override fun onRestoreFinished() {
+ BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
+ }
+}
+```
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index 18e8fc3..f47041d 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 @@
*/
@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 @@
}
/**
- * 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
similarity index 69%
rename from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
rename to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenPathManager.kt
index f5fba7f..d590760 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 ae85675..16f6b5e 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 @@
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 3586dcb..b21895b 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/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index bd27c89..1118efc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -81,11 +81,13 @@
public static final int BROADCAST_STATE_UNKNOWN = 0;
public static final int BROADCAST_STATE_ON = 1;
public static final int BROADCAST_STATE_OFF = 2;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(
prefix = {"BROADCAST_STATE_"},
value = {BROADCAST_STATE_UNKNOWN, BROADCAST_STATE_ON, BROADCAST_STATE_OFF})
public @interface BroadcastState {}
+
private static final String SETTINGS_PKG = "com.android.settings";
private static final String TAG = "LocalBluetoothLeBroadcast";
private static final boolean DEBUG = BluetoothUtils.D;
@@ -1068,7 +1070,7 @@
return;
}
int fallbackActiveGroupId = getFallbackActiveGroupId();
- if (targetCachedDevice.getGroupId() == fallbackActiveGroupId) {
+ if (getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
Log.d(
TAG,
"Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
@@ -1091,6 +1093,23 @@
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
+ private int getGroupId(CachedBluetoothDevice cachedDevice) {
+ int groupId = cachedDevice.getGroupId();
+ String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
+ if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ Log.d(TAG, "getGroupId by CSIP profile for device: " + anonymizedAddress);
+ return groupId;
+ }
+ for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+ if (profile instanceof LeAudioProfile) {
+ Log.d(TAG, "getGroupId by LEA profile for device: " + anonymizedAddress);
+ return ((LeAudioProfile) profile).getGroupId(cachedDevice.getDevice());
+ }
+ }
+ Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
+ return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
+ }
+
private void notifyBroadcastStateChange(@BroadcastState int state) {
if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 0df4615..21cc9a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -161,11 +161,11 @@
override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
withContext(backgroundCoroutineContext) {
- if (isMuted) {
- audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
- } else {
- audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
- }
+ audioManager.adjustStreamVolume(
+ audioStream.value,
+ if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
+ 0,
+ )
}
private fun getMinVolume(stream: AudioStream): Int =
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 1586b8f..c9ac97d 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
@@ -84,10 +84,10 @@
(audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION &&
audioStreamModel.isMuted)
) {
- return 0
+ return audioStreamModel.minVolume
}
} else if (audioStreamModel.isMuted) {
- return 0
+ return audioStreamModel.minVolume
}
return audioStreamModel.volume
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 1728a80..9860cd8 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -77,13 +77,13 @@
`when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
`when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
`when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
- val streamType = it.arguments[1] as Int
- volumeByStream[it.arguments[0] as Int] = streamType
+ val streamType = it.arguments[0] as Int
+ volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
triggerEvent(AudioManagerEvent.StreamVolumeChanged(AudioStream(streamType)))
}
`when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
val streamType = it.arguments[0] as Int
- isMuteByStream[streamType] = it.arguments[2] == AudioManager.ADJUST_MUTE
+ isMuteByStream[streamType] = it.arguments[1] == AudioManager.ADJUST_MUTE
triggerEvent(AudioManagerEvent.StreamMuteChanged(AudioStream(streamType)))
}
`when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
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 fe1529d..9c518de 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 @@
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 @@
@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 30d5d4b..eaec617 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 @@
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 893932f..046d6e2 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 @@
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 46c89900..6eb2dd0 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -670,7 +670,6 @@
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 eb2d13d..43ea3ec 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 98591e9..3c18f17 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" />
@@ -910,7 +911,7 @@
<activity
android:name=".volume.panel.ui.activity.VolumePanelActivity"
- android:label="@string/sound_settings"
+ android:label="@string/accessibility_volume_settings"
android:excludeFromRecents="true"
android:exported="false"
android:launchMode="singleInstance"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 85bdb29..1c98630 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -115,10 +115,10 @@
}
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 {
+ 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/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 0d6b710..6a510bd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -85,6 +85,8 @@
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.pointer.motionEventSpy
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -177,33 +179,43 @@
}
.thenIf(!viewModel.isEditMode) {
Modifier.pointerInput(
- gridState,
- contentOffset,
- communalContent,
- gridCoordinates
- ) {
- detectLongPressGesture { offset ->
- // Deduct both grid offset relative to its container and content offset.
- val adjustedOffset =
- gridCoordinates?.let {
- offset - it.positionInWindow() - contentOffset
+ gridState,
+ contentOffset,
+ communalContent,
+ gridCoordinates
+ ) {
+ detectLongPressGesture { offset ->
+ // Deduct both grid offset relative to its container and content
+ // offset.
+ val adjustedOffset =
+ gridCoordinates?.let {
+ offset - it.positionInWindow() - contentOffset
+ }
+ val index =
+ adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+ // Display the button only when the gesture initiates from widgets,
+ // the CTA tile, or an empty area on the screen. UMO/smartspace have
+ // their own long-press handlers. To prevent user confusion, we
+ // should
+ // not display this button.
+ if (
+ index == null ||
+ communalContent[index].isWidgetContent() ||
+ communalContent[index] is
+ CommunalContentModel.CtaTileInViewMode
+ ) {
+ isButtonToEditWidgetsShowing = true
}
- val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
- // Display the button only when the gesture initiates from widgets,
- // the CTA tile, or an empty area on the screen. UMO/smartspace have
- // their own long-press handlers. To prevent user confusion, we should
- // not display this button.
- if (
- index == null ||
- communalContent[index].isWidgetContent() ||
- communalContent[index] is CommunalContentModel.CtaTileInViewMode
- ) {
- isButtonToEditWidgetsShowing = true
+ val key =
+ index?.let { keyAtIndexIfEditable(communalContent, index) }
+ viewModel.setSelectedKey(key)
}
- val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
- viewModel.setSelectedKey(key)
}
- }
+ .onPreviewKeyEvent {
+ onKeyEvent(viewModel)
+ false
+ }
+ .motionEventSpy { onMotionEvent(viewModel) }
},
) {
CommunalHubLazyGrid(
@@ -311,6 +323,14 @@
}
}
+private fun onKeyEvent(viewModel: BaseCommunalViewModel) {
+ viewModel.signalUserInteraction()
+}
+
+private fun onMotionEvent(viewModel: BaseCommunalViewModel) {
+ viewModel.signalUserInteraction()
+}
+
@Composable
private fun ScrollOnNewSmartspaceEffect(
viewModel: BaseCommunalViewModel,
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 51464d0..31201c2f 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 @@
) {
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 @@
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/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 5f7bd47..b721e41 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -32,6 +32,11 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.compose.animation.Expandable
@@ -52,6 +57,7 @@
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
val viewModelByState by viewModelFlow.collectAsState()
val viewModel = viewModelByState ?: return
+ val label = viewModel.label.toString()
Column(
modifier = modifier,
@@ -59,7 +65,11 @@
horizontalAlignment = Alignment.CenterHorizontally,
) {
Expandable(
- modifier = Modifier.height(64.dp).fillMaxWidth(),
+ modifier =
+ Modifier.height(64.dp).fillMaxWidth().semantics {
+ role = Role.Button
+ contentDescription = label
+ },
color = MaterialTheme.colorScheme.primaryContainer,
shape = RoundedCornerShape(28.dp),
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
@@ -71,7 +81,8 @@
}
}
Text(
- text = viewModel.label.toString(),
+ modifier = Modifier.clearAndSetSemantics {},
+ text = label,
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index dfee684..28fd785 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -32,6 +32,9 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
@@ -50,13 +53,16 @@
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
val viewModelByState by viewModelFlow.collectAsState()
val viewModel = viewModelByState ?: return
+ val label = viewModel.label.toString()
+
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
OutlinedIconToggleButton(
- modifier = Modifier.height(64.dp).fillMaxWidth(),
+ modifier =
+ Modifier.height(64.dp).fillMaxWidth().semantics { contentDescription = label },
checked = viewModel.isChecked,
onCheckedChange = onCheckedChange,
shape = RoundedCornerShape(28.dp),
@@ -72,7 +78,8 @@
Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
}
Text(
- text = viewModel.label.toString(),
+ modifier = Modifier.clearAndSetSemantics {},
+ text = label,
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index 53de5bc..6f2ed81 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -49,10 +49,16 @@
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.LiveRegionMode
+import androidx.compose.ui.semantics.liveRegion
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.compose.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.toColor
+import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.ConnectedDeviceViewModel
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.DeviceIconViewModel
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.MediaOutputViewModel
@@ -74,14 +80,19 @@
viewModel.connectedDeviceViewModel.collectAsState()
val deviceIconViewModel: DeviceIconViewModel? by
viewModel.deviceIconViewModel.collectAsState()
+ val clickLabel = stringResource(R.string.volume_panel_enter_media_output_settings)
Expandable(
- modifier = Modifier.fillMaxWidth().height(80.dp),
+ modifier =
+ Modifier.fillMaxWidth().height(80.dp).semantics {
+ liveRegion = LiveRegionMode.Polite
+ this.onClick(label = clickLabel) { false }
+ },
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(28.dp),
onClick = { viewModel.onBarClick(it) },
) { _ ->
- Row(verticalAlignment = Alignment.CenterVertically) {
+ Row(modifier = Modifier, verticalAlignment = Alignment.CenterVertically) {
connectedDeviceViewModel?.let { ConnectedDeviceText(it) }
deviceIconViewModel?.let { ConnectedDeviceIcon(it) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
index 89251939..26086d1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -24,13 +24,16 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.android.compose.PlatformIconButton
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.res.R
@@ -101,16 +104,19 @@
}
}
- PlatformIconButton(
+ IconButton(
modifier = Modifier.align(Alignment.TopEnd).size(64.dp).padding(20.dp),
- iconResource = R.drawable.ic_close,
- contentDescription = null,
onClick = { dialog.dismiss() },
colors =
IconButtonDefaults.iconButtonColors(
contentColor = MaterialTheme.colorScheme.outline
)
- )
+ ) {
+ Icon(
+ painterResource(R.drawable.ic_close),
+ contentDescription = stringResource(R.string.accessibility_desc_close),
+ )
+ }
}
}
}
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 81d2da0..e1cf3a5 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
@@ -45,6 +45,11 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSliderColors
import com.android.systemui.res.R
@@ -76,16 +81,26 @@
VolumeSlider(
modifier = Modifier.weight(1f),
state = sliderState,
- onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
+ },
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
+ val expandButtonStateDescription =
+ if (isExpanded) stringResource(R.string.volume_panel_expanded_sliders)
+ else stringResource(R.string.volume_panel_collapsed_sliders)
if (isExpandable) {
ExpandButton(
+ modifier =
+ Modifier.semantics {
+ role = Role.Switch
+ stateDescription = expandButtonStateDescription
+ },
isExpanded = isExpanded,
onExpandedChanged = onExpandedChanged,
- sliderColors,
- Modifier,
+ sliderColors = sliderColors,
)
}
}
@@ -114,9 +129,10 @@
VolumeSlider(
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
state = sliderState,
- onValueChangeFinished = {
- sliderViewModel.onValueChangeFinished(sliderState, it)
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
},
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index 910ee72..b284c69 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -43,7 +43,10 @@
VolumeSlider(
modifier = Modifier.fillMaxWidth(),
state = sliderState,
- onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
+ },
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
}
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 3e0aee5..fa94ea0 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
@@ -18,19 +18,27 @@
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
-import androidx.compose.animation.expandVertically
-import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonColors
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.ProgressBarRangeInfo
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.disabled
+import androidx.compose.ui.semantics.progressBarRangeInfo
+import androidx.compose.ui.semantics.setProgress
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
@@ -40,23 +48,61 @@
@Composable
fun VolumeSlider(
state: SliderState,
- onValueChangeFinished: (Float) -> Unit,
+ onValueChange: (newValue: Float) -> Unit,
+ onIconTapped: () -> Unit,
modifier: Modifier = Modifier,
sliderColors: PlatformSliderColors,
) {
- var value by remember(state.value) { mutableFloatStateOf(state.value) }
+ val value by
+ animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
PlatformSlider(
- modifier = modifier,
+ modifier =
+ modifier.clearAndSetSemantics {
+ if (!state.isEnabled) disabled()
+ contentDescription = state.label
+
+ // provide a not animated value to the a11y because it fails to announce the settled
+ // value when it changes rapidly.
+ progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
+ setProgress { targetValue ->
+ val targetDirection =
+ when {
+ targetValue > value -> 1
+ targetValue < value -> -1
+ else -> 0
+ }
+
+ val newValue =
+ (value + targetDirection * state.a11yStep).coerceIn(
+ state.valueRange.start,
+ state.valueRange.endInclusive
+ )
+ onValueChange(newValue)
+ true
+ }
+ },
value = value,
valueRange = state.valueRange,
- onValueChange = { value = it },
- onValueChangeFinished = { onValueChangeFinished(value) },
+ onValueChange = onValueChange,
enabled = state.isEnabled,
icon = { isDragging ->
if (isDragging) {
- Text(text = value.toInt().toString())
+ Text(text = value.toInt().toString(), color = LocalContentColor.current)
} else {
- state.icon?.let { Icon(modifier = Modifier.size(24.dp), icon = it) }
+ state.icon?.let {
+ IconButton(
+ onClick = onIconTapped,
+ colors =
+ IconButtonColors(
+ contentColor = LocalContentColor.current,
+ containerColor = Color.Transparent,
+ disabledContentColor = LocalContentColor.current,
+ disabledContainerColor = Color.Transparent,
+ )
+ ) {
+ Icon(modifier = Modifier.size(24.dp), icon = it)
+ }
+ }
}
},
colors = sliderColors,
@@ -66,19 +112,21 @@
modifier = Modifier.basicMarquee(),
text = state.label,
style = MaterialTheme.typography.titleMedium,
+ color = LocalContentColor.current,
maxLines = 1,
)
state.disabledMessage?.let { message ->
AnimatedVisibility(
!state.isEnabled,
- enter = expandVertically { it },
- exit = shrinkVertically { it },
+ enter = slideInVertically { it },
+ exit = slideOutVertically { it },
) {
Text(
modifier = Modifier.basicMarquee(),
text = message,
style = MaterialTheme.typography.bodySmall,
+ color = LocalContentColor.current,
maxLines = 1,
)
}
diff --git a/packages/SystemUI/customization/res/values/ids.xml b/packages/SystemUI/customization/res/values/ids.xml
new file mode 100644
index 0000000..5eafbfc
--- /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 09fdd11..bd1403a 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.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 @@
@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 @@
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 @@
@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 @@
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 @@
@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 @@
)
)
- 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 @@
@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 @@
@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 @@
@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 @@
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 @@
@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))
- val msg = underTest.verifySim(listOf(0, 0, 0))
+ 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/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index ce6445b..d624bf7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal
+import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,13 +26,17 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dock.dockManager
import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
@@ -54,11 +59,15 @@
@Before
fun setUp() {
with(kosmos) {
+ fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT)
+
underTest =
CommunalSceneStartable(
dockManager = dockManager,
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
+ keyguardInteractor = keyguardInteractor,
+ systemSettings = fakeSettings,
applicationScope = applicationCoroutineScope,
bgScope = applicationCoroutineScope,
)
@@ -246,6 +255,95 @@
}
}
+ @Test
+ fun hubTimeout_whenDreaming() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
+ @Test
+ fun hubTimeout_dreamStopped() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Dream stops, timeout is cancelled and device stays on hub, because the regular
+ // screen timeout will take effect at this point.
+ fakeKeyguardRepository.setDreaming(false)
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
+ fun hubTimeout_userActivityTriggered_resetsTimeout() =
+ with(kosmos) {
+ testScope.runTest {
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Wait a bit, but not long enough to timeout.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+
+ // Send user interaction to reset timeout.
+ communalInteractor.signalUserInteraction()
+
+ // If user activity didn't reset timeout, we would have gone back to Blank by now.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Timeout happens one interval after the user interaction.
+ advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
+ @Test
+ fun hubTimeout_screenTimeoutChanged() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2)
+
+ // Device is dreaming and on communal.
+ fakeKeyguardRepository.setDreaming(true)
+ communalInteractor.onSceneChanged(CommunalScenes.Communal)
+
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ // Scene times out back to blank after the screen timeout.
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+
+ advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+ }
+ }
+
private fun TestScope.updateDocked(docked: Boolean) =
with(kosmos) {
runCurrent()
@@ -260,4 +358,8 @@
setCommunalAvailable(true)
runCurrent()
}
+
+ companion object {
+ private const val SCREEN_TIMEOUT = 1000
+ }
}
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 2e9ee5c..4a7757b 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 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 @@
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 @@
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 4f44705..70ceb2a 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 @@
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.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.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 @@
assertThat(canSwipeToEnter).isFalse()
trustRepository.setCurrentUserTrusted(true)
- runCurrent()
faceAuthRepository.isAuthenticated.value = false
+ runCurrent()
assertThat(canSwipeToEnter).isTrue()
}
@@ -383,6 +398,204 @@
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 87b1bbb..1adf414 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.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.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 @@
@Test
@DisableFlags(FLAG_HOME_PANEL_DREAM)
- fun testStartDoesNotRunDreamServiceWhenFlagIsDisabled() =
+ fun testStartDisablesDreamServiceWhenFlagIsDisabled() =
testScope.runTest {
selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
startable.start()
runCurrent()
- verify(packageManager, never()).setComponentEnabledSetting(any(), any(), any())
+ verify(packageManager)
+ .setComponentEnabledSetting(
+ eq(componentName),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ eq(PackageManager.DONT_KILL_APP)
+ )
}
private fun ControlsServiceInfo(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
index a1f885c..c0e5a9b 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 @@
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/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index d3fa360..cd79ed1 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 @@
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 e683f34c..278bf9b 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.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 @@
}
@Test
+ fun alphaOnFullQsExpansion() =
+ testScope.runTest {
+ val viewState = ViewStateAccessor()
+ val alpha by collectLastValue(underTest.keyguardAlpha(viewState))
+
+ showLockscreenWithQSExpanded()
+
+ // Alpha fades out as QS expands
+ shadeRepository.setQsExpansion(0.5f)
+ assertThat(alpha).isWithin(0.01f).of(0.5f)
+ shadeRepository.setQsExpansion(0.9f)
+ assertThat(alpha).isWithin(0.01f).of(0.1f)
+
+ // Ensure that alpha is set back to 1f when QS is fully expanded
+ shadeRepository.setQsExpansion(1f)
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ @Test
fun shadeCollapseFadeIn() =
testScope.runTest {
val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
new file mode 100644
index 0000000..183a58a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.app.Notification
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeGlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationThrottleHun.FLAG_NAME)
+class AvalancheControllerTest : SysuiTestCase() {
+
+ private val mAvalancheController = AvalancheController()
+
+ // For creating mocks
+ @get:Rule var rule: MockitoRule = MockitoJUnit.rule()
+ @Mock private val runnableMock: Runnable? = null
+
+ // For creating TestableHeadsUpManager
+ @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
+ private val mUiEventLoggerFake = UiEventLoggerFake()
+ private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
+ private val mGlobalSettings = FakeGlobalSettings()
+ private val mSystemClock = FakeSystemClock()
+ private val mExecutor = FakeExecutor(mSystemClock)
+ private var testableHeadsUpManager: BaseHeadsUpManager? = null
+
+ @Before
+ fun setUp() {
+ // Use default non-a11y timeout
+ Mockito.`when`(
+ mAccessibilityMgr!!.getRecommendedTimeoutMillis(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt()
+ )
+ )
+ .then { i: InvocationOnMock -> i.getArgument(0) }
+
+ // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null
+ testableHeadsUpManager =
+ TestableHeadsUpManager(
+ mContext,
+ mLogger,
+ mExecutor,
+ mGlobalSettings,
+ mSystemClock,
+ mAccessibilityMgr,
+ mUiEventLoggerFake,
+ mAvalancheController
+ )
+ }
+
+ private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
+ val entry = testableHeadsUpManager!!.createHeadsUpEntry()
+
+ entry.setEntry(
+ NotificationEntryBuilder()
+ .setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, "")))
+ .build()
+ )
+ return entry
+ }
+
+ @Test
+ fun testUpdate_isShowing_runsRunnable() {
+ // Entry is showing
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was run
+ Mockito.verify(runnableMock, Mockito.times(1)).run()
+ }
+
+ @Test
+ fun testUpdate_noneShowingAndNotNext_showNow() {
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+
+ // None showing
+ mAvalancheController.headsUpEntryShowing = null
+
+ // Entry is NOT next
+ mAvalancheController.clearNext()
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry is showing now
+ Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(headsUpEntry)
+ }
+
+ @Test
+ fun testUpdate_isNext_addsRunnable() {
+ // Another entry is already showing
+ val otherShowingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+ // Entry is next
+ val headsUpEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+ // Entry has one Runnable
+ val runnableList: List<Runnable?>? = mAvalancheController.nextMap[headsUpEntry]
+ Truth.assertThat(runnableList).isNotNull()
+ Truth.assertThat(runnableList!!.size).isEqualTo(1)
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock, "testLabel")
+
+ // Entry has two Runnables
+ Truth.assertThat(runnableList.size).isEqualTo(2)
+ }
+
+ @Test
+ fun testUpdate_isNotNextWithOtherHunShowing_isNext() {
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+
+ // Another entry is already showing
+ val otherShowingEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.headsUpEntryShowing = otherShowingEntry
+
+ // Entry is NOT next
+ mAvalancheController.clearNext()
+
+ // Update
+ mAvalancheController.update(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry is next
+ Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isTrue()
+ }
+
+ @Test
+ fun testDelete_isNext_removedFromNext_runnableNotRun() {
+ // Entry is next
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.addToNext(headsUpEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock, "testLabel")
+
+ // Entry was removed from next
+ Truth.assertThat(mAvalancheController.nextMap.containsKey(headsUpEntry)).isFalse()
+
+ // Runnable was not run
+ Mockito.verify(runnableMock, Mockito.times(0)).run()
+ }
+
+ @Test
+ fun testDelete_wasDropped_removedFromDropSet() {
+ // Entry was dropped
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.debugDropSet.add(headsUpEntry)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Entry was removed from dropSet
+ Truth.assertThat(mAvalancheController.debugDropSet.contains(headsUpEntry)).isFalse()
+ }
+
+ @Test
+ fun testDelete_wasDropped_runnableNotRun() {
+ // Entry was dropped
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.debugDropSet.add(headsUpEntry)
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was not run
+ Mockito.verify(runnableMock, Mockito.times(0)).run()
+ }
+
+ @Test
+ fun testDelete_isShowing_runnableRun() {
+ // Entry is showing
+ val headsUpEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = headsUpEntry
+
+ // Delete
+ mAvalancheController.delete(headsUpEntry, runnableMock!!, "testLabel")
+
+ // Runnable was run
+ Mockito.verify(runnableMock, Mockito.times(1)).run()
+ }
+
+ @Test
+ fun testDelete_isShowing_showNext() {
+ // Entry is showing
+ val showingEntry = createHeadsUpEntry(id = 0)
+ mAvalancheController.headsUpEntryShowing = showingEntry
+
+ // There's another entry waiting to show next
+ val nextEntry = createHeadsUpEntry(id = 1)
+ mAvalancheController.addToNext(nextEntry, runnableMock!!)
+
+ // Delete
+ mAvalancheController.delete(showingEntry, runnableMock, "testLabel")
+
+ // Next entry is shown
+ Truth.assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index db4d42f..830bcef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -35,13 +35,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Intent;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -77,6 +74,8 @@
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
+ private AvalancheController mAvalancheController = new AvalancheController();
+
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
@@ -99,7 +98,7 @@
private BaseHeadsUpManager createHeadsUpManager() {
return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
- mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
+ mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, mAvalancheController);
}
private NotificationEntry createStickyEntry(int id) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index c032d7c..61a79d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -28,8 +28,8 @@
import android.content.Context;
import android.testing.TestableLooper;
-import androidx.test.filters.SmallTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -76,6 +76,7 @@
@Mock private UiEventLogger mUiEventLogger;
@Mock private JavaAdapter mJavaAdapter;
@Mock private ShadeInteractor mShadeInteractor;
+ private AvalancheController mAvalancheController = new AvalancheController();
private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
TestableHeadsUpManagerPhone(
@@ -92,7 +93,8 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor
+ ShadeInteractor shadeInteractor,
+ AvalancheController avalancheController
) {
super(
context,
@@ -109,7 +111,8 @@
accessibilityManagerWrapper,
uiEventLogger,
javaAdapter,
- shadeInteractor
+ shadeInteractor,
+ avalancheController
);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
@@ -131,12 +134,15 @@
mAccessibilityManagerWrapper,
mUiEventLogger,
mJavaAdapter,
- mShadeInteractor
+ mShadeInteractor,
+ mAvalancheController
);
}
@Before
public void setUp() {
+ // TODO(b/315362456) create separate test with the flag disabled
+ // then modify this file to test with the flag enabled
mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index 2747629..d8f77f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -43,9 +43,10 @@
GlobalSettings globalSettings,
SystemClock systemClock,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ AvalancheController avalancheController) {
super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
- executor, accessibilityManagerWrapper, uiEventLogger);
+ executor, accessibilityManagerWrapper, uiEventLogger, avalancheController);
mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index e06efe8..3d93654 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
@@ -135,7 +135,7 @@
}
@Test
- fun streamIsMuted_getStream_volumeZero() {
+ fun streamIsMuted_getStream_volumeMin() {
with(kosmos) {
testScope.runTest {
val model by collectLastValue(underTest.getAudioStream(audioStream))
@@ -166,7 +166,7 @@
}
@Test
- fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsZero() {
+ fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsMin() {
with(kosmos) {
testScope.runTest {
audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
@@ -184,7 +184,7 @@
}
@Test
- fun ringerModeVibrate_getRingerStream_volumeIsZero() {
+ fun ringerModeVibrate_getRingerStream_volumeIsMin() {
with(kosmos) {
testScope.runTest {
audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index 06ae220..7c6ab9d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -108,6 +108,7 @@
assertThat(values)
.containsExactly(
+ SpatialAudioEnabledModel.Unknown,
SpatialAudioEnabledModel.Disabled,
SpatialAudioEnabledModel.HeadTrackingEnabled,
SpatialAudioEnabledModel.SpatialAudioEnabled,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
index a1e4fca..79d3fe9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
@@ -30,28 +30,8 @@
private val underTest = VolumeSliderInteractor()
@Test
- fun translateValueToVolume() {
- assertThat(underTest.translateValueToVolume(30f, volumeRange)).isEqualTo(3)
- }
-
- @Test
- fun processVolumeToValue_muted_zero() {
- assertThat(underTest.processVolumeToValue(3, volumeRange, null, true)).isEqualTo(0)
- }
-
- @Test
- fun processVolumeToValue_currentValue_currentValue() {
- assertThat(underTest.processVolumeToValue(3, volumeRange, 30f, false)).isEqualTo(30f)
- }
-
- @Test
- fun processVolumeToValue_currentValueDiffersVolume_returnsTranslatedVolume() {
- assertThat(underTest.processVolumeToValue(1, volumeRange, 60f, false)).isEqualTo(10f)
- }
-
- @Test
- fun processVolumeToValue_currentValueDiffersNotEnoughVolume_returnsTranslatedVolume() {
- assertThat(underTest.processVolumeToValue(1, volumeRange, 12f, false)).isEqualTo(12f)
+ fun processVolumeToValue_returnsTranslatedVolume() {
+ assertThat(underTest.processVolumeToValue(2, volumeRange)).isEqualTo(20f)
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index 71866b3..82ce6d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -37,7 +37,7 @@
DefaultComponentsLayoutManager(
BOTTOM_BAR,
headerComponents = listOf(COMPONENT_1),
- footerComponents = listOf(COMPONENT_2),
+ footerComponents = listOf(COMPONENT_5, COMPONENT_2),
)
@Test
@@ -48,10 +48,18 @@
val component2 = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
val component3 = ComponentState(COMPONENT_3, kosmos.mockVolumePanelUiComponent, false)
val component4 = ComponentState(COMPONENT_4, kosmos.mockVolumePanelUiComponent, false)
+ val component5 = ComponentState(COMPONENT_5, kosmos.mockVolumePanelUiComponent, false)
val layout =
underTest.layout(
VolumePanelState(0, false, false),
- setOf(bottomBarComponentState, component1, component2, component3, component4)
+ setOf(
+ bottomBarComponentState,
+ component1,
+ component2,
+ component3,
+ component4,
+ component5,
+ )
)
Truth.assertThat(layout.bottomBarComponent).isEqualTo(bottomBarComponentState)
@@ -59,7 +67,7 @@
.containsExactlyElementsIn(listOf(component1))
.inOrder()
Truth.assertThat(layout.footerComponents)
- .containsExactlyElementsIn(listOf(component2))
+ .containsExactlyElementsIn(listOf(component5, component2))
.inOrder()
Truth.assertThat(layout.contentComponents)
.containsExactlyElementsIn(listOf(component3, component4))
@@ -85,5 +93,6 @@
const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
const val COMPONENT_3: VolumePanelComponentKey = "test_component:3"
const val COMPONENT_4: VolumePanelComponentKey = "test_component:4"
+ const val COMPONENT_5: VolumePanelComponentKey = "test_component:5"
}
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b8f20f6..e3a5e15 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1556,11 +1556,11 @@
<string name="volume_panel_noise_control_title">Noise Control</string>
<!-- Label for button to enabled/disable spatial audio [CHAR_LIMIT=30] -->
<string name="volume_panel_spatial_audio_title">Spatial Audio</string>
- <!-- Label for button to disable spatial audio [CHAR_LIMIT=20] -->
+ <!-- Label for a spatial audio button for the case when it is disabled [CHAR_LIMIT=20] -->
<string name="volume_panel_spatial_audio_off">Off</string>
- <!-- Label for button to enabled spatial audio [CHAR_LIMIT=20] -->
+ <!-- Label for a spatial audio button for the case when it is enabled without head tracking [CHAR_LIMIT=20] -->
<string name="volume_panel_spatial_audio_fixed">Fixed</string>
- <!-- Label for button to enabled head tracking [CHAR_LIMIT=20] -->
+ <!-- Label for a spatial audio button for the case when it is enabled with head tracking [CHAR_LIMIT=20] -->
<string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
<string name="volume_ringer_change">Tap to change ringer mode</string>
@@ -1576,6 +1576,18 @@
<string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring (<xliff:g id="volume level" example="56">%1$s</xliff:g>)</string>
+ <!-- An audible a11y label for a button, that opens settings when clicked [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_enter_media_output_settings">Enter output settings</string>
+ <!-- An audible a11y state description for a button, that expands volume sliders menu [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_expanded_sliders">Volume sliders expanded</string>
+ <!-- An audible a11y state description for a button, that collapses volume sliders menu [CHAR LIMIT=NONE] -->
+ <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string>
+
+ <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_hint_mute">mute %s</string>
+ <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] -->
+ <string name="volume_panel_hint_unmute">unmute %s</string>
+
<!-- Title with application label for media output settings. [CHAR LIMIT=20] -->
<string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string>
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 0ef3d20..a90d4b2 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 @@
iconResId = R.drawable.pip_ic_close_white
)
)
+
+ // Ensure this is unfocusable & uninteractable
+ isClickable = false
+ isFocusable = false
+ importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
+
// END DragToInteractView modification
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index d3e85e0..1f04599 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -113,13 +113,8 @@
/* Moves position without updating underlying percentage position. Can be animated. */
void moveToPosition(PointF position, boolean animateMovement) {
- if (Flags.floatingMenuImeDisplacementAnimation()) {
- moveToPositionX(position.x, animateMovement);
- moveToPositionY(position.y, animateMovement);
- } else {
- moveToPositionX(position.x, /* animateMovement = */ false);
- moveToPositionY(position.y, /* animateMovement = */ false);
- }
+ moveToPositionX(position.x, animateMovement);
+ moveToPositionY(position.y, animateMovement);
}
void moveToPositionX(float positionX) {
@@ -127,7 +122,7 @@
}
void moveToPositionX(float positionX, boolean animateMovement) {
- if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+ if (animateMovement) {
springMenuWith(DynamicAnimation.TRANSLATION_X,
createSpringForce(),
/* velocity = */ 0,
@@ -142,7 +137,7 @@
}
void moveToPositionY(float positionY, boolean animateMovement) {
- if (animateMovement && Flags.floatingMenuImeDisplacementAnimation()) {
+ if (animateMovement) {
springMenuWith(DynamicAnimation.TRANSLATION_Y,
createSpringForce(),
/* velocity = */ 0,
@@ -455,7 +450,7 @@
? MIN_PERCENT
: Math.min(MAX_PERCENT, position.y / draggableBounds.height());
- if (Flags.floatingMenuImeDisplacementAnimation() && !writeToPosition) {
+ if (!writeToPosition) {
mMenuView.onEdgeChangedIfNeeded();
} else {
mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
index e57323b..35fe6b1 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 @@
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 577bbc0..270fedc 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 @@
loadLayoutResources();
addView(mTargetFeaturesView);
+
+ setClickable(false);
+ setFocusable(false);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@Override
@@ -224,8 +228,7 @@
}
// We can skip animating if FAB is not visible
- if (Flags.floatingMenuImeDisplacementAnimation()
- && animateMovement && getVisibility() == VISIBLE) {
+ if (animateMovement && getVisibility() == VISIBLE) {
mMenuAnimationController.moveToPosition(position, /* animateMovement = */ true);
// onArrivalAtPosition() is called at the end of the animation.
} else {
@@ -331,7 +334,7 @@
mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked);
}
- if (Flags.floatingMenuOverlapsNavBarsFlag() && !Flags.floatingMenuAnimatedTuck()) {
+ if (!Flags.floatingMenuAnimatedTuck()) {
if (isMoveToTucked) {
final float halfWidth = getMenuWidth() / 2.0f;
final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 4865fce..760e1c3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -34,7 +34,6 @@
import androidx.annotation.DimenRes;
-import com.android.systemui.Flags;
import com.android.systemui.res.R;
import java.lang.annotation.Retention;
@@ -155,11 +154,6 @@
final int margin = getMenuMargin();
final Rect draggableBounds = new Rect(getWindowAvailableBounds());
- if (!Flags.floatingMenuOverlapsNavBarsFlag()) {
- // Initializes start position for mapping the translation of the menu view.
- draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0);
- }
-
draggableBounds.top += margin;
draggableBounds.right -= getMenuWidth();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index cd3b8a6..97e38f4 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 @@
if (Flags.floatingMenuAnimatedTuck()) {
setClipChildren(true);
}
+ setClickable(false);
+ setFocusable(false);
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
@Override
@@ -443,21 +446,18 @@
}
public void onMoveToTuckedChanged(boolean moveToTuck) {
- if (Flags.floatingMenuOverlapsNavBarsFlag()) {
- if (moveToTuck) {
- final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
- final int[] location = getLocationOnScreen();
- bounds.offset(
- location[0],
- location[1]
- );
+ if (moveToTuck) {
+ final Rect bounds = mMenuViewAppearance.getWindowAvailableBounds();
+ final int[] location = getLocationOnScreen();
+ bounds.offset(
+ location[0],
+ location[1]
+ );
- setClipBounds(bounds);
- }
- // Instead of clearing clip bounds when moveToTuck is false,
- // wait until the spring animation finishes.
+ setClipBounds(bounds);
}
- // Function is a no-operation if flag is disabled.
+ // Instead of clearing clip bounds when moveToTuck is false,
+ // wait until the spring animation finishes.
}
private void onSpringAnimationsEndAction() {
@@ -475,9 +475,7 @@
setClipBounds(null);
}
}
- if (Flags.floatingMenuImeDisplacementAnimation()) {
- mMenuView.onArrivalAtPosition(false);
- }
+ mMenuView.onArrivalAtPosition(false);
}
void dispatchAccessibilityAction(int id) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index bc9d1ff..6b1240b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -20,11 +20,9 @@
import android.content.Context;
import android.graphics.PixelFormat;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import com.android.systemui.Flags;
import com.android.systemui.util.settings.SecureSettings;
/**
@@ -88,14 +86,9 @@
params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
params.windowAnimations = android.R.style.Animation_Translucent;
// Insets are configured to allow the menu to display over navigation and system bars.
- if (Flags.floatingMenuOverlapsNavBarsFlag()) {
- params.setFitInsetsTypes(0);
- params.layoutInDisplayCutoutMode =
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- } else {
- params.setFitInsetsTypes(
- WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
- }
+ params.setFitInsetsTypes(0);
+ params.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
return params;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 9de71c1..8bd675c 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 @@
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 b87fadf..4d88f49 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 @@
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 @@
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 e48f05d..7bb75bf 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.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 @@
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 3469cfa..e457601 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 @@
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 9c28f1c..9949e4c 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 @@
/** 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 61aeffe..86b0b44 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.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 @@
*/
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 @@
* 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 @@
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 @@
promptSelectorInteractor.prompt
.map {
when {
- !customBiometricPrompt() || it == null -> ""
+ !(customBiometricPrompt() && constraintBp()) || it == null -> ""
it.logoDescription != null -> it.logoDescription
else ->
try {
@@ -329,7 +321,7 @@
/** 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 3092def..7f6fc91 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
@@ -177,7 +177,7 @@
)
.toMessage()
} else if (
- trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
+ trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredForUnattendedUpdate
) {
BouncerMessageStrings.authRequiredForUnattendedUpdate(
currentSecurityMode.toAuthModel()
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 c3d4cb3..7d3075a 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 7f4a029..e910a92 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 @@
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 3819e61..4f4f3d0 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 @@
}
};
+ private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ updateSensorRegistration();
+ }
+ };
+
private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@@ -176,6 +184,8 @@
mStatusBarStateController.addCallback(mStatusBarStateListener);
mState = mStatusBarStateController.getState();
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
mJavaAdapter.alwaysCollectFlow(
@@ -364,6 +374,24 @@
} 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 @@
logDebug("Starting Session");
mSessionStarted = true;
mFalsingDataProvider.setJustUnlockedWithFace(false);
- registerSensors();
mFalsingDataProvider.onSessionStarted();
}
}
@@ -380,7 +407,6 @@
if (mSessionStarted) {
logDebug("Ending Session");
mSessionStarted = false;
- unregisterSensors();
mFalsingDataProvider.onSessionEnd();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index c3c7411..98c8205 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal
+import android.provider.Settings
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.CoreStartable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -24,25 +25,32 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dock.DockManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.SystemSettings
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
/**
* A [CoreStartable] responsible for automatically navigating between communal scenes when certain
* conditions are met.
*/
-@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalSceneStartable
@Inject
@@ -50,9 +58,13 @@
private val dockManager: DockManager,
private val communalInteractor: CommunalInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val systemSettings: SystemSettings,
@Application private val applicationScope: CoroutineScope,
@Background private val bgScope: CoroutineScope,
) : CoreStartable {
+ private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT
+
override fun start() {
// Handle automatically switching based on keyguard state.
keyguardTransitionInteractor.startedKeyguardTransitionStep
@@ -78,6 +90,43 @@
// }
// }
// .launchIn(bgScope)
+
+ systemSettings
+ .observerFlow(Settings.System.SCREEN_OFF_TIMEOUT)
+ // Read the setting value on start.
+ .emitOnStart()
+ .onEach {
+ screenTimeout =
+ systemSettings.getInt(
+ Settings.System.SCREEN_OFF_TIMEOUT,
+ DEFAULT_SCREEN_TIMEOUT
+ )
+ }
+ .launchIn(bgScope)
+
+ // Handle timing out back to the dream.
+ bgScope.launch {
+ combine(
+ communalInteractor.desiredScene,
+ keyguardInteractor.isDreaming,
+ // Emit a value on start so the combine starts.
+ communalInteractor.userActivity.emitOnStart()
+ ) { scene, isDreaming, _ ->
+ // Time out should run whenever we're dreaming and the hub is open, even if not
+ // docked.
+ scene == CommunalScenes.Communal && isDreaming
+ }
+ // collectLatest cancels the previous action block when new values arrive, so any
+ // already running timeout gets cancelled when conditions change or user interaction
+ // is detected.
+ .collectLatest { shouldTimeout ->
+ if (!shouldTimeout) {
+ return@collectLatest
+ }
+ delay(screenTimeout.milliseconds)
+ communalInteractor.onSceneChanged(CommunalScenes.Blank)
+ }
+ }
}
private suspend fun determineSceneAfterTransition(
@@ -105,5 +154,6 @@
companion object {
val AWAKE_DEBOUNCE_DELAY = 5.seconds
val DOCK_DEBOUNCE_DELAY = 1.seconds
+ val DEFAULT_SCREEN_TIMEOUT = 15000
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 940b48c..52025b1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -63,10 +63,13 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -84,7 +87,7 @@
class CommunalInteractor
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
+ @Application val applicationScope: CoroutineScope,
broadcastDispatcher: BroadcastDispatcher,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
@@ -152,6 +155,14 @@
/** Transition state of the hub mode. */
val transitionState: StateFlow<ObservableTransitionState> = communalRepository.transitionState
+ val _userActivity: MutableSharedFlow<Unit> =
+ MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ val userActivity: Flow<Unit> = _userActivity.asSharedFlow()
+
+ fun signalUserInteraction() {
+ _userActivity.tryEmit(Unit)
+ }
+
/**
* Updates the transition state of the hub [SceneTransitionLayout].
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 85f3c20..c913300 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -45,6 +45,10 @@
val selectedKey: StateFlow<String?>
get() = _selectedKey
+ fun signalUserInteraction() {
+ communalInteractor.signalUserInteraction()
+ }
+
fun onSceneChanged(scene: SceneKey) {
communalInteractor.onSceneChanged(scene)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index a4011fd..1003050 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.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 @@
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 96171aa..d495fac 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 @@
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 @@
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 99bd25b..7733de4 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 @@
/** 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 a5f6f7c..8059993 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.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 @@
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 029a4f3..fa2421a 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 @@
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 @@
*/
private val isPassivelyAuthenticated =
merge(
- trustRepository.isCurrentUserTrusted,
- deviceEntryFaceAuthRepository.isAuthenticated,
+ trustInteractor.isTrusted,
+ faceAuthInteractor.authenticated,
)
.onStart { emit(false) }
@@ -134,6 +143,67 @@
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 @@
}
}
}
+
+ 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 fd6fbc9..98deda09 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 @@
private fun startUpdatingFaceHelpMessageDeferral() {
scope.launch {
- biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+ biometricSettingsInteractor.isFaceAuthEnrolledAndEnabled
.flatMapLatest { faceEnrolledAndEnabled ->
if (faceEnrolledAndEnabled) {
faceAcquired
@@ -94,7 +94,7 @@
}
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 3b94166..65f3eb7 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 @@
*/
@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 0c9fbc2..a7266503 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 @@
/** 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 0000000..5b672ac
--- /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 6cd94c6..1452526 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 @@
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 @@
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 6fa20de..1e3c604 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 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/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index f5f5571..882f231 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 @@
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 @@
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 0000000..2ff6e16
--- /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 08904b6..d6f3634 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 @@
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 @@
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/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
index 027a739..bf22563 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 @@
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 f4903f1..768bb8e 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.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 @@
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 @@
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
KeyguardStateController keyguardStateController,
ShadeViewController shadeViewController,
+ PanelExpansionInteractor panelExpansionInteractor,
NotificationRemoteInputManager notificationRemoteInputManager,
NotificationShadeDepthController notificationShadeDepthController,
@Main Handler mainHandler,
@@ -575,6 +578,7 @@
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mKeyguardStateController = keyguardStateController;
mShadeViewController = shadeViewController;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mNotificationRemoteInputManager = notificationRemoteInputManager;
mOverviewProxyService = overviewProxyService;
mNavigationModeController = navigationModeController;
@@ -749,7 +753,7 @@
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 c5190a2..1927f49 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.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 @@
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 void setComponents(ShadeViewController panel) {
- mPanelView = panel;
+ public void setComponents(ShadeViewController svc, PanelExpansionInteractor pei) {
+ mShadeViewController = svc;
+ mPanelExpansionInteractor = pei;
updatePanelSystemUiStateFlags();
}
@@ -750,10 +754,10 @@
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 @@
*/
void updateSlippery() {
setSlippery(!isQuickStepSwipeUpEnabled() ||
- (mPanelView != null && mPanelView.isFullyExpanded() && !mPanelView.isCollapsing()));
+ (mPanelExpansionInteractor != null && mPanelExpansionInteractor.isFullyExpanded()
+ && !mPanelExpansionInteractor.isCollapsing()));
}
void setSlippery(boolean slippery) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 35cac4b..1456747 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 @@
name = "handleClick";
if (mState.disabledByPolicy) {
Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
- mContext, mEnforcedAdmin);
+ mEnforcedAdmin);
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
} else {
mQSLogger.logHandleClick(mTileSpec, msg.arg1);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index bd66843..d82b175 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -41,6 +41,7 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.recordissue.IssueRecordingService
import com.android.systemui.recordissue.RecordIssueDialogDelegate
@@ -66,6 +67,7 @@
private val keyguardDismissUtil: KeyguardDismissUtil,
private val keyguardStateController: KeyguardStateController,
private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val panelInteractor: PanelInteractor,
private val userContextProvider: UserContextProvider,
private val delegateFactory: RecordIssueDialogDelegate.Factory,
) :
@@ -138,6 +140,8 @@
.create {
isRecording = true
startIssueRecordingService(it.screenRecord, it.winscopeTracing)
+ dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+ panelInteractor.collapsePanels()
refreshState()
}
.createDialog()
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 32deb30..6b654be 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.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 @@
(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 d1f8945..87b89ea 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 @@
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/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 8a84496..7009816 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -27,8 +27,10 @@
import android.util.Log
import androidx.core.content.FileProvider
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.dagger.qualifiers.LongRunning
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingController
import com.android.systemui.screenrecord.RecordingService
@@ -54,7 +56,9 @@
uiEventLogger: UiEventLogger,
notificationManager: NotificationManager,
userContextProvider: UserContextProvider,
- keyguardDismissUtil: KeyguardDismissUtil
+ keyguardDismissUtil: KeyguardDismissUtil,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val panelInteractor: PanelInteractor,
) :
RecordingService(
controller,
@@ -102,6 +106,8 @@
}
ACTION_SHARE -> {
shareRecording(intent)
+ dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
+ panelInteractor.collapsePanels()
// Unlike all other actions, action_share has different behavior for the screen
// recording qs tile than it does for the record issue qs tile. Return sticky to
@@ -124,13 +130,11 @@
FileSender.buildSendIntent(this, listOf(sharableUri))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- if (mNotificationId != NOTIF_BASE_ID) {
- mNotificationManager.cancelAsUser(
- null,
- mNotificationId,
- UserHandle(mUserContextTracker.userContext.userId)
- )
- }
+ mNotificationManager.cancelAsUser(
+ null,
+ mNotificationId,
+ UserHandle(mUserContextTracker.userContext.userId)
+ )
// TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
mKeyguardDismissUtil.executeWhenUnlocked(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index 1d9b755..1f6d212 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -22,7 +22,6 @@
import android.graphics.Bitmap
import android.graphics.Rect
import android.util.Log
-import android.view.Display
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.ScrollCaptureResponse
@@ -37,30 +36,33 @@
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(private val 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 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
@@ -86,6 +88,8 @@
init {
view.setUiEventLogger(logger)
+ view.setDefaultDisplay(displayId)
+ view.setFlags(flags)
addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
if (LogConfig.DEBUG_WINDOW) {
@@ -180,7 +184,7 @@
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 {
@@ -215,7 +219,7 @@
if (LogConfig.DEBUG_INPUT) {
Log.d(TAG, "onKeyEvent: $keyCode")
}
- onDismissRequested.invoke(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER)
+ onDismissRequested.invoke(SCREENSHOT_DISMISSED_OTHER)
return true
}
return false
@@ -224,10 +228,9 @@
)
}
- 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 9aaed9e..198a29c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -344,7 +344,7 @@
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
- mViewProxy = viewProxyFactory.getProxy(mContext, mUiEventLogger);
+ mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId);
mScreenshotHandler.setOnTimeoutRunnable(() -> {
if (DEBUG_UI) {
@@ -595,8 +595,6 @@
setWindowFocusable(false);
}
});
- mViewProxy.setFlags(mFlags);
- mViewProxy.setDefaultDisplay(mDisplayId);
if (DEBUG_WINDOW) {
Log.d(TAG, "setContentView: " + mViewProxy.getView());
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index d019660..182b889 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -25,16 +25,12 @@
import android.view.View
import android.view.ViewGroup
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 flags: FeatureFlags?
var packageName: String
var callbacks: ScreenshotView.ScreenshotViewCallback?
var screenshot: ScreenshotData?
@@ -73,6 +69,6 @@
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/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index a00c81d..cdb9abb 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 @@
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 861a2ed..539b0c2 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 @@
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.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 @@
private final UiEventLogger mUiEventLogger;
private final SeekableSliderHapticPlugin mBrightnessSliderHapticPlugin;
+ private final ActivityStarter mActivityStarter;
private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
@Override
@@ -84,11 +87,13 @@
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 @@
@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 @@
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 @@
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 c43d20c..92006a4 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.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 @@
* @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 a5a0ae7..288ff09 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 @@
@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 void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
- mEnforcedAdmin = admin;
+ void setAdminBlocker(AdminBlocker blocker) {
+ mAdminBlocker = blocker;
+ setEnabled(blocker == null);
+ }
+
+ interface AdminBlocker {
+ boolean block();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index de21a73..a343ded 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 @@
*/
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 @@
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 b67156f..c9140b5 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 @@
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 @@
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 @@
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 01118bd..bd96a33 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 @@
* 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 20f73b0..c5a6968 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 @@
@Inject
constructor(
sceneInteractor: SceneInteractor,
+ shadeInteractor: ShadeInteractor,
+ shadeAnimationInteractor: ShadeAnimationInteractor,
) : PanelExpansionInteractor {
/**
@@ -93,6 +95,16 @@
}
}
+ @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 5a777e8..134c983 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 @@
* 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 2a7658a..f364d6d 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 @@
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 @@
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 eaac8ae..d9982e3 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 @@
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 @@
}
}
.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 072f56d..dcfccd8 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 @@
return false;
}
- if (!Flags.notificationsBackgroundMediaIcons()) {
+ if (!Flags.notificationsBackgroundIcons()) {
inflateOrUpdateIcons(entry);
}
@@ -73,14 +73,14 @@
@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 @@
mIconsState.put(entry, STATE_ICONS_UNINFLATED);
}
- if (Flags.notificationsBackgroundMediaIcons()) {
+ if (Flags.notificationsBackgroundIcons()) {
if (isMediaNotification(entry.getSbn())) {
inflateOrUpdateIcons(entry);
}
@@ -120,7 +120,7 @@
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 6400ff6..4bbe035 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 @@
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 a5f42bb..a900e45 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.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 @@
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 @@
* @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 @@
@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 @@
})
?: 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 @@
}
}
- @Throws(InflationException::class)
- private fun createPeopleAvatar(entry: NotificationEntry): Icon? {
- var ic: Icon? = null
+ 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)
+ )
+ }
- val shortcut = entry.ranking.conversationShortcutInfo
- if (shortcut != null) {
- ic = launcherApps.getShortcutIcon(shortcut)
+ 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)
+ }
+ }
}
- // Fall back to extract from message
+ @Throws(InflationException::class)
+ private fun createPeopleAvatar(entry: NotificationEntry): Icon {
+ var ic: Icon? = null
+
+ 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)
+ }
+ }
+
+ // Try to extract from message
if (ic == null) {
val extras: Bundle = entry.sbn.notification.extras
val messages =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 2745817..5e87a7a 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 @@
if (shadeExpansion > 0f || qsExpansion > 0f) {
if (configurationBasedDimensions.useSplitShade) {
emit(1f)
+ } else if (qsExpansion == 1f) {
+ // Ensure HUNs will be visible in QS shade (at least while unlocked)
+ emit(1f)
} else {
// Fade as QS shade expands
emit(1f - qsExpansion)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a155e94..24be3db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -43,6 +43,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.AnimationStateHandler;
+import com.android.systemui.statusbar.policy.AvalancheController;
import com.android.systemui.statusbar.policy.BaseHeadsUpManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -124,9 +125,10 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
- ShadeInteractor shadeInteractor) {
+ ShadeInteractor shadeInteractor,
+ AvalancheController avalancheController) {
super(context, logger, handler, globalSettings, systemClock, executor,
- accessibilityManagerWrapper, uiEventLogger);
+ accessibilityManagerWrapper, uiEventLogger, avalancheController);
Resources resources = mContext.getResources();
mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
statusBarStateController.addCallback(mStatusBarStateListener);
@@ -279,7 +281,7 @@
if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
headsUpEntry.mRemoteInputActive = remoteInputActive;
if (remoteInputActive) {
- headsUpEntry.removeAutoRemovalCallbacks("setRemoteInputActive(true)");
+ headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
} else {
headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
}
@@ -482,7 +484,7 @@
this.mExpanded = expanded;
if (expanded) {
- removeAutoRemovalCallbacks("setExpanded(true)");
+ cancelAutoRemovalCallbacks("setExpanded(true)");
} else {
updateEntry(false /* updatePostTime */, "setExpanded(false)");
}
@@ -495,7 +497,7 @@
mGutsShownPinned = gutsShownPinned;
if (gutsShownPinned) {
- removeAutoRemovalCallbacks("setGutsShownPinned(true)");
+ cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
} else {
updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index d4960d7..712f65d 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 @@
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 8e9c038..5b14259 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.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 @@
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 @@
StatusBarNotificationPresenter(
Context context,
ShadeViewController panel,
+ PanelExpansionInteractor panelExpansionInteractor,
QuickSettingsController quickSettingsController,
HeadsUpManager headsUp,
NotificationShadeWindowView statusBarWindow,
@@ -134,6 +137,7 @@
mActivityStarter = activityStarter;
mKeyguardStateController = keyguardStateController;
mNotificationPanel = panel;
+ mPanelExpansionInteractor = panelExpansionInteractor;
mQsController = quickSettingsController;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
@@ -202,7 +206,7 @@
@Override
public boolean isCollapsing() {
- return mNotificationPanel.isCollapsing()
+ return mPanelExpansionInteractor.isCollapsing()
|| mNotificationShadeWindowController.isLaunchingActivity();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
new file mode 100644
index 0000000..6aaf5d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.policy
+
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import javax.inject.Inject
+
+/*
+ * Control when heads up notifications show during an avalanche where notifications arrive in fast
+ * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
+ */
+@SysUISingleton
+class AvalancheController @Inject constructor() {
+
+ private val tag = "AvalancheController"
+ private val debug = false
+
+ // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD
+ @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null
+
+ // List of runnables to run for the HUN showing right now
+ private var headsUpEntryShowingRunnableList: MutableList<Runnable> = ArrayList()
+
+ // HeadsUpEntry waiting to show
+ // Use sortable list instead of priority queue for debugging
+ private val nextList: MutableList<HeadsUpEntry> = ArrayList()
+
+ // Map of HeadsUpEntry waiting to show, and runnables to run when it shows.
+ // Use HashMap instead of SortedMap for faster lookup, and also because the ordering
+ // provided by HeadsUpEntry.compareTo is not consistent over time or with HeadsUpEntry.equals
+ @VisibleForTesting var nextMap: MutableMap<HeadsUpEntry, MutableList<Runnable>> = HashMap()
+
+ // Map of Runnable to label for debugging only
+ private val debugRunnableLabelMap: MutableMap<Runnable, String> = HashMap()
+
+ // HeadsUpEntry we did not show at all because they are not the top priority hun in their batch
+ // For debugging only
+ @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
+
+ /**
+ * Run or delay Runnable for given HeadsUpEntry
+ */
+ fun update(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ if (!NotificationThrottleHun.isEnabled) {
+ runnable.run()
+ return
+ }
+ val fn = "[$label] => AvalancheController.update ${getKey(entry)}"
+
+ if (debug) {
+ debugRunnableLabelMap[runnable] = label
+ }
+
+ if (isShowing(entry)) {
+ log {"$fn => [update showing]" }
+ runnable.run()
+ } else if (entry in nextMap) {
+ log { "$fn => [update next]" }
+ nextMap[entry]?.add(runnable)
+ } else if (headsUpEntryShowing == null) {
+ log { "$fn => [showNow]" }
+ showNow(entry, arrayListOf(runnable))
+ } else {
+ // Clean up invalid state when entry is in list but not map and vice versa
+ if (entry in nextMap) nextMap.remove(entry)
+ if (entry in nextList) nextList.remove(entry)
+
+ addToNext(entry, runnable)
+
+ // Shorten headsUpEntryShowing display time
+ val nextIndex = nextList.indexOf(entry)
+ val isOnlyNextEntry = nextIndex == 0 && nextList.size == 1
+ if (isOnlyNextEntry) {
+ // HeadsUpEntry.updateEntry recursively calls AvalancheController#update
+ // and goes to the isShowing case above
+ headsUpEntryShowing!!.updateEntry(false, "avalanche duration update")
+ }
+ }
+ logState("after $fn")
+ }
+
+ @VisibleForTesting
+ fun addToNext(entry: HeadsUpEntry, runnable: Runnable) {
+ nextMap[entry] = arrayListOf(runnable)
+ nextList.add(entry)
+ }
+
+ /**
+ * Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
+ * all Runnables associated with that entry.
+ */
+ fun delete(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+ if (!NotificationThrottleHun.isEnabled) {
+ runnable.run()
+ return
+ }
+ val fn = "[$label] => AvalancheController.delete " + getKey(entry)
+
+ if (entry in nextMap) {
+ log { "$fn => [remove from next]" }
+ if (entry in nextMap) nextMap.remove(entry)
+ if (entry in nextList) nextList.remove(entry)
+ } else if (entry in debugDropSet) {
+ log { "$fn => [remove from dropset]" }
+ debugDropSet.remove(entry)
+ } else if (isShowing(entry)) {
+ log { "$fn => [remove showing ${getKey(entry)}]" }
+ runnable.run()
+ showNext()
+ } else {
+ log { "$fn => [removing untracked ${getKey(entry)}]" }
+ }
+ logState("after $fn")
+ }
+
+ /**
+ * Returns true if given HeadsUpEntry is the last one tracked by AvalancheController. Used by
+ * BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration during active
+ * avalanche.
+ */
+ fun shortenDuration(entry: HeadsUpEntry): Boolean {
+ if (!NotificationThrottleHun.isEnabled) {
+ // Use default display duration, like we always did before AvalancheController existed
+ return false
+ }
+ val showingList: MutableList<HeadsUpEntry> = mutableListOf()
+ headsUpEntryShowing?.let { showingList.add(it) }
+ val allEntryList = showingList + nextList
+
+ // Shorten duration if not last entry
+ return allEntryList.indexOf(entry) != allEntryList.size - 1
+ }
+
+ /**
+ * Return true if entry is waiting to show.
+ */
+ fun isWaiting(key: String): Boolean {
+ if (!NotificationThrottleHun.isEnabled) {
+ return false
+ }
+ for (entry in nextMap.keys) {
+ if (entry.mEntry?.key.equals(key)) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Return list of keys for huns waiting
+ */
+ fun getWaitingKeys(): MutableList<String> {
+ if (!NotificationThrottleHun.isEnabled) {
+ return mutableListOf()
+ }
+ val keyList = mutableListOf<String>()
+ for (entry in nextMap.keys) {
+ entry.mEntry?.let { keyList.add(entry.mEntry!!.key) }
+ }
+ return keyList
+ }
+
+ private fun isShowing(entry: HeadsUpEntry): Boolean {
+ return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key
+ }
+
+ private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
+ log { "show " + getKey(entry) + " backlog size: " + runnableList.size }
+
+ headsUpEntryShowing = entry
+
+ runnableList.forEach {
+ if (it in debugRunnableLabelMap) {
+ log { "run runnable from: ${debugRunnableLabelMap[it]}" }
+ }
+ it.run()
+ }
+ }
+
+ private fun showNext() {
+ log { "showNext" }
+ headsUpEntryShowing = null
+
+ if (nextList.isEmpty()) {
+ log { "no more to show!" }
+ return
+ }
+
+ // Only show first (top priority) entry in next batch
+ nextList.sort()
+ headsUpEntryShowing = nextList[0]
+ headsUpEntryShowingRunnableList = nextMap[headsUpEntryShowing]!!
+
+ // Remove runnable labels for dropped huns
+ val listToDrop = nextList.subList(1, nextList.size)
+ if (debug) {
+ // Clear runnable labels
+ for (e in listToDrop) {
+ val runnableList = nextMap[e]!!
+ for (r in runnableList) {
+ debugRunnableLabelMap.remove(r)
+ }
+ }
+ debugDropSet.addAll(listToDrop)
+ }
+
+ clearNext()
+ showNow(headsUpEntryShowing!!, headsUpEntryShowingRunnableList)
+ }
+
+ fun clearNext() {
+ nextList.clear()
+ nextMap.clear()
+ }
+
+ // Methods below are for logging only ==========================================================
+
+ private inline fun log(s: () -> String) {
+ if (debug) {
+ Log.d(tag, s())
+ }
+ }
+
+ // TODO(b/315362456) expose as dumpable for bugreports
+ private fun logState(reason: String) {
+ log { "state $reason" }
+ log { "showing: " + getKey(headsUpEntryShowing) }
+ log { "next list: $nextListStr map: $nextMapStr" }
+ log { "drop: $dropSetStr" }
+ }
+
+ private val dropSetStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in debugDropSet) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ private val nextListStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in nextList) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ private val nextMapStr: String
+ get() {
+ val queue = ArrayList<String>()
+ for (entry in nextMap.keys) {
+ queue.add(getKey(entry))
+ }
+ return java.lang.String.join(" ", queue)
+ }
+
+ fun getKey(entry: HeadsUpEntry?): String {
+ if (entry == null) {
+ return "null"
+ }
+ if (entry.mEntry == null) {
+ return entry.toString()
+ }
+ return entry.mEntry!!.key
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 530e49c..05cc73e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -45,6 +45,8 @@
import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import java.util.stream.Stream;
/**
@@ -68,6 +70,7 @@
private final AccessibilityManagerWrapper mAccessibilityMgr;
private final UiEventLogger mUiEventLogger;
+ private final AvalancheController mAvalancheController;
protected final SystemClock mSystemClock;
protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
@@ -100,13 +103,15 @@
SystemClock systemClock,
@Main DelayableExecutor executor,
AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ AvalancheController avalancheController) {
mLogger = logger;
mExecutor = executor;
mSystemClock = systemClock;
mContext = context;
mAccessibilityMgr = accessibilityManagerWrapper;
mUiEventLogger = uiEventLogger;
+ mAvalancheController = avalancheController;
Resources resources = context.getResources();
mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time);
mStickyForSomeTimeAutoDismissTime = resources.getInteger(
@@ -157,18 +162,26 @@
*/
@Override
public void showNotification(@NonNull NotificationEntry entry) {
- mLogger.logShowNotification(entry);
-
- // Add new entry and begin managing it
HeadsUpEntry headsUpEntry = createHeadsUpEntry();
- headsUpEntry.setEntry(entry);
- mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
- onEntryAdded(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- entry.setIsHeadsUpEntry(true);
- updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
- entry.setInterruption();
+ // Attach NotificationEntry for AvalancheController to log key and
+ // record mPostTime for AvalancheController sorting
+ headsUpEntry.setEntry(entry);
+
+ Runnable runnable = () -> {
+ // TODO(b/315362456) log outside runnable too
+ mLogger.logShowNotification(entry);
+
+ // Add new entry and begin managing it
+ mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+ onEntryAdded(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ entry.setIsHeadsUpEntry(true);
+
+ updateNotificationInternal(entry.getKey(), true /* shouldHeadsUpAgain */);
+ entry.setInterruption();
+ };
+ mAvalancheController.update(headsUpEntry, runnable, "showNotification");
}
/**
@@ -181,6 +194,11 @@
@Override
public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
mLogger.logRemoveNotification(key, releaseImmediately);
+
+ if (mAvalancheController.isWaiting(key)) {
+ removeEntry(key);
+ return true;
+ }
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
if (headsUpEntry == null) {
return true;
@@ -203,6 +221,14 @@
*/
public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ Runnable runnable = () -> {
+ updateNotificationInternal(key, shouldHeadsUpAgain);
+ };
+ mAvalancheController.update(headsUpEntry, runnable, "updateNotification");
+ }
+
+ private void updateNotificationInternal(@NonNull String key, boolean shouldHeadsUpAgain) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
if (headsUpEntry == null) {
// the entry was released before this update (i.e by a listener) This can happen
@@ -231,12 +257,16 @@
for (String key : keysToRemove) {
removeEntry(key);
}
+ for (String key : mAvalancheController.getWaitingKeys()) {
+ removeEntry(key);
+ }
}
/**
* Returns the entry if it is managed by this manager.
* @param key key of notification
* @return the entry
+ * TODO(b/315362456) See if caller needs to check AvalancheController waiting entries
*/
@Nullable
public NotificationEntry getEntry(@NonNull String key) {
@@ -251,6 +281,7 @@
@NonNull
@Override
public Stream<NotificationEntry> getAllEntries() {
+ // TODO(b/315362456) See if callers need to check AvalancheController
return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
}
@@ -267,7 +298,7 @@
* @return true if the notification is managed by this manager
*/
public boolean isHeadsUpEntry(@NonNull String key) {
- return mHeadsUpEntryMap.containsKey(key);
+ return mHeadsUpEntryMap.containsKey(key) || mAvalancheController.isWaiting(key);
}
/**
@@ -331,7 +362,7 @@
* Manager-specific logic that should occur when an entry is added.
* @param headsUpEntry entry added
*/
- protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
+ void onEntryAdded(HeadsUpEntry headsUpEntry) {
NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(true);
@@ -349,20 +380,24 @@
*/
protected final void removeEntry(@NonNull String key) {
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
- if (headsUpEntry == null) {
- return;
- }
- NotificationEntry entry = headsUpEntry.mEntry;
- // If the notification is animating, we will remove it at the end of the animation.
- if (entry != null && entry.isExpandAnimationRunning()) {
- return;
- }
- entry.demoteStickyHun();
- mHeadsUpEntryMap.remove(key);
- onEntryRemoved(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- headsUpEntry.reset();
+ Runnable runnable = () -> {
+ if (headsUpEntry == null) {
+ return;
+ }
+ NotificationEntry entry = headsUpEntry.mEntry;
+
+ // If the notification is animating, we will remove it at the end of the animation.
+ if (entry != null && entry.isExpandAnimationRunning()) {
+ return;
+ }
+ entry.demoteStickyHun();
+ mHeadsUpEntryMap.remove(key);
+ onEntryRemoved(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ headsUpEntry.reset();
+ };
+ mAvalancheController.delete(headsUpEntry, runnable, "removeEntry");
}
/**
@@ -380,7 +415,7 @@
}
}
- protected void updatePinnedMode() {
+ private void updatePinnedMode() {
boolean hasPinnedNotification = hasPinnedNotificationInternal();
if (hasPinnedNotification == mHasPinnedNotification) {
return;
@@ -416,7 +451,9 @@
* Snoozes all current Heads Up Notifications.
*/
public void snooze() {
- for (String key : mHeadsUpEntryMap.keySet()) {
+ List<String> keySet = new ArrayList<>(mHeadsUpEntryMap.keySet());
+ keySet.addAll(mAvalancheController.getWaitingKeys());
+ for (String key : keySet) {
HeadsUpEntry entry = getHeadsUpEntry(key);
String packageName = entry.mEntry.getSbn().getPackageName();
String snoozeKey = snoozeKey(packageName, mUser);
@@ -432,6 +469,7 @@
@Nullable
protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
+ // TODO(b/315362456) See if callers need to check AvalancheController
return (HeadsUpEntry) mHeadsUpEntryMap.get(key);
}
@@ -515,18 +553,22 @@
*/
public void unpinAll(boolean userUnPinned) {
for (String key : mHeadsUpEntryMap.keySet()) {
- HeadsUpEntry entry = getHeadsUpEntry(key);
- setEntryPinned(entry, false /* isPinned */);
- // maybe it got un sticky
- entry.updateEntry(false /* updatePostTime */, "unpinAll");
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
- // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
- // on the screen.
- if (userUnPinned && entry.mEntry != null) {
- if (entry.mEntry.mustStayOnScreen()) {
- entry.mEntry.setHeadsUpIsVisible();
+ Runnable runnable = () -> {
+ setEntryPinned(headsUpEntry, false /* isPinned */);
+ // maybe it got un sticky
+ headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
+
+ // when the user unpinned all of HUNs by moving one HUN, all of HUNs should not stay
+ // on the screen.
+ if (userUnPinned && headsUpEntry.mEntry != null) {
+ if (headsUpEntry.mEntry.mustStayOnScreen()) {
+ headsUpEntry.mEntry.setHeadsUpIsVisible();
+ }
}
- }
+ };
+ mAvalancheController.delete(headsUpEntry, runnable, "unpinAll");
}
}
@@ -606,6 +648,7 @@
*/
@Override
public boolean isSticky(String key) {
+ // TODO(b/315362456) See if callers need to check AvalancheController
HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
if (headsUpEntry != null) {
return headsUpEntry.isSticky();
@@ -633,9 +676,10 @@
/**
* This represents a notification and how long it is in a heads up mode. It also manages its
- * lifecycle automatically when created.
+ * lifecycle automatically when created. This class is public because it is exposed by methods
+ * of AvalancheController that take it as param.
*/
- protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+ public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
public boolean mRemoteInputActive;
public boolean mUserActionMayIndirectlyRemove;
@@ -672,27 +716,41 @@
}
/**
+ * An interface that returns the amount of time left this HUN should show.
+ */
+ interface FinishTimeUpdater {
+ long updateAndGetTimeRemaining();
+ }
+
+ /**
* Updates an entry's removal time.
* @param updatePostTime whether or not to refresh the post time
*/
public void updateEntry(boolean updatePostTime, @Nullable String reason) {
- mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
+ Runnable runnable = () -> {
+ mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
- final long now = mSystemClock.elapsedRealtime();
- mEarliestRemovalTime = now + mMinimumDisplayTime;
+ final long now = mSystemClock.elapsedRealtime();
+ mEarliestRemovalTime = now + mMinimumDisplayTime;
- if (updatePostTime) {
- mPostTime = Math.max(mPostTime, now);
- }
+ if (updatePostTime) {
+ mPostTime = Math.max(mPostTime, now);
+ }
+ };
+ mAvalancheController.update(this, runnable, "updateEntry (updatePostTime)");
if (isSticky()) {
- removeAutoRemovalCallbacks("updateEntry (sticky)");
+ cancelAutoRemovalCallbacks("updateEntry (sticky)");
return;
}
- final long finishTime = calculateFinishTime();
- final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
- scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
+ FinishTimeUpdater finishTimeCalculator = () -> {
+ final long finishTime = calculateFinishTime();
+ final long now = mSystemClock.elapsedRealtime();
+ final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+ return timeLeft;
+ };
+ scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)");
}
/**
@@ -758,12 +816,31 @@
}
}
+ @Override
+ public int hashCode() {
+ if (mEntry == null) return super.hashCode();
+ int result = mEntry.getKey().hashCode();
+ result = 31 * result;
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof HeadsUpEntry)) return false;
+ HeadsUpEntry otherHeadsUpEntry = (HeadsUpEntry) o;
+ if (mEntry != null && otherHeadsUpEntry.mEntry != null) {
+ return mEntry.getKey().equals(otherHeadsUpEntry.mEntry.getKey());
+ }
+ return false;
+ }
+
public void setExpanded(boolean expanded) {
this.mExpanded = expanded;
}
public void reset() {
- removeAutoRemovalCallbacks("reset()");
+ cancelAutoRemovalCallbacks("reset()");
mEntry = null;
mRemoveRunnable = null;
mExpanded = false;
@@ -773,37 +850,48 @@
/**
* Clear any pending removal runnables.
*/
- public void removeAutoRemovalCallbacks(@Nullable String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
+ public void cancelAutoRemovalCallbacks(@Nullable String reason) {
+ Runnable runnable = () -> {
+ final boolean removed = cancelAutoRemovalCallbackInternal();
- if (removed) {
- mLogger.logAutoRemoveCanceled(mEntry, reason);
- }
+ if (removed) {
+ mLogger.logAutoRemoveCanceled(mEntry, reason);
+ }
+ };
+ mAvalancheController.update(this, runnable,
+ reason + " removeAutoRemovalCallbacks");
}
- public void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
- if (mRemoveRunnable == null) {
- Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
- return;
- }
+ public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator,
+ @NonNull String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
+ Runnable runnable = () -> {
+ long delayMs = finishTimeCalculator.updateAndGetTimeRemaining();
- if (removed) {
- mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
- } else {
- mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
- }
+ if (mRemoveRunnable == null) {
+ Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+ return;
+ }
- mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
- delayMillis);
+ final boolean deletedExistingRemovalRunnable = cancelAutoRemovalCallbackInternal();
+ mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
+ delayMs);
+
+ if (deletedExistingRemovalRunnable) {
+ mLogger.logAutoRemoveRescheduled(mEntry, delayMs, reason);
+ } else {
+ mLogger.logAutoRemoveScheduled(mEntry, delayMs, reason);
+ }
+ };
+ mAvalancheController.update(this, runnable,
+ reason + " scheduleAutoRemovalCallback");
}
- public boolean removeAutoRemovalCallbackInternal() {
+ public boolean cancelAutoRemovalCallbackInternal() {
final boolean scheduled = (mCancelRemoveRunnable != null);
if (scheduled) {
- mCancelRemoveRunnable.run();
+ mCancelRemoveRunnable.run(); // Delete removal runnable from Executor queue
mCancelRemoveRunnable = null;
}
@@ -815,8 +903,12 @@
*/
public void removeAsSoonAsPossible() {
if (mRemoveRunnable != null) {
- final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
- scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
+
+ FinishTimeUpdater finishTimeCalculator = () -> {
+ final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
+ return timeLeft;
+ };
+ scheduleAutoRemovalCallback(finishTimeCalculator, "removeAsSoonAsPossible");
}
}
@@ -834,9 +926,15 @@
* {@link SystemClock#elapsedRealtime()}
*/
protected long calculateFinishTime() {
- final long duration = getRecommendedHeadsUpTimeoutMs(
- isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
-
+ int requestedTimeOutMs;
+ if (isStickyForSomeTime()) {
+ requestedTimeOutMs = mStickyForSomeTimeAutoDismissTime;
+ } else if (mAvalancheController.shortenDuration(this)) {
+ requestedTimeOutMs = 1000;
+ } else {
+ requestedTimeOutMs = mAutoDismissTime;
+ }
+ final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
return mPostTime + duration;
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index cabe831..d10554f 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 @@
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/spatial/domain/SpatialAudioAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
index 71bce5e..9d74c58 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
@@ -18,11 +18,12 @@
import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
@VolumePanelScope
class SpatialAudioAvailabilityCriteria
@@ -31,5 +32,11 @@
ComponentAvailabilityCriteria {
override fun isAvailable(): Flow<Boolean> =
- interactor.isAvailable.map { it is SpatialAudioAvailabilityModel.SpatialAudio }
+ combine(interactor.isAvailable, interactor.isEnabled) { isAvailable, isEnabled ->
+ if (isAvailable is SpatialAudioAvailabilityModel.SpatialAudio) {
+ isEnabled !is SpatialAudioEnabledModel.Unknown
+ } else {
+ false
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index 6032bfe..a32b75a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -115,7 +115,7 @@
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
- SpatialAudioEnabledModel.Disabled,
+ SpatialAudioEnabledModel.Unknown,
)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
index 9735e5c..4255a40 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
@@ -39,4 +39,7 @@
/** Head tracking is enabled. This also means that [SpatialAudioEnabled]. */
data object HeadTrackingEnabled : SpatialAudioEnabled
+
+ /** Spatial audio enabled state is unknown. */
+ data object Unknown : SpatialAudioEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
index 0c91bbf..ecd89ea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
@@ -24,75 +24,24 @@
class VolumeSliderInteractor @Inject constructor() {
/** mimic percentage volume setting */
- val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
+ private val displayValueRange: ClosedFloatingPointRange<Float> = 0f..100f
/**
* Translates [volume], that belongs to [volumeRange] to the value that belongs to
* [displayValueRange].
- *
- * [currentValue] is the raw value received from the slider. Returns [currentValue] when it
- * translates to the same volume as [volume] parameter. This ensures smooth slider experience
- * (avoids snapping when the user stops dragging).
*/
fun processVolumeToValue(
volume: Int,
volumeRange: ClosedRange<Int>,
- currentValue: Float?,
- isMuted: Boolean,
): Float {
- if (isMuted) {
- return 0f
- }
- val changedVolume: Int? = currentValue?.let { translateValueToVolume(it, volumeRange) }
- return if (volume != volumeRange.start && volume == changedVolume) {
- currentValue
- } else {
- translateToRange(
- currentValue = volume.toFloat(),
- currentRangeStart = volumeRange.start.toFloat(),
- currentRangeEnd = volumeRange.endInclusive.toFloat(),
- targetRangeStart = displayValueRange.start,
- targetRangeEnd = displayValueRange.endInclusive,
- )
- }
- }
-
- /** Translates [value] from [displayValueRange] to volume that has [volumeRange]. */
- fun translateValueToVolume(
- value: Float,
- volumeRange: ClosedRange<Int>,
- ): Int {
- return translateToRange(
- currentValue = value,
- currentRangeStart = displayValueRange.start,
- currentRangeEnd = displayValueRange.endInclusive,
- targetRangeStart = volumeRange.start.toFloat(),
- targetRangeEnd = volumeRange.endInclusive.toFloat(),
- )
- .toInt()
- }
-
- /**
- * Translates a value from one range to another.
- *
- * ```
- * Given: currentValue=3, currentRange=[0, 8], targetRange=[0, 100]
- * Result: 37.5
- * ```
- */
- private fun translateToRange(
- currentValue: Float,
- currentRangeStart: Float,
- currentRangeEnd: Float,
- targetRangeStart: Float,
- targetRangeEnd: Float,
- ): Float {
- val currentRangeLength: Float = (currentRangeEnd - currentRangeStart)
- val targetRangeLength: Float = targetRangeEnd - targetRangeStart
+ val currentRangeStart: Float = volumeRange.start.toFloat()
+ val targetRangeStart: Float = displayValueRange.start
+ val currentRangeLength: Float = (volumeRange.endInclusive.toFloat() - currentRangeStart)
+ val targetRangeLength: Float = displayValueRange.endInclusive - targetRangeStart
if (currentRangeLength == 0f || targetRangeLength == 0f) {
return 0f
}
- val volumeFraction: Float = (currentValue - currentRangeStart) / currentRangeLength
+ val volumeFraction: Float = (volume.toFloat() - currentRangeStart) / currentRangeLength
return targetRangeStart + volumeFraction * targetRangeLength
}
}
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 532e517..1b73208 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
@@ -28,6 +28,7 @@
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -71,50 +72,49 @@
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
)
- private var value = 0f
override val slider: StateFlow<SliderState> =
combine(
audioVolumeInteractor.getAudioStream(audioStream),
audioVolumeInteractor.canChangeVolume(audioStream),
audioVolumeInteractor.ringerMode,
) { model, isEnabled, ringerMode ->
- model.toState(value, isEnabled, ringerMode)
+ model.toState(isEnabled, ringerMode)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
- override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+ override fun onValueChanged(state: SliderState, newValue: Float) {
val audioViewModel = state as? State
audioViewModel ?: return
coroutineScope.launch {
- value = newValue
- val volume =
- volumeSliderInteractor.translateValueToVolume(
- newValue,
- audioViewModel.audioStreamModel.volumeRange
- )
- audioVolumeInteractor.setVolume(audioStream, volume)
+ audioVolumeInteractor.setVolume(audioStream, newValue.roundToInt())
+ }
+ }
+
+ override fun toggleMuted(state: SliderState) {
+ val audioViewModel = state as? State
+ audioViewModel ?: return
+ coroutineScope.launch {
+ audioVolumeInteractor.setMuted(audioStream, !audioViewModel.audioStreamModel.isMuted)
}
}
private fun AudioStreamModel.toState(
- value: Float,
isEnabled: Boolean,
ringerMode: RingerMode,
): State {
return State(
- value =
- volumeSliderInteractor.processVolumeToValue(
- volume,
- volumeRange,
- value,
- isMuted,
+ value = volume.toFloat(),
+ valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
+ valueText =
+ SliderViewModel.formatValue(
+ volumeSliderInteractor.processVolumeToValue(volume, volumeRange)
),
- valueRange = volumeSliderInteractor.displayValueRange,
icon = getIcon(ringerMode),
label = labelsByStream[audioStream]?.let(context::getString)
?: error("No label for the stream: $audioStream"),
disabledMessage = disabledTextByStream[audioStream]?.let(context::getString),
isEnabled = isEnabled,
+ a11yStep = volumeRange.step,
audioStreamModel = this,
)
}
@@ -156,8 +156,10 @@
override val valueRange: ClosedFloatingPointRange<Float>,
override val icon: Icon,
override val label: String,
+ override val valueText: String,
override val disabledMessage: String?,
override val isEnabled: Boolean,
+ override val a11yStep: Int,
val audioStreamModel: AudioStreamModel,
) : SliderState
@@ -165,8 +167,10 @@
override val value: Float = 0f
override val valueRange: ClosedFloatingPointRange<Float> = 0f..1f
override val icon: Icon? = null
+ override val valueText: String = ""
override val label: String = ""
override val disabledMessage: String? = null
+ override val a11yStep: Int = 0
override val isEnabled: Boolean = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index ae93826..86b2d73 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -26,8 +26,8 @@
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -46,46 +46,46 @@
) : SliderViewModel {
private val volumeRange = 0..routingSession.routingSessionInfo.volumeMax
- private val value = MutableStateFlow(0f)
override val slider: StateFlow<SliderState> =
- combine(value, mediaOutputInteractor.currentConnectedDevice) { value, _ ->
- getCurrentState(value)
- }
- .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState(value.value))
+ combine(mediaOutputInteractor.currentConnectedDevice) { _ -> getCurrentState() }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState())
- override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+ override fun onValueChanged(state: SliderState, newValue: Float) {
coroutineScope.launch {
- value.value = newValue
- castVolumeInteractor.setVolume(
- routingSession,
- volumeSliderInteractor.translateValueToVolume(newValue, volumeRange),
- )
+ castVolumeInteractor.setVolume(routingSession, newValue.roundToInt())
}
}
- private fun getCurrentState(value: Float): State {
- return State(
- value =
- volumeSliderInteractor.processVolumeToValue(
- volume = routingSession.routingSessionInfo.volume,
- volumeRange = volumeRange,
- currentValue = value,
- isMuted = false,
- ),
- valueRange = volumeSliderInteractor.displayValueRange,
+ override fun toggleMuted(state: SliderState) {
+ // do nothing because this action isn't supported for Cast sliders.
+ }
+
+ private fun getCurrentState(): State =
+ State(
+ value = routingSession.routingSessionInfo.volume.toFloat(),
+ valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
icon = Icon.Resource(R.drawable.ic_cast, null),
+ valueText =
+ SliderViewModel.formatValue(
+ volumeSliderInteractor.processVolumeToValue(
+ volume = routingSession.routingSessionInfo.volume,
+ volumeRange = volumeRange,
+ )
+ ),
label = context.getString(R.string.media_device_cast),
isEnabled = true,
+ a11yStep = 1
)
- }
private data class State(
override val value: Float,
override val valueRange: ClosedFloatingPointRange<Float>,
override val icon: Icon,
+ override val valueText: String,
override val label: String,
override val isEnabled: Boolean,
+ override val a11yStep: Int,
) : SliderState {
override val disabledMessage: String?
get() = null
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
index 6e9794b..b87d0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt
@@ -27,7 +27,13 @@
val value: Float
val valueRange: ClosedFloatingPointRange<Float>
val icon: Icon?
- val label: String
- val disabledMessage: String?
val isEnabled: Boolean
+ val valueText: String
+ val label: String
+ /**
+ * A11y slider controls works by adjusting one step up or down. The default slider step isn't
+ * enough to trigger rounding to the correct value.
+ */
+ val a11yStep: Int
+ val disabledMessage: String?
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
index 0c4b322..e78f833 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -23,5 +23,12 @@
val slider: StateFlow<SliderState>
- fun onValueChangeFinished(state: SliderState, newValue: Float)
+ fun onValueChanged(state: SliderState, newValue: Float)
+
+ fun toggleMuted(state: SliderState)
+
+ companion object {
+
+ fun formatValue(value: Float): String = "%.0f".format(value)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
index 7fd9c8a..635191a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/layout/DefaultComponentsLayoutManager.kt
@@ -46,12 +46,18 @@
!footerComponents.contains(it.key) &&
it.key != bottomBar
}
- val headerComponents = components.filter { headerComponents.contains(it.key) }
- val footerComponents = components.filter { footerComponents.contains(it.key) }
+ val headerComponents =
+ components
+ .filter { it.key in headerComponents }
+ .sortedBy { headerComponents.indexOf(it.key) }
+ val footerComponents =
+ components
+ .filter { it.key in footerComponents }
+ .sortedBy { footerComponents.indexOf(it.key) }
return ComponentsLayout(
- headerComponents = headerComponents.sortedBy { it.key },
+ headerComponents = headerComponents,
contentComponents = contentComponents.sortedBy { it.key },
- footerComponents = footerComponents.sortedBy { it.key },
+ footerComponents = footerComponents,
bottomBarComponent = components.find { it.key == bottomBar }
?: error(
"VolumePanelComponents.BOTTOM_BAR must be present in the default " +
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index de795a7..f76957f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -308,20 +308,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
- public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() {
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
- final PointF beforePosition = mMenuView.getMenuPosition();
-
- dispatchShowingImeInsets();
-
- final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
- assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
- assertThat(menuBottom).isLessThan(beforePosition.y);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
final PointF beforePosition = mMenuView.getMenuPosition();
@@ -337,19 +323,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
- public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() {
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
- final PointF beforePosition = mMenuView.getMenuPosition();
-
- dispatchHidingImeInsets();
-
- assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
- assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
- }
-
- @Test
- @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION)
public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200));
final PointF beforePosition = mMenuView.getMenuPosition();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 10b86ea..2b4e9ec 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.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 @@
@Test
fun testShowCredentialUI_withDescription() {
- mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val container = initializeFingerprintContainer(
authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
@@ -397,6 +395,7 @@
}
@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 7b972d3..81d4e83 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 @@
@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 140849b..7db4ca9 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 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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
@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 @@
fun logoDescriptionSetByApp() =
runGenericTest(logoDescription = logoDescriptionFromApp) {
mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP)
val logoDescription by collectLastValue(viewModel.logoDescription)
assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
}
+ @Test
+ fun iconViewLoaded() = runGenericTest {
+ val isIconViewLoaded by collectLastValue(viewModel.isIconViewLoaded)
+ // TODO(b/328677869): Add test for noIcon logic.
+ assertThat(isIconViewLoaded).isFalse()
+
+ viewModel.setIsIconViewLoaded(true)
+
+ assertThat(isIconViewLoaded).isTrue()
+ }
+
/** Asserts that the selected buttons are visible now. */
private suspend fun TestScope.assertButtonsVisible(
tryAgain: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 3f13033..45d20dc 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 @@
}
@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 e1e9fcb..dac88a3 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 @@
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 0000000..decbdaf
--- /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 31746a2..b38d5e3 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.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 @@
() -> 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/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index 3122edb..761c411 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
import com.android.systemui.settings.UserContextProvider
@@ -66,6 +67,7 @@
@Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var dialogLauncherAnimator: DialogTransitionAnimator
+ @Mock private lateinit var panelInteractor: PanelInteractor
@Mock private lateinit var userContextProvider: UserContextProvider
@Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
@Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
@@ -96,6 +98,7 @@
keyguardDismissUtil,
keyguardStateController,
dialogLauncherAnimator,
+ panelInteractor,
userContextProvider,
delegateFactory,
)
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 ab90b9b..25ba09a 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.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 @@
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 @@
mFalsingManager,
uiEventLogger,
SeekableSliderHapticPlugin(vibratorHelper, systemClock),
+ activityStarter,
)
mController.init()
mController.setOnChangedListener(listener)
@@ -120,7 +124,7 @@
@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 617b25d..88b239a 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 @@
// 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 b548117..e90a3ac8 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 @@
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 @@
}
@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 @@
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 @@
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 a12806b..4ac9dc2 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.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 @@
@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 @@
`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 @@
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 @@
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 @@
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(largeIc)
}
@@ -129,6 +151,7 @@
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
}
@@ -143,6 +166,7 @@
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(smallIc)
}
@@ -161,6 +185,7 @@
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 @@
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 @@
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 718f998..3b78b7e 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 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.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 @@
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 @@
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 cdbbc93..f947640 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 @@
// 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 @@
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 @@
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 @@
// 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 @@
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 @@
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 b0404a0..a8c5fc3 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.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 @@
mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(
mContext,
shadeViewController,
+ mock(PanelExpansionInteractor.class),
mock(QuickSettingsController.class),
mock(HeadsUpManager.class),
notificationShadeWindowView,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/FakeSystemUiModule.kt
index 6dd8d07..0660d00 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 @@
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 @@
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 3428553..8001604 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 @@
*
* 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 69b769e..bc0bf9d 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.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 @@
CoroutineTestScopeModule::class,
FakeSystemUiModule::class,
SceneContainerFrameworkModule::class,
+ FaceWakeUpTriggersConfigModule::class,
]
)
interface SysUITestModule {
@@ -69,6 +73,11 @@
@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 62a1aa9..3d84291 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 @@
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.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.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.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 @@
@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 @@
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 68ef555..8a95136 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 @@
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 @@
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 bd30fb4..60d61ac 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 @@
)
}
+ /** 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 c3af437..2e2cf9a 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 @@
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 1c8190e..fbb8ea2 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 @@
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 66c6f86..ebed922 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 @@
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 0d1a31f..e73e295 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 @@
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 @@
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 0000000..2f30d34
--- /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 365d97f..d6f2f77 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.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 0000000..0ebf164
--- /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 0b13858..b34681a 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 @@
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.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 2a4dd3a..09c8f87 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 @@
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 d2dd200..6d24e2a 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 @@
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 d3a8e0c..0950f04 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 @@
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 3239175..004f37c 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -528,7 +528,7 @@
*/
public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension(
int extensionType) {
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
// Basic extensions are deprecated starting with extension version 1.5
return new Pair<>(new PreviewExtenderImpl() {
@@ -713,7 +713,7 @@
* @hide
*/
public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) {
- if (Flags.concertMode()) {
+ if (Flags.concertModeApi()) {
if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
if (EFV_SUPPORTED) {
return new EyesFreeVideographyAdvancedExtenderImpl();
diff --git a/ravenwood/README.md b/ravenwood/README.md
index 9c4fda7..8cafb43 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/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 4be303a..2d531e7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1706,20 +1706,101 @@
}
@Override
- @RequiresPermission(Manifest.permission.STATUS_BAR_SERVICE)
+ @RequiresPermission(allOf = {
+ Manifest.permission.STATUS_BAR_SERVICE,
+ Manifest.permission.MANAGE_ACCESSIBILITY
+ })
public void notifyQuickSettingsTilesChanged(
- @UserIdInt int userId, List<ComponentName> tileComponentNames) {
- mSecurityPolicy.enforceCallingPermission(
+ @UserIdInt int userId, @NonNull List<ComponentName> tileComponentNames) {
+ if (!android.view.accessibility.Flags.a11yQsShortcut()) {
+ return;
+ }
+
+ mContext.enforceCallingPermission(
Manifest.permission.STATUS_BAR_SERVICE,
/* function= */ "notifyQuickSettingsTilesChanged");
+ mContext.enforceCallingPermission(
+ Manifest.permission.MANAGE_ACCESSIBILITY,
+ /* function= */ "notifyQuickSettingsTilesChanged");
- Slog.d(LOG_TAG, TextUtils.formatSimple(
- "notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
- userId, tileComponentNames));
- // TODO (b/314843909): in the follow up cl
+ if (DEBUG) {
+ Slog.d(LOG_TAG, TextUtils.formatSimple(
+ "notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
+ userId, tileComponentNames));
+ }
+ final Set<ComponentName> newTileComponentNames = new ArraySet<>(tileComponentNames);
+ final Set<ComponentName> addedTiles;
+ final Set<ComponentName> removedTiles;
+ final Map<ComponentName, AccessibilityServiceInfo> tileServiceToA11yServiceInfo;
+ final Map<ComponentName, ComponentName> a11yFeatureToTileService;
+
// update in-memory copy of QS_TILES in AccessibilityManager
- // update Settings.Secure.ACCESSIBILITY_QS_TARGETS and its in-memory copy
- // show full device control warning if needed (b/314850435)
+ synchronized (mLock) {
+ AccessibilityUserState userState = getUserStateLocked(userId);
+
+ tileServiceToA11yServiceInfo = userState.getTileServiceToA11yServiceInfoMapLocked();
+ a11yFeatureToTileService = userState.getA11yFeatureToTileService();
+
+ ArraySet<ComponentName> currentTiles = userState.getA11yQsTilesInQsPanel();
+ // Find newly added tiles
+ addedTiles = newTileComponentNames
+ .stream()
+ .filter(tileComponentName -> !currentTiles.contains(tileComponentName))
+ .collect(Collectors.toSet());
+ // Find newly removed tiles
+ removedTiles = currentTiles
+ .stream()
+ .filter(tileComponentName -> !newTileComponentNames.contains(tileComponentName))
+ .collect(Collectors.toSet());
+
+ if (addedTiles.isEmpty() && removedTiles.isEmpty()) {
+ return;
+ }
+
+ userState.updateA11yTilesInQsPanelLocked(newTileComponentNames);
+ }
+
+ List<String> a11yFeaturesToEnable = new ArrayList<>();
+ List<String> a11yFeaturesToRemove = new ArrayList<>();
+ // Find the framework features to configure the qs shortcut on/off
+ for (Map.Entry<ComponentName, ComponentName> frameworkFeatureWithTile :
+ ShortcutConstants.A11Y_FEATURE_TO_FRAMEWORK_TILE.entrySet()) {
+ String a11yFeature = frameworkFeatureWithTile.getKey().flattenToString();
+ ComponentName tile = frameworkFeatureWithTile.getValue();
+ if (addedTiles.contains(tile)) {
+ a11yFeaturesToEnable.add(a11yFeature);
+ } else if (removedTiles.contains(tile)) {
+ a11yFeaturesToRemove.add(a11yFeature);
+ }
+ }
+ // Find the accessibility service/activity to configure the qs shortcut on/off
+ for (Map.Entry<ComponentName, ComponentName> a11yFeatureWithTileService :
+ a11yFeatureToTileService.entrySet()) {
+ String a11yFeature = a11yFeatureWithTileService.getKey().flattenToString();
+ ComponentName tileService = a11yFeatureWithTileService.getValue();
+ if (addedTiles.contains(tileService)) {
+ AccessibilityServiceInfo serviceInfo = tileServiceToA11yServiceInfo.getOrDefault(
+ tileService, null);
+ if (serviceInfo != null && isAccessibilityServiceWarningRequired(serviceInfo)) {
+ // TODO(b/314850435): show full device control warning if needed after
+ // SysUI QS Panel can update live
+ continue;
+ }
+ a11yFeaturesToEnable.add(a11yFeature);
+ } else if (removedTiles.contains(tileService)) {
+ a11yFeaturesToRemove.add(a11yFeature);
+ }
+ }
+ // Turn on/off a11y qs shortcut for the a11y features based on the change in QS Panel
+ if (!a11yFeaturesToEnable.isEmpty()) {
+ enableShortcutForTargets(/* enable= */ true, UserShortcutType.QUICK_SETTINGS,
+ a11yFeaturesToEnable, userId);
+ }
+
+ if (!a11yFeaturesToRemove.isEmpty()) {
+ enableShortcutForTargets(/* enable= */ false, UserShortcutType.QUICK_SETTINGS,
+ a11yFeaturesToRemove, userId);
+ }
}
/**
@@ -3661,18 +3742,35 @@
/**
* Update the Settings.Secure.ACCESSIBILITY_QS_TARGETS so that it only contains valid content,
* and a side loaded service can't spoof the package name of the default service.
+ * <p>
+ * 1. Remove the target if the target is no longer installed on the device <br/>
+ * 2. Add the target if the target is enabled and the target's tile is in the QS Panel <br/>
+ * </p>
*/
private void updateAccessibilityQsTargetsLocked(AccessibilityUserState userState) {
- final Set<String> targets =
- userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
- final int lastSize = targets.size();
- if (lastSize == 0) {
+ if (!android.view.accessibility.Flags.a11yQsShortcut()) {
return;
}
+ final Set<String> targets =
+ userState.getShortcutTargetsLocked(UserShortcutType.QUICK_SETTINGS);
+
// Removes the targets that are no longer installed on the device.
boolean somethingChanged = targets.removeIf(
name -> !userState.isShortcutTargetInstalledLocked(name));
+ // Add the target if the a11y service is enabled and the tile exist in QS panel
+ Set<ComponentName> enabledServices = userState.getEnabledServicesLocked();
+ Map<ComponentName, ComponentName> a11yFeatureToTileService =
+ userState.getA11yFeatureToTileService();
+ Set<ComponentName> currentA11yTilesInQsPanel = userState.getA11yQsTilesInQsPanel();
+ for (ComponentName enabledService : enabledServices) {
+ ComponentName tileService =
+ a11yFeatureToTileService.getOrDefault(enabledService, null);
+ if (tileService != null && currentA11yTilesInQsPanel.contains(tileService)) {
+ somethingChanged |= targets.add(enabledService.flattenToString());
+ }
+ }
+
if (!somethingChanged) {
return;
}
@@ -3700,14 +3798,18 @@
return;
}
- final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = List.of(
+ final List<Pair<Integer, String>> shortcutTypeAndShortcutSetting = new ArrayList<>(3);
+ shortcutTypeAndShortcutSetting.add(
new Pair<>(ACCESSIBILITY_SHORTCUT_KEY,
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
+ shortcutTypeAndShortcutSetting.add(
new Pair<>(ACCESSIBILITY_BUTTON,
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
- new Pair<>(UserShortcutType.QUICK_SETTINGS,
- Settings.Secure.ACCESSIBILITY_QS_TARGETS)
- );
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS));
+ if (android.view.accessibility.Flags.a11yQsShortcut()) {
+ shortcutTypeAndShortcutSetting.add(
+ new Pair<>(UserShortcutType.QUICK_SETTINGS,
+ Settings.Secure.ACCESSIBILITY_QS_TARGETS));
+ }
final ComponentName serviceName = service.getComponentName();
for (Pair<Integer, String> shortcutTypePair : shortcutTypeAndShortcutSetting) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 063eafe..4b128f7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -66,6 +66,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
/**
* Class that hold states and settings per user and share between
@@ -104,6 +106,15 @@
final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>();
private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>();
+ /**
+ * The QuickSettings tiles in the QS Panel. This can be different from
+ * {@link #mAccessibilityQsTargets} in that {@link #mA11yTilesInQsPanel} stores the
+ * TileService's or the a11y framework tile component names (e.g.
+ * {@link AccessibilityShortcutController#COLOR_INVERSION_TILE_COMPONENT_NAME}) instead of the
+ * A11y Feature's component names.
+ */
+ private final ArraySet<ComponentName> mA11yTilesInQsPanel = new ArraySet<>();
+
private final ServiceInfoChangeListener mServiceInfoChangeListener;
private ComponentName mServiceChangingSoftKeyboardMode;
@@ -566,7 +577,9 @@
pw.println("}");
pw.append(" button target:{").append(mTargetAssignedToAccessibilityButton);
pw.println("}");
- pw.append(" qs shortcut targets:" + mAccessibilityQsTargets);
+ pw.append(" qs shortcut targets:").append(mAccessibilityQsTargets.toString());
+ pw.println();
+ pw.append(" a11y tiles in QS panel:").append(mA11yTilesInQsPanel.toString());
pw.println();
pw.append(" Bound services:{");
final int serviceCount = mBoundServices.size();
@@ -1100,10 +1113,46 @@
return new ArraySet<>(mAccessibilityQsTargets);
}
+ public void updateA11yTilesInQsPanelLocked(Set<ComponentName> componentNames) {
+ mA11yTilesInQsPanel.clear();
+ mA11yTilesInQsPanel.addAll(componentNames);
+ }
+
+ /**
+ * Returns a copy of the a11y tiles that are in the QuickSettings panel
+ */
+ public ArraySet<ComponentName> getA11yQsTilesInQsPanel() {
+ return new ArraySet<>(mA11yTilesInQsPanel);
+ }
+
+ /**
+ * Returns a map of AccessibilityService or AccessibilityShortcut to its provided TileService
+ */
public Map<ComponentName, ComponentName> getA11yFeatureToTileService() {
Map<ComponentName, ComponentName> featureToTileServiceMap = new ArrayMap<>();
featureToTileServiceMap.putAll(mA11yServiceToTileService);
featureToTileServiceMap.putAll(mA11yActivityToTileService);
return featureToTileServiceMap;
}
+
+ /**
+ * Returns a map of TileService's componentName to the AccessibilityServiceInfo it ties to.
+ */
+ public Map<ComponentName, AccessibilityServiceInfo> getTileServiceToA11yServiceInfoMapLocked() {
+ Map<ComponentName, AccessibilityServiceInfo> tileServiceToA11yServiceInfoMap =
+ new ArrayMap<>();
+ Map<ComponentName, AccessibilityServiceInfo> a11yServiceToServiceInfoMap =
+ mInstalledServices.stream().collect(
+ Collectors.toMap(
+ AccessibilityServiceInfo::getComponentName,
+ Function.identity()));
+ for (Map.Entry<ComponentName, ComponentName> serviceToTile :
+ mA11yServiceToTileService.entrySet()) {
+ if (a11yServiceToServiceInfoMap.containsKey(serviceToTile.getKey())) {
+ tileServiceToA11yServiceInfoMap.put(serviceToTile.getValue(),
+ a11yServiceToServiceInfoMap.get(serviceToTile.getKey()));
+ }
+ }
+ return tileServiceToA11yServiceInfoMap;
+ }
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 1749ee3..16fe007 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 @@
}
}
}
- 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 ca2a3dd..a55b8d0 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.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 @@
mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid);
mSaveEventLogger = SaveEventLogger.forSessionId(sessionId);
mIsPrimaryCredential = isPrimaryCredential;
- mIgnoreViewStateResetToEmpty = Flags.ignoreViewStateResetToEmpty();
+ mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty();
synchronized (mLock) {
mSessionFlags = new SessionFlags();
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 7d8aad7..ecd14ce 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.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 @@
Trace.beginSection(
"SensitiveContentProtectionManagerService.onProjectionStart");
try {
- onProjectionStart(info);
+ onProjectionStart(info.getPackageName());
} finally {
Trace.endSection();
}
@@ -124,14 +125,6 @@
}
}
- // 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 @@
}
}
- 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 130a733..1334a95 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.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 @@
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 @@
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 @@
}
IAccountManagerResponse getResponseAndClose() {
+ if (mAuthenticatorUid != 0 && mBindingStartTime > 0) {
+ sResponseLatency.logSampleWithUid(mAuthenticatorUid,
+ SystemClock.uptimeMillis() - mBindingStartTime);
+ }
if (mResponse == null) {
close();
return null;
@@ -5353,7 +5366,8 @@
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 5298846..0012b3d 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.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 @@
}
// 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 5e6ff55..447dfd9 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 @@
// cleaning up the old proxies.
VMRuntime.getRuntime().requestConcurrentGC();
}
- }, BackgroundThread.getHandler());
+ }, mHandler);
t.traceEnd(); // setBinderProxies
t.traceEnd(); // ActivityManagerStartApps
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5521381..0a6e9d3 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -286,9 +286,6 @@
// when the flag is fused on.
private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8;
- // TODO: Use the trunk stable flag.
- private static final boolean DEFER_FROZEN_OUTGOING_BCASTS = false;
-
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST);
@@ -766,7 +763,7 @@
// TODO: Apply delivery group policies and FLAG_REPLACE_PENDING to collapse the
// outgoing broadcasts.
// TODO: Add traces/logs for the enqueueing outgoing broadcasts logic.
- if (DEFER_FROZEN_OUTGOING_BCASTS && isProcessFreezable(r.callerApp)) {
+ if (Flags.deferOutgoingBcasts() && isProcessFreezable(r.callerApp)) {
final BroadcastProcessQueue queue = getOrCreateProcessQueue(
r.callerApp.processName, r.callerApp.uid);
if (queue.getOutgoingBroadcastCount() >= mConstants.MAX_FROZEN_OUTGOING_BROADCASTS) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e0c2425..14428c4 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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 48bf9f4..e915688 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 @@
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 @@
*/
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 @@
*/
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 a923daa..e578861 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 @@
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 @@
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 @@
* @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 @@
* @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 0000000..133c79f
--- /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 0000000..daa0211
--- /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/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 40b2f5a..10030b3 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -21,6 +21,7 @@
import android.os.IBinder;
import android.os.PowerManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -30,8 +31,7 @@
class BrightnessRangeController {
private final HighBrightnessModeController mHbmController;
- private final NormalBrightnessModeController mNormalBrightnessModeController =
- new NormalBrightnessModeController();
+ private final NormalBrightnessModeController mNormalBrightnessModeController;
private final HdrClamper mHdrClamper;
@@ -45,17 +45,21 @@
Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler,
DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
this(hbmController, modeChangeCallback, displayDeviceConfig,
+ new NormalBrightnessModeController(),
new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags,
displayToken, info);
}
+ @VisibleForTesting
BrightnessRangeController(HighBrightnessModeController hbmController,
Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
+ NormalBrightnessModeController normalBrightnessModeController,
HdrClamper hdrClamper, DisplayManagerFlags flags, IBinder displayToken,
DisplayDeviceInfo info) {
mHbmController = hbmController;
mModeChangeCallback = modeChangeCallback;
mHdrClamper = hdrClamper;
+ mNormalBrightnessModeController = normalBrightnessModeController;
mUseHdrClamper = flags.isHdrClamperEnabled();
mUseNbmController = flags.isNbmControllerEnabled();
if (mUseNbmController) {
@@ -126,8 +130,11 @@
float getCurrentBrightnessMax() {
- if (mUseNbmController && mHbmController.getHighBrightnessMode()
- == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
+ // nbmController might adjust maxBrightness only if device does not support HBM or
+ // hbm is currently not allowed
+ if (mUseNbmController
+ && (!mHbmController.deviceSupportsHbm()
+ || !mHbmController.isHbmCurrentlyAllowed())) {
return Math.min(mHbmController.getCurrentBrightnessMax(),
mNormalBrightnessModeController.getCurrentBrightnessMax());
}
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index ab7c503..a12d248 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -42,6 +42,9 @@
import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.display.utils.DebugUtils;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Listens for Skin thermal sensor events, disables external displays if thermal status becomes
* equal or above {@link android.os.Temperature#THROTTLING_CRITICAL}, enables external displays if
@@ -106,6 +109,10 @@
private final ExternalDisplayStatsService mExternalDisplayStatsService;
@ThrottlingStatus
private volatile int mStatus = THROTTLING_NONE;
+ //@GuardedBy("mSyncRoot")
+ private boolean mIsBootCompleted;
+ //@GuardedBy("mSyncRoot")
+ private final Set<Integer> mDisplayIdsWaitingForBootCompletion = new HashSet<>();
ExternalDisplayPolicy(@NonNull final Injector injector) {
mInjector = injector;
@@ -121,6 +128,17 @@
* Starts listening for temperature changes.
*/
void onBootCompleted() {
+ synchronized (mSyncRoot) {
+ mIsBootCompleted = true;
+ for (var displayId : mDisplayIdsWaitingForBootCompletion) {
+ var logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (logicalDisplay != null) {
+ handleExternalDisplayConnectedLocked(logicalDisplay);
+ }
+ }
+ mDisplayIdsWaitingForBootCompletion.clear();
+ }
+
if (!mFlags.isConnectedDisplayManagementEnabled()) {
if (DEBUG) {
Slog.d(TAG, "External display management is not enabled on your device:"
@@ -189,6 +207,11 @@
return;
}
+ if (!mIsBootCompleted) {
+ mDisplayIdsWaitingForBootCompletion.add(logicalDisplay.getDisplayIdLocked());
+ return;
+ }
+
mExternalDisplayStatsService.onDisplayConnected(logicalDisplay);
if ((Build.IS_ENG || Build.IS_USERDEBUG)
@@ -227,7 +250,12 @@
return;
}
- mExternalDisplayStatsService.onDisplayDisconnected(logicalDisplay.getDisplayIdLocked());
+ var displayId = logicalDisplay.getDisplayIdLocked();
+ if (mDisplayIdsWaitingForBootCompletion.remove(displayId)) {
+ return;
+ }
+
+ mExternalDisplayStatsService.onDisplayDisconnected(displayId);
}
/**
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index a9f78fd..47176fe 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -168,7 +168,7 @@
}
float getCurrentBrightnessMax() {
- if (!deviceSupportsHbm() || isCurrentlyAllowed()) {
+ if (!deviceSupportsHbm() || isHbmCurrentlyAllowed()) {
// Either the device doesn't support HBM, or HBM range is currently allowed (device
// it in a high-lux environment). In either case, return the highest brightness
// level supported by the device.
@@ -356,7 +356,7 @@
return event.getStartTimeMillis();
}
- private boolean isCurrentlyAllowed() {
+ boolean isHbmCurrentlyAllowed() {
// Returns true if HBM is allowed (above the ambient lux threshold) and there's still
// time within the current window for additional HBM usage. We return false if there is an
// HDR layer because we don't want the brightness MAX to change for HDR, which has its
@@ -369,7 +369,7 @@
&& !mIsBlockedByLowPowerMode);
}
- private boolean deviceSupportsHbm() {
+ boolean deviceSupportsHbm() {
return mHbmData != null && mHighBrightnessModeMetadata != null;
}
@@ -462,7 +462,7 @@
+ ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn
+ ", remainingAllowedTime: " + remainingTime
+ ", isLuxHigh: " + mIsInAllowedAmbientRange
- + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed()
+ + ", isHBMCurrentlyAllowed: " + isHbmCurrentlyAllowed()
+ ", isHdrLayerPresent: " + mIsHdrLayerPresent
+ ", mMaxDesiredHdrSdrRatio: " + mMaxDesiredHdrSdrRatio
+ ", isAutoBrightnessEnabled: " + mIsAutoBrightnessEnabled
@@ -575,7 +575,7 @@
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
} else if (mIsHdrLayerPresent) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
- } else if (isCurrentlyAllowed()) {
+ } else if (isHbmCurrentlyAllowed()) {
return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index e6bf2c9..3fafca8 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -266,7 +266,11 @@
throw new RuntimeException(e);
}
}
+ updateConfiguration(grammaticalGender, userId);
+ Trace.endSection();
+ }
+ private void updateConfiguration(int grammaticalGender, int userId) {
try {
Configuration config = new Configuration();
int preValue = config.getGrammaticalGender();
@@ -280,7 +284,6 @@
} catch (RemoteException e) {
Log.w(TAG, "Can not update configuration", e);
}
- Trace.endSection();
}
public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
@@ -372,7 +375,9 @@
if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
try (FileInputStream in = new FileInputStream(file)) {
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
- mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
+ int grammaticalGender = getGrammaticalGenderFromXml(parser);
+ mGrammaticalGenderCache.put(userId, grammaticalGender);
+ updateConfiguration(grammaticalGender, userId);
} catch (IOException | XmlPullParserException e) {
Log.e(TAG, "Failed to parse XML configuration from " + file, e);
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index c7b60da..dd6433d 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -19,11 +19,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
@@ -67,7 +69,7 @@
AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
}
- static void initialize(@NonNull Handler handler) {
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
handler.post(() -> {
@@ -79,8 +81,16 @@
handler.post(() -> {
synchronized (ImfLock.class) {
if (!sPerUserMap.contains(userId)) {
- sPerUserMap.put(userId,
- AdditionalSubtypeUtils.load(userId));
+ final AdditionalSubtypeMap additionalSubtypeMap =
+ AdditionalSubtypeUtils.load(userId);
+ sPerUserMap.put(userId, additionalSubtypeMap);
+ final InputMethodSettings settings =
+ InputMethodManagerService
+ .queryInputMethodServicesInternal(context,
+ userId,
+ additionalSubtypeMap,
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
}
}
});
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index fef5661..a0e910d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -284,6 +284,10 @@
final Context mContext;
final Resources mRes;
private final Handler mHandler;
+
+ /**
+ * TODO(b/329163064): Remove this field.
+ */
@NonNull
@MultiUserUnawareField
private InputMethodSettings mSettings;
@@ -867,7 +871,18 @@
if (!mSystemReady) {
return;
}
- buildInputMethodListLocked(true);
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ final InputMethodSettings settings = queryInputMethodServicesInternal(
+ mContext,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ if (userId == mSettings.getUserId()) {
+ mSettings = settings;
+ }
+ }
+ postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext);
updateFromSettingsLocked(true);
@@ -1063,13 +1078,7 @@
final boolean isCurrentUser = (userId == mSettings.getUserId());
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings;
- if (isCurrentUser) {
- settings = mSettings;
- } else {
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
InputMethodInfo curIm = null;
String curInputMethodId = settings.getSelectedInputMethod();
@@ -1113,13 +1122,20 @@
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
}
-
- if (!isCurrentUser
- || !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+ if (isCurrentUser
+ && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
return;
}
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (!isCurrentUser) {
+ return;
+ }
+ mSettings = queryInputMethodServicesInternal(mContext, userId,
+ newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
boolean changed = false;
@@ -1271,17 +1287,20 @@
void onUnlockUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final int currentUserId = mSettings.getUserId();
if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
+ Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId="
+ + mSettings.getUserId());
}
- if (userId != currentUserId) {
+ if (!mSystemReady) {
return;
}
- mSettings = InputMethodSettings.createEmptyMap(userId);
- if (mSystemReady) {
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (mSettings.getUserId() == userId) {
+ mSettings = newSettings;
// We need to rebuild IMEs.
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
}
}
@@ -1347,12 +1366,13 @@
mShowOngoingImeSwitcherForPhones = false;
- AdditionalSubtypeMapRepository.initialize(mHandler);
+ // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+ InputMethodSettingsRepository.initialize(mHandler, mContext);
+ AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
final int userId = mActivityManagerInternal.getCurrentUserId();
- // mSettings should be created before buildInputMethodListLocked
- mSettings = InputMethodSettings.createEmptyMap(userId);
+ mSettings = InputMethodSettingsRepository.get(userId);
mSwitchingController =
InputMethodSubtypeSwitchingController.createInstanceLocked(context,
@@ -1515,7 +1535,12 @@
// The mSystemReady flag is set during boot phase,
// and user switch would not happen at that time.
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
- buildInputMethodListLocked(initialUserSwitch);
+
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ newUserId, AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(newUserId, newSettings);
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
// This is the first time of the user switch and
// set the current ime to the proper one.
@@ -1596,7 +1621,13 @@
final String defaultImiId = mSettings.getSelectedInputMethod();
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(currentUserId, newSettings);
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(
+ !imeSelectedOnBoot /* resetDefaultEnabledIme */);
updateFromSettingsLocked(true);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, currentUserId),
@@ -1703,9 +1734,7 @@
&& (!connectionless
|| mBindingController.supportsConnectionlessStylusHandwriting());
}
- //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
- //TODO(b/210039666): use cache.
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
settings.getSelectedInputMethod());
return imi != null && imi.supportsStylusHandwriting()
@@ -1729,9 +1758,8 @@
private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
final InputMethodSettings settings;
- if (userId == mSettings.getUserId()
- && directBootAwareness == DirectBootAwareness.AUTO) {
- settings = mSettings;
+ if (directBootAwareness == DirectBootAwareness.AUTO) {
+ settings = InputMethodSettingsRepository.get(userId);
} else {
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
@@ -1755,7 +1783,7 @@
methodList = mSettings.getEnabledInputMethodList();
settings = mSettings;
} else {
- settings = queryMethodMapForUserLocked(userId);
+ settings = InputMethodSettingsRepository.get(userId);
methodList = settings.getEnabledInputMethodList();
}
// filter caller's access to input methods
@@ -1815,22 +1843,7 @@
@GuardedBy("ImfLock.class")
private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
- if (userId == mSettings.getUserId()) {
- final InputMethodInfo imi;
- String selectedMethodId = getSelectedMethodIdLocked();
- if (imiId == null && selectedMethodId != null) {
- imi = mSettings.getMethodMap().get(selectedMethodId);
- } else {
- imi = mSettings.getMethodMap().get(imiId);
- }
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
- return Collections.emptyList();
- }
- return mSettings.getEnabledInputMethodSubtypeList(
- imi, allowsImplicitlyEnabledSubtypes);
- }
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(imiId);
if (imi == null) {
return Collections.emptyList();
@@ -3996,8 +4009,7 @@
return mSettings.getLastInputMethodSubtype();
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getLastInputMethodSubtype();
+ return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
}
}
@@ -4029,19 +4041,21 @@
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
final boolean isCurrentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = isCurrentUser
- ? mSettings
- : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
if (additionalSubtypeMap != newAdditionalSubtypeMap) {
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
if (isCurrentUser) {
final long ident = Binder.clearCallingIdentity();
try {
- buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+ mSettings = newSettings;
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4071,8 +4085,7 @@
try {
synchronized (ImfLock.class) {
final boolean currentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = currentUser
- ? mSettings : queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
return;
}
@@ -4969,7 +4982,7 @@
}
@GuardedBy("ImfLock.class")
- void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
+ void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme) {
if (DEBUG) {
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ " \n ------ caller=" + Debug.getCallers(10));
@@ -4981,10 +4994,6 @@
mMethodMapUpdateCount++;
mMyPackageMonitor.clearKnownImePackageNamesLocked();
- mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
- DirectBootAwareness.AUTO);
-
// Construct the set of possible IME packages for onPackageChanged() to avoid false
// negatives when the package state remains to be the same but only the component state is
// changed.
@@ -5255,8 +5264,8 @@
return getCurrentInputMethodSubtypeLocked();
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
+ return InputMethodSettingsRepository.get(userId)
+ .getCurrentInputMethodSubtypeForNonCurrentUsers();
}
}
@@ -5318,27 +5327,11 @@
*/
@GuardedBy("ImfLock.class")
private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
- final InputMethodSettings settings;
- if (userId == mSettings.getUserId()) {
- settings = mSettings;
- } else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
return settings.getMethodMap().get(settings.getSelectedInputMethod());
}
@GuardedBy("ImfLock.class")
- private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
- }
-
- @GuardedBy("ImfLock.class")
private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
if (userId == mSettings.getUserId()) {
if (!mSettings.getMethodMap().containsKey(imeId)
@@ -5349,7 +5342,7 @@
setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
return true;
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.getMethodMap().containsKey(imeId)
|| !settings.getEnabledInputMethodList().contains(
settings.getMethodMap().get(imeId))) {
@@ -5489,7 +5482,7 @@
setInputMethodEnabledLocked(imeId, enabled);
return true;
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.getMethodMap().containsKey(imeId)) {
return false; // IME is not found.
}
@@ -6243,7 +6236,7 @@
previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
}
} else {
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (enabled) {
if (!settings.getMethodMap().containsKey(imeId)) {
failedToEnableUnknownIme = true;
@@ -6377,10 +6370,8 @@
nextIme = mSettings.getSelectedInputMethod();
nextEnabledImes = mSettings.getEnabledInputMethodList();
} else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings = queryInputMethodServicesInternal(
- mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
+ final InputMethodSettings settings =
+ InputMethodSettingsRepository.get(userId);
nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
settings.getMethodList());
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 0000000..60b9a4c
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodSettingsRepository() {
+ }
+
+ @NonNull
+ @GuardedBy("ImfLock.class")
+ static InputMethodSettings get(@UserIdInt int userId) {
+ final InputMethodSettings obj = sPerUserMap.get(userId);
+ if (obj != null) {
+ return obj;
+ }
+ return InputMethodSettings.createEmptyMap(userId);
+ }
+
+ @GuardedBy("ImfLock.class")
+ static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+ sPerUserMap.put(userId, obj);
+ }
+
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ handler.post(() -> {
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
+ });
+ }
+ });
+ synchronized (ImfLock.class) {
+ for (int userId : userManagerInternal.getUserIds()) {
+ final InputMethodSettings settings =
+ InputMethodManagerService.queryInputMethodServicesInternal(
+ context,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ sPerUserMap.put(userId, settings);
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c38fbda..e80c79a8 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.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;
@@ -740,7 +741,7 @@
private static final int MY_UID = Process.myUid();
private static final int MY_PID = Process.myPid();
- static final IBinder ALLOWLIST_TOKEN = new Binder();
+ private static final IBinder ALLOWLIST_TOKEN = new Binder();
protected RankingHandler mRankingHandler;
private long mLastOverRateLogTime;
private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
@@ -1825,6 +1826,12 @@
}
}
+ protected void logSensitiveAdjustmentReceived(boolean hasPosted,
+ boolean hasSensitiveContent, int lifespanMs) {
+ FrameworkStatsLog.write(FrameworkStatsLog.SENSITIVE_NOTIFICATION_REDACTION, hasPosted,
+ hasSensitiveContent, lifespanMs);
+ }
+
@GuardedBy("mNotificationLock")
void clearSoundLocked() {
mSoundNotificationKey = null;
@@ -4868,7 +4875,7 @@
// Remove background token before returning notification to untrusted app, this
// ensures the app isn't able to perform background operations that are
// associated with notification interactions.
- notification.overrideAllowlistToken(null);
+ notification.clearAllowlistToken();
return new StatusBarNotification(
sbn.getPackageName(),
sbn.getOpPkg(),
@@ -6384,7 +6391,7 @@
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 @@
}
}
if (!foundEnqueued) {
- applyAdjustmentFromAssistant(token, adjustment);
+ applyAdjustmentsFromAssistant(token, List.of(adjustment));
}
}
} finally {
@@ -6422,7 +6429,7 @@
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 @@
}
}
- 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 @@
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()));
+ }
}
}
@@ -7870,8 +7884,6 @@
}
}
- notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
-
// Remote views? Are they too big?
checkRemoteViews(pkg, tag, id, notification);
}
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
similarity index 72%
rename from core/java/android/app/ondeviceintelligence/Content.aidl
rename to services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
index 40f0ef9..81f11b5 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 71800ef..28682e3 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.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.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 @@
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 @@
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 @@
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));
}
- 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 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));
}
- 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());
- }
- 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 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.processRequestStreaming(Binder.getCallingUid(), feature,
- request, requestType,
- cancellationSignal, processingSignal,
- streamingCallback));
- }
+
+ @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));
+ }
+
+ @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));
+ }
+
+ @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 @@
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 @@
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 @@
@NonNull IOnDeviceSandboxedInferenceService service) {
try {
ensureRemoteIntelligenceServiceInitialized();
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
intelligenceService -> intelligenceService.notifyInferenceServiceConnected());
service.registerRemoteStorageService(
getIRemoteStorageService());
@@ -404,7 +441,7 @@
public void getReadOnlyFileDescriptor(
String filePath,
AndroidFuture<ParcelFileDescriptor> future) {
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFileDescriptor(
filePath, future));
}
@@ -413,7 +450,7 @@
public void getReadOnlyFeatureFileDescriptorMap(
Feature feature,
RemoteCallback remoteCallback) {
- mRemoteOnDeviceIntelligenceService.post(
+ mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFeatureFileDescriptorMap(
feature, remoteCallback));
}
@@ -469,4 +506,92 @@
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 0000000..a76d8a3
--- /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 0000000..fb6099c
--- /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 0000000..7251e6e
--- /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 6266ef3..4ed6e80 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.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 @@
FilteredSnapshot withFilteredSnapshot(int callingUid, @NonNull UserHandle user);
/**
+ * Add a pair of signing details so that packages signed with {@code oldSigningDetails} will
+ * behave as if they are signed by the {@code newSigningDetails}.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @param newSigningDetails the new signing detail that will replace the original one
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void addOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails,
+ @NonNull SigningDetails newSigningDetails);
+
+ /**
+ * Remove a pair of signing details previously added via {@link #addOverrideSigningDetails} by
+ * the old signing details.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @param oldSigningDetails the original signing detail of the package
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void removeOverrideSigningDetails(@NonNull SigningDetails oldSigningDetails);
+
+ /**
+ * Clear all pairs of signing details previously added via {@link #addOverrideSigningDetails}.
+ * <p>
+ * This is only available on {@link android.os.Build#isDebuggable debuggable} builds.
+ *
+ * @throws SecurityException if the build is not debuggable
+ *
+ * @hide
+ */
+ @TestApi
+ void clearOverrideSigningDetails();
+
+ /**
* @hide
*/
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 8d05450..55afb17 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.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 @@
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 daa0211..8337fd2 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/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 0165d65..65ab129 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -314,6 +314,11 @@
wallpaper.wallpaperId = makeWallpaperIdLocked();
}
+ Rect legacyCropHint = new Rect(
+ getAttributeInt(parser, "cropLeft", 0),
+ getAttributeInt(parser, "cropTop", 0),
+ getAttributeInt(parser, "cropRight", 0),
+ getAttributeInt(parser, "cropBottom", 0));
Rect totalCropHint = new Rect(
getAttributeInt(parser, "totalCropLeft", 0),
getAttributeInt(parser, "totalCropTop", 0),
@@ -332,18 +337,19 @@
parser.getAttributeInt(null, "cropBottom" + pair.second, 0));
if (!cropHint.isEmpty()) wallpaper.mCropHints.put(pair.first, cropHint);
}
- if (wallpaper.mCropHints.size() == 0) {
+ if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
// migration case: the crops per screen orientation are not specified.
- // use the old attributes to find the crop for one screen orientation.
- Integer orientation = totalCropHint.width() < totalCropHint.height()
+ int orientation = legacyCropHint.width() < legacyCropHint.height()
? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
- if (!totalCropHint.isEmpty()) wallpaper.mCropHints.put(orientation, totalCropHint);
+ if (!legacyCropHint.isEmpty()) {
+ wallpaper.mCropHints.put(orientation, legacyCropHint);
+ }
} else {
wallpaper.cropHint.set(totalCropHint);
}
wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
} else {
- wallpaper.cropHint.set(totalCropHint);
+ wallpaper.cropHint.set(legacyCropHint);
}
final DisplayData wpData = mWallpaperDisplayHelper
.getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0069cdd..6fa6957 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7786,8 +7786,11 @@
@Override
void prepareSurfaces() {
- final boolean show = isVisible() || isAnimating(PARENTS,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+ final boolean show = (isVisible()
+ // Ensure that the activity content is hidden when the decor surface is boosted to
+ // prevent UI redressing attack.
+ && !getTask().isDecorSurfaceBoosted())
+ || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
| ANIMATION_TYPE_PREDICT_BACK);
if (mSurfaceControl != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 2fc6b5f..963b4cb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1478,7 +1478,6 @@
}
mLaunchingActivityWakeLock.release();
}
- mRootWindowContainer.ensureActivitiesVisible();
}
// Atomically retrieve all of the other things to do.
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 071f403..f7baa79 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 @@
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.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 @@
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 @@
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 @@
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 @@
return true;
}
- private void showBalBlockedToast() {
- if (balShowToastsBlocked()) {
- showToast("BAL blocked. go/debug-bal");
- }
- }
-
- private void showBalRiskToast() {
- if (balShowToasts()) {
- showToast("BAL allowed in compat mode. go/debug-bal");
- }
- }
-
@VisibleForTesting void showToast(String toastText) {
UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
toastText, Toast.LENGTH_LONG).show());
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index d87e21c..55dc30c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3741,7 +3741,9 @@
wc.assignChildLayers(t);
if (!wc.needsZBoost()) {
// Place the decor surface under any untrusted content.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+ if (mDecorSurfaceContainer != null
+ && !mDecorSurfaceContainer.mIsBoosted
+ && !decorSurfacePlaced
&& shouldPlaceDecorSurfaceBelowContainer(wc)) {
mDecorSurfaceContainer.assignLayer(t, layer++);
decorSurfacePlaced = true;
@@ -3760,7 +3762,9 @@
}
// Place the decor surface just above the owner TaskFragment.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced
+ if (mDecorSurfaceContainer != null
+ && !mDecorSurfaceContainer.mIsBoosted
+ && !decorSurfacePlaced
&& wc == mDecorSurfaceContainer.mOwnerTaskFragment) {
mDecorSurfaceContainer.assignLayer(t, layer++);
decorSurfacePlaced = true;
@@ -3768,10 +3772,10 @@
}
}
- // If not placed yet, the decor surface should be on top of all non-boosted children.
- if (mDecorSurfaceContainer != null && !decorSurfacePlaced) {
+ // Boost the decor surface above other non-boosted windows if requested. The cover surface
+ // will ensure that the content of the windows below are invisible.
+ if (mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted) {
mDecorSurfaceContainer.assignLayer(t, layer++);
- decorSurfacePlaced = true;
}
for (int j = 0; j < mChildren.size(); ++j) {
@@ -3796,6 +3800,24 @@
return !isOwnActivity && !isTrustedTaskFragment;
}
+ void setDecorSurfaceBoosted(
+ @NonNull TaskFragment ownerTaskFragment,
+ boolean isBoosted,
+ @Nullable SurfaceControl.Transaction clientTransaction) {
+ if (mDecorSurfaceContainer == null
+ || mDecorSurfaceContainer.mOwnerTaskFragment != ownerTaskFragment) {
+ return;
+ }
+ mDecorSurfaceContainer.setBoosted(isBoosted, clientTransaction);
+ // scheduleAnimation() is called inside assignChildLayers(), which ensures that child
+ // surface visibility is updated with prepareSurfaces()
+ assignChildLayers();
+ }
+
+ boolean isDecorSurfaceBoosted() {
+ return mDecorSurfaceContainer != null && mDecorSurfaceContainer.mIsBoosted;
+ }
+
boolean isTaskId(int taskId) {
return mTaskId == taskId;
}
@@ -6796,14 +6818,35 @@
}
/**
- * A decor surface that is requested by a {@code TaskFragmentOrganizer} which will be placed
- * below children windows except for own Activities and TaskFragment in fully trusted mode.
+ * A class managing the decor surface.
+ *
+ * A decor surface is requested by a {@link TaskFragmentOrganizer} and is placed below children
+ * windows in the Task except for own Activities and TaskFragments in fully trusted mode. The
+ * decor surface is created and shared with the client app with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE} and
+ * be removed with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE}.
+ *
+ * When boosted with
+ * {@link android.window.TaskFragmentOperation#OP_TYPE_SET_DECOR_SURFACE_BOOSTED}, the decor
+ * surface is placed above all non-boosted windows in the Task, but all the content below it
+ * will be hidden to prevent UI redressing attacks. This can be used by the draggable
+ * divider between {@link TaskFragment}s where veils are drawn on the decor surface while
+ * dragging to indicate new bounds.
*/
@VisibleForTesting
class DecorSurfaceContainer {
+
+ // The container surface is the parent of the decor surface. The container surface
+ // should NEVER be shared with the client. It is used to ensure that the decor surface has
+ // a z-order in the Task that is managed by WM core and cannot be updated by the client
+ // process.
@VisibleForTesting
@NonNull final SurfaceControl mContainerSurface;
+ // The decor surface is shared with the client process owning the
+ // {@link TaskFragmentOrganizer}. It can be used to draw the divider between TaskFragments
+ // or other decorations.
@VisibleForTesting
@NonNull final SurfaceControl mDecorSurface;
@@ -6812,12 +6855,18 @@
@VisibleForTesting
@NonNull TaskFragment mOwnerTaskFragment;
+ private boolean mIsBoosted;
+
+ // The surface transactions that will be applied when the layer is reassigned.
+ @NonNull private final List<SurfaceControl.Transaction> mPendingClientTransactions =
+ new ArrayList<>();
+
private DecorSurfaceContainer(@NonNull TaskFragment initialOwner) {
mOwnerTaskFragment = initialOwner;
mContainerSurface = makeSurface().setContainerLayer()
.setParent(mSurfaceControl)
.setName(mSurfaceControl + " - decor surface container")
- .setEffectLayer()
+ .setContainerLayer()
.setHidden(false)
.setCallsite("Task.DecorSurfaceContainer")
.build();
@@ -6830,14 +6879,28 @@
.build();
}
+ private void setBoosted(
+ boolean isBoosted, @Nullable SurfaceControl.Transaction clientTransaction) {
+ mIsBoosted = isBoosted;
+ // The client transaction will be applied together with the next assignLayer.
+ if (clientTransaction != null) {
+ mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction);
+ }
+ }
+
private void assignLayer(@NonNull SurfaceControl.Transaction t, int layer) {
t.setLayer(mContainerSurface, layer);
t.setVisibility(mContainerSurface, mOwnerTaskFragment.isVisible());
+ for (int i = 0; i < mPendingClientTransactions.size(); i++) {
+ t.merge(mPendingClientTransactions.get(i));
+ }
+ mPendingClientTransactions.clear();
}
private void release() {
- mDecorSurface.release();
- mContainerSurface.release();
+ getSyncTransaction()
+ .remove(mDecorSurface)
+ .remove(mContainerSurface);
}
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c919250..3cf561c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -996,8 +996,7 @@
} else {
// Still have something resumed; can't sleep until it is paused.
ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
- startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
- "sleep");
+ startPausing(true /* uiSleeping */, null /* resuming */, "sleep");
}
shouldSleep = false;
} else if (mPausingActivity != null) {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index a84a99a..74dad91 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -671,12 +671,10 @@
if (hasImeSurface) {
if (topActivity.isVisibleRequested() && dc.mInputMethodWindow != null
&& dc.isFixedRotationLaunchingApp(topActivity)) {
- removalInfo.deferRemoveForImeMode = DEFER_MODE_ROTATION;
+ removalInfo.deferRemoveMode = DEFER_MODE_ROTATION;
} else {
- removalInfo.deferRemoveForImeMode = DEFER_MODE_NORMAL;
+ removalInfo.deferRemoveMode = DEFER_MODE_NORMAL;
}
- } else {
- removalInfo.deferRemoveForImeMode = DEFER_MODE_NONE;
}
final WindowState mainWindow =
@@ -745,6 +743,7 @@
removalInfo.taskId = taskId;
removalInfo.windowlessSurface = true;
removalInfo.removeImmediately = immediately;
+ removalInfo.deferRemoveMode = DEFER_MODE_NONE;
try {
lastOrganizer.removeStartingWindow(removalInfo);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 71ffabf..40b1b20 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9098,7 +9098,7 @@
Objects.requireNonNull(outInputChannel);
synchronized (mGlobalLock) {
WindowState hostWindowState = hostInputTransferToken != null
- ? mInputToWindowMap.get(hostInputTransferToken.mToken) : null;
+ ? mInputToWindowMap.get(hostInputTransferToken.getToken()) : null;
EmbeddedWindowController.EmbeddedWindow win =
new EmbeddedWindowController.EmbeddedWindow(session, this, clientToken,
hostWindowState, callingUid, callingPid, sanitizedType, displayId,
@@ -9127,12 +9127,13 @@
// If the transferToToken exists in the input to window map, it means the request
// is to transfer from embedded to host. Otherwise, the transferToToken
// represents an embedded window so transfer from host to embedded.
- WindowState windowStateTo = mInputToWindowMap.get(transferToToken.mToken);
+ WindowState windowStateTo = mInputToWindowMap.get(transferToToken.getToken());
if (windowStateTo != null) {
didTransfer = mEmbeddedWindowController.transferToHost(transferFromToken,
windowStateTo);
} else {
- WindowState windowStateFrom = mInputToWindowMap.get(transferFromToken.mToken);
+ WindowState windowStateFrom = mInputToWindowMap.get(
+ transferFromToken.getToken());
didTransfer = mEmbeddedWindowController.transferToEmbedded(windowStateFrom,
transferToToken);
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a63e106..7e6f5ac 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -34,6 +34,7 @@
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH;
@@ -124,6 +125,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.HashMap;
@@ -1557,13 +1559,11 @@
break;
}
case OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE: {
- final Task task = taskFragment.getTask();
- task.moveOrCreateDecorSurfaceFor(taskFragment);
+ taskFragment.getTask().moveOrCreateDecorSurfaceFor(taskFragment);
break;
}
case OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE: {
- final Task task = taskFragment.getTask();
- task.removeDecorSurface();
+ taskFragment.getTask().removeDecorSurface();
break;
}
case OP_TYPE_SET_DIM_ON_TASK: {
@@ -1577,6 +1577,23 @@
operation.isMoveToBottomIfClearWhenLaunch());
break;
}
+ case OP_TYPE_SET_DECOR_SURFACE_BOOSTED: {
+ if (Flags.activityEmbeddingInteractiveDividerFlag()) {
+ final SurfaceControl.Transaction clientTransaction =
+ operation.getSurfaceTransaction();
+ if (clientTransaction != null) {
+ // Sanitize the client transaction. sanitize() silently removes invalid
+ // operations and does not throw or provide signal about whether there are
+ // any invalid operations.
+ clientTransaction.sanitize(caller.mPid, caller.mUid);
+ }
+ taskFragment.getTask().setDecorSurfaceBoosted(
+ taskFragment,
+ operation.getBooleanValue() /* isBoosted */,
+ clientTransaction);
+ }
+ break;
+ }
}
return effects;
}
@@ -1616,19 +1633,6 @@
return false;
}
- // TODO (b/293654166) remove the decor surface checks once we clear security reviews
- if ((opType == OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE
- || opType == OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE)
- && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
- final Throwable exception = new SecurityException(
- "Only a system organizer can perform OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE"
- + " or OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE."
- );
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
- opType, exception);
- return false;
- }
-
if ((opType == OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH)
&& !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
final Throwable exception = new SecurityException(
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index c778398..610fcb5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -502,7 +502,7 @@
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
- mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
+ mLocked.pointerCaptureRequest.isEnable() ? "Enabled" : "Disabled",
mLocked.pointerCaptureRequest.seq);
if (auto pc = mLocked.legacyPointerController.lock(); pc) {
dump += pc->dump();
@@ -1717,7 +1717,7 @@
return;
}
- ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling");
+ ALOGV("%s pointer capture.", request.isEnable() ? "Enabling" : "Disabling");
mLocked.pointerCaptureRequest = request;
} // release lock
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 96ef2ed..bdea4f9 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -568,7 +568,10 @@
/* clicked_entries */ browsedClickedEntries,
/* provider_of_clicked_entry */ browsedProviderUid,
/* api_status */ apiStatus,
- /* primary_indicated */ finalPhaseMetric.isPrimary()
+ /* primary_indicated */ finalPhaseMetric.isPrimary(),
+ /* oem_credential_manager_ui_uid */ finalPhaseMetric.getOemUiUid(),
+ /* fallback_credential_manager_ui_uid */ finalPhaseMetric.getFallbackUiUid(),
+ /* oem_ui_usage_status */ finalPhaseMetric.getOemUiUsageStatus()
);
} catch (Exception e) {
Slog.w(TAG, "Unexpected error during final no uid metric logging: " + e);
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index cad9a09..c5f2921 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -182,7 +182,7 @@
CredentialProviderInfo info,
String hybridService) {
Slog.i(TAG, "Filtering request options for: " + info.getComponentName());
- if (android.credentials.flags.Flags.hybridFilterFixEnabled()) {
+ if (android.credentials.flags.Flags.hybridFilterOptFixEnabled()) {
ComponentName hybridComponentName = ComponentName.unflattenFromString(hybridService);
if (hybridComponentName != null && hybridComponentName
.equals(info.getComponentName())) {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index 468d3c8..9dd6db6 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -67,6 +67,10 @@
// Other General Information, such as final api status, provider status, entry info, etc...
+ private int mOemUiUid = -1;
+ private int mFallbackUiUid = -1;
+ private OemUiUsageStatus mOemUiUsageStatus = OemUiUsageStatus.UNKNOWN;
+
private int mChosenProviderStatus = -1;
// Indicates if an exception was thrown by this provider, false by default
private boolean mHasException = false;
@@ -302,4 +306,28 @@
public boolean isPrimary() {
return mIsPrimary;
}
+
+ public void setOemUiUid(int oemUiUid) {
+ mOemUiUid = oemUiUid;
+ }
+
+ public int getOemUiUid() {
+ return mOemUiUid;
+ }
+
+ public void setFallbackUiUid(int fallbackUiUid) {
+ mFallbackUiUid = fallbackUiUid;
+ }
+
+ public int getFallbackUiUid() {
+ return mFallbackUiUid;
+ }
+
+ public void setOemUiUsageStatus(OemUiUsageStatus oemUiUsageStatus) {
+ mOemUiUsageStatus = oemUiUsageStatus;
+ }
+
+ public int getOemUiUsageStatus() {
+ return mOemUiUsageStatus.getLoggingInt();
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
new file mode 100644
index 0000000..2fd3a86
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/OemUiUsageStatus.java
@@ -0,0 +1,42 @@
+/*
+ * 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.credentials.metrics;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_NOT_SPECIFIED;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_FOUND;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_ENABLED;
+
+
+public enum OemUiUsageStatus {
+ UNKNOWN(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_UNKNOWN),
+ SUCCESS(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SUCCESS),
+ FAILURE_NOT_SPECIFIED(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_NOT_SPECIFIED),
+ FAILURE_SPECIFIED_BUT_NOT_FOUND(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_FOUND),
+ FAILURE_SPECIFIED_BUT_NOT_ENABLED(CREDENTIAL_MANAGER_FINAL_NO_UID_REPORTED__OEM_UI_USAGE_STATUS__OEM_UI_USAGE_STATUS_SPECIFIED_BUT_NOT_ENABLED);
+
+ private final int mLoggingInt;
+
+ OemUiUsageStatus(int loggingInt) {
+ mLoggingInt = loggingInt;
+ }
+
+ public int getLoggingInt() {
+ return mLoggingInt;
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 73d830d..c6189ed 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.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 @@
startSystemCaptionsManagerService(context, t);
startTextToSpeechManagerService(context, t);
startWearableSensingService(t);
+ startOnDeviceIntelligenceService(t);
if (deviceHasConfigString(
context, R.string.config_defaultAmbientContextDetectionService)) {
@@ -3335,6 +3337,12 @@
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 acaec21..fd2e8c8 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.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 @@
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 67df67f..af8ce31 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 @@
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 @@
SigningDetails.CertCapabilities.PERMISSION
)
if (!Flags.signaturePermissionAllowlistEnabled()) {
- return hasCommonSigner;
+ return hasCommonSigner
}
if (!hasCommonSigner) {
return false
@@ -1308,7 +1314,7 @@
" ${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 0000000..ad2d70bb
--- /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/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
new file mode 100644
index 0000000..1f3184d
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.display
+
+import android.os.IBinder
+import androidx.test.filters.SmallTest
+import com.android.server.display.brightness.clamper.HdrClamper
+import com.android.server.display.feature.DisplayManagerFlags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+private const val MAX_BRIGHTNESS = 1.0f
+private const val TRANSITION_POINT = 0.7f
+private const val NORMAL_BRIGHTNESS_HIGH = 0.8f
+private const val NORMAL_BRIGHTNESS_LOW = 0.6f
+
+@SmallTest
+class BrightnessRangeControllerTest {
+
+ private val mockHbmController = mock<HighBrightnessModeController>()
+ private val mockCallback = mock<Runnable>()
+ private val mockConfig = mock<DisplayDeviceConfig>()
+ private val mockNormalBrightnessController = mock<NormalBrightnessModeController>()
+ private val mockHdrClamper = mock<HdrClamper>()
+ private val mockFlags = mock<DisplayManagerFlags>()
+ private val mockToken = mock<IBinder>()
+
+ @Test
+ fun `returns HBC max brightness if HBM supported and ON`() {
+ val controller = createController()
+ assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+ }
+
+ @Test
+ fun `returns NBC max brightness if device does not support HBM`() {
+ val controller = createController(hbmSupported = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+ }
+
+ @Test
+ fun `returns NBC max brightness if HBM not allowed`() {
+ val controller = createController(hbmAllowed = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+ }
+
+ @Test
+ fun `returns HBC max brightness if NBM is disabled`() {
+ val controller = createController(nbmEnabled = false, hbmAllowed = false)
+ assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+ }
+
+ @Test
+ fun `returns HBC max brightness if lower than NBC max brightness`() {
+ val controller = createController(
+ hbmAllowed = false,
+ hbmMaxBrightness = TRANSITION_POINT,
+ nbmMaxBrightness = NORMAL_BRIGHTNESS_HIGH
+ )
+ assertThat(controller.currentBrightnessMax).isEqualTo(TRANSITION_POINT)
+ }
+
+ private fun createController(
+ nbmEnabled: Boolean = true,
+ hbmSupported: Boolean = true,
+ hbmAllowed: Boolean = true,
+ hbmMaxBrightness: Float = MAX_BRIGHTNESS,
+ nbmMaxBrightness: Float = NORMAL_BRIGHTNESS_LOW
+ ): BrightnessRangeController {
+ whenever(mockFlags.isNbmControllerEnabled).thenReturn(nbmEnabled)
+ whenever(mockHbmController.deviceSupportsHbm()).thenReturn(hbmSupported)
+ whenever(mockHbmController.isHbmCurrentlyAllowed).thenReturn(hbmAllowed)
+ whenever(mockHbmController.currentBrightnessMax).thenReturn(hbmMaxBrightness)
+ whenever(mockNormalBrightnessController.currentBrightnessMax).thenReturn(nbmMaxBrightness)
+
+ return BrightnessRangeController(mockHbmController, mockCallback, mockConfig,
+ mockNormalBrightnessController, mockHdrClamper, mockFlags, mockToken,
+ DisplayDeviceInfo())
+ }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index b142334..18f0311 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -2408,6 +2408,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2440,6 +2441,7 @@
.when(() -> SystemProperties.getBoolean(ENABLE_ON_CONNECT, false));
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerInternal localService = displayManager.new LocalService();
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2487,6 +2489,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
@@ -2652,6 +2655,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
DisplayManagerInternal localService = displayManager.new LocalService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -2699,6 +2703,7 @@
when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
manageDisplaysPermission(/* granted= */ true);
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
DisplayManagerService.BinderService bs = displayManager.new BinderService();
LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 76b7780..fb23213 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1927,6 +1927,8 @@
mock(ScreenOffBrightnessSensorController.class);
final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
final HdrClamper hdrClamper = mock(HdrClamper.class);
+ final NormalBrightnessModeController normalBrightnessModeController = mock(
+ NormalBrightnessModeController.class);
BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
@@ -1939,7 +1941,8 @@
TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
- hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
+ hysteresisLevels, screenOffBrightnessSensorController,
+ hbmController, normalBrightnessModeController, hdrClamper,
clamperController, mDisplayManagerFlagsMock));
final LogicalDisplay display = mock(LogicalDisplay.class);
@@ -2027,6 +2030,8 @@
private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
private final HighBrightnessModeController mHighBrightnessModeController;
+ private final NormalBrightnessModeController mNormalBrightnessModeController;
+
private final HdrClamper mHdrClamper;
private final BrightnessClamperController mClamperController;
@@ -2040,6 +2045,7 @@
HysteresisLevels hysteresisLevels,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController highBrightnessModeController,
+ NormalBrightnessModeController normalBrightnessModeController,
HdrClamper hdrClamper,
BrightnessClamperController clamperController,
DisplayManagerFlags flags) {
@@ -2051,6 +2057,7 @@
mHysteresisLevels = hysteresisLevels;
mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
mHighBrightnessModeController = highBrightnessModeController;
+ mNormalBrightnessModeController = normalBrightnessModeController;
mHdrClamper = hdrClamper;
mClamperController = clamperController;
mFlags = flags;
@@ -2163,7 +2170,8 @@
DisplayDeviceConfig displayDeviceConfig, Handler handler,
DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
return new BrightnessRangeController(hbmController, modeChangeCallback,
- displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
+ displayDeviceConfig, mNormalBrightnessModeController, mHdrClamper,
+ mFlags, displayToken, info);
}
@Override
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 1529a08..1a71e77 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -228,13 +228,27 @@
@Test
public void testOnExternalDisplayAvailable() {
- when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
+
mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+ assertNotAskedToEnableDisplay();
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayConnected(any());
+
+ mExternalDisplayPolicy.onBootCompleted();
assertAskedToEnableDisplay();
verify(mMockedExternalDisplayStatsService).onDisplayConnected(eq(mMockedLogicalDisplay));
}
@Test
+ public void testOnExternalDisplayUnpluggedBeforeBootCompletes() {
+ mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+ mExternalDisplayPolicy.handleLogicalDisplayDisconnectedLocked(mMockedLogicalDisplay);
+ mExternalDisplayPolicy.onBootCompleted();
+ assertNotAskedToEnableDisplay();
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayConnected(any());
+ verify(mMockedExternalDisplayStatsService, never()).onDisplayDisconnected(anyInt());
+ }
+
+ @Test
public void testOnExternalDisplayAvailable_criticalThermalCondition()
throws RemoteException {
// Disallow external displays due to thermals.
@@ -303,8 +317,14 @@
mDisplayEventCaptor.capture());
assertThat(mLogicalDisplayCaptor.getValue()).isEqualTo(mMockedLogicalDisplay);
assertThat(mDisplayEventCaptor.getValue()).isEqualTo(EVENT_DISPLAY_CONNECTED);
+ verify(mMockedLogicalDisplay).setEnabledLocked(false);
clearInvocations(mMockedLogicalDisplayMapper);
- when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(true);
+ clearInvocations(mMockedLogicalDisplay);
+ }
+
+ private void assertNotAskedToEnableDisplay() {
+ verify(mMockedInjector, never()).sendExternalDisplayEventLocked(any(), anyInt());
+ verify(mMockedLogicalDisplay, never()).setEnabledLocked(anyBoolean());
}
private void assertIsExternalDisplayAllowed(final boolean enabled) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index bd20ae2..ce281da 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -56,6 +56,7 @@
import org.junit.Rule;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.File;
@@ -160,6 +161,7 @@
realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+ realAms.mOomAdjuster.mCachedAppOptimizer = Mockito.mock(CachedAppOptimizer.class);
realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
ExtendedMockito.doNothing().when(() -> ProcessList.setOomAdj(anyInt(), anyInt(), anyInt()));
realAms.mPackageManagerInt = mPackageManagerInt;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 66ab807..1172685 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -86,7 +86,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
@@ -2335,8 +2334,8 @@
.isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue));
}
- @Ignore
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DEFER_OUTGOING_BCASTS)
public void testDeferOutgoingBroadcasts() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
setProcessFreezable(callerApp, true /* pendingFreeze */, false /* frozen */);
@@ -2350,6 +2349,8 @@
makeRegisteredReceiver(receiverGreenApp),
makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+ // Verify that we invoke the call to freeze the caller app.
+ verify(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncImmediateLSP(callerApp);
waitForIdle();
verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
index daa0211..8337fd2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/OWNERS
@@ -1,3 +1 @@
-ancr@google.com
-harshitmahajan@google.com
-robertogil@google.com
+include /services/core/java/com/android/server/crashrecovery/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 7f88b00..eb89503 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -88,6 +88,7 @@
import com.android.compatibility.common.util.TestUtils;
import com.android.internal.R;
+import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.FloatingMenuSize;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -1318,6 +1319,152 @@
}
}
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_statusBarServiceNotGranted_throwsException() {
+ mTestableContext.getTestablePermissions().setPermission(
+ Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
+ mockManageAccessibilityGranted(mTestableContext);
+
+ assertThrows(SecurityException.class,
+ () -> mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_manageAccessibilityNotGranted_throwsException() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mTestableContext.getTestablePermissions().setPermission(
+ Manifest.permission.STATUS_BAR_SERVICE, PackageManager.PERMISSION_DENIED);
+
+ assertThrows(SecurityException.class,
+ () -> mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME)));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ List<ComponentName> tiles = List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+ AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+ );
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ tiles
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
+ ).containsExactlyElementsIn(tiles);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_sameQsTiles_noUpdateToA11yTilesInQsPanel() {
+ notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel();
+ List<ComponentName> tiles =
+ mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel().stream().toList();
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ tiles
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel()
+ ).containsExactlyElementsIn(tiles);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_serviceWarningRequired_qsShortcutRemainDisabled() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+ ComponentName tile = new ComponentName(
+ TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+ TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(tile)
+ );
+
+ assertThat(mA11yms.getCurrentUserState().getA11yQsTargets()).doesNotContain(tile);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_serviceWarningNotRequired_qsShortcutEnabled() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ setupShortcutTargetServices();
+ final AccessibilityUserState userState = mA11yms.getCurrentUserState();
+ userState.mAccessibilityButtonTargets.clear();
+ userState.mAccessibilityButtonTargets.add(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
+ ComponentName tile = new ComponentName(
+ TARGET_ALWAYS_ON_A11Y_SERVICE.getPackageName(),
+ TARGET_ALWAYS_ON_A11Y_SERVICE_TILE_CLASS);
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ List.of(tile)
+ );
+
+ assertThat(mA11yms.getCurrentUserState().getA11yQsTargets())
+ .contains(TARGET_ALWAYS_ON_A11Y_SERVICE.flattenToString());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled() {
+ mockStatusBarServiceGranted(mTestableContext);
+ mockManageAccessibilityGranted(mTestableContext);
+ List<ComponentName> tiles = List.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+ AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+ );
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ tiles
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTargets()
+ ).containsExactlyElementsIn(List.of(
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString())
+ );
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void notifyQuickSettingsTilesChanged_removeFrameworkTile_qsShortcutDisabled() {
+ notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled();
+ Set<ComponentName> qsTiles = mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel();
+ qsTiles.remove(AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME);
+
+ mA11yms.notifyQuickSettingsTilesChanged(
+ mA11yms.getCurrentUserState().mUserId,
+ qsTiles.stream().toList()
+ );
+
+ assertThat(
+ mA11yms.getCurrentUserState().getA11yQsTargets()
+ ).doesNotContain(
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString());
+ }
+
private static AccessibilityServiceInfo mockAccessibilityServiceInfo(
ComponentName componentName) {
return mockAccessibilityServiceInfo(
@@ -1367,6 +1514,11 @@
PackageManager.PERMISSION_GRANTED);
}
+ private void mockStatusBarServiceGranted(TestableContext context) {
+ context.getTestablePermissions().setPermission(Manifest.permission.STATUS_BAR_SERVICE,
+ PackageManager.PERMISSION_GRANTED);
+ }
+
private void assertStartActivityWithExpectedComponentName(Context mockContext,
String componentName) {
verify(mockContext).startActivityAsUser(mIntentArgumentCaptor.capture(),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 52a5d8f..b1964e2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -30,6 +30,8 @@
import static com.android.server.accessibility.AccessibilityUserState.doesShortcutTargetsStringContain;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
@@ -45,6 +47,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.graphics.Color;
import android.platform.test.annotations.RequiresFlagsDisabled;
@@ -59,6 +63,7 @@
import androidx.test.InstrumentationRegistry;
import com.android.internal.R;
+import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.After;
@@ -68,6 +73,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Map;
+import java.util.Set;
+
/** Tests for AccessibilityUserState */
public class AccessibilityUserStateTest {
@@ -431,7 +439,70 @@
assertEquals(focusStrokeWidthValue, mUserState.getFocusStrokeWidthLocked());
assertEquals(focusColorValue, mUserState.getFocusColorLocked());
+ }
+ @Test
+ public void updateA11yQsTargetLocked_valueUpdated() {
+ Set<String> newTargets = Set.of(
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString(),
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString()
+ );
+
+ mUserState.updateA11yQsTargetLocked(newTargets);
+
+ assertThat(mUserState.getA11yQsTargets()).isEqualTo(newTargets);
+ }
+
+ @Test
+ public void getA11yQsTargets_returnsCopiedData() {
+ updateA11yQsTargetLocked_valueUpdated();
+
+ Set<String> targets = mUserState.getA11yQsTargets();
+ targets.clear();
+
+ assertThat(mUserState.getA11yQsTargets()).isNotEmpty();
+ }
+
+ @Test
+ public void updateA11yTilesInQsPanelLocked_valueUpdated() {
+ Set<ComponentName> newTargets = Set.of(
+ AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
+ AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME
+ );
+
+ mUserState.updateA11yTilesInQsPanelLocked(newTargets);
+
+ assertThat(mUserState.getA11yQsTilesInQsPanel()).isEqualTo(newTargets);
+ }
+
+ @Test
+ public void getA11yQsTilesInQsPanel_returnsCopiedData() {
+ updateA11yTilesInQsPanelLocked_valueUpdated();
+
+ Set<ComponentName> targets = mUserState.getA11yQsTilesInQsPanel();
+ targets.clear();
+
+ assertThat(mUserState.getA11yQsTilesInQsPanel()).isNotEmpty();
+ }
+
+ @Test
+ public void getTileServiceToA11yServiceInfoMapLocked() {
+ final ComponentName tileComponent =
+ new ComponentName(COMPONENT_NAME.getPackageName(), "FakeTileService");
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = tileComponent.getPackageName();
+ serviceInfo.name = COMPONENT_NAME.getClassName();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ when(mMockServiceInfo.getTileServiceName()).thenReturn(tileComponent.getClassName());
+ when(mMockServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
+ mUserState.mInstalledServices.add(mMockServiceInfo);
+ mUserState.updateTileServiceMapForAccessibilityServiceLocked();
+
+ Map<ComponentName, AccessibilityServiceInfo> actual =
+ mUserState.getTileServiceToA11yServiceInfoMapLocked();
+
+ assertThat(actual).containsExactly(tileComponent, mMockServiceInfo);
}
private int getSecureIntForUser(String key, int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index dc26e6e..f6dc2f0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -18,21 +18,23 @@
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.windowId;
import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.a11yWindowId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.EventWindowIdMatcher.eventWindowId;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -350,6 +352,88 @@
}
@Test
+ public void onWindowsChanged_shouldNotReportNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, not(hasItem(windowId(windowId))));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedNonTouchableWindow() {
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ when(window.isTouchable()).thenReturn(false);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, window.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasItem(windowId(windowId)));
+ }
+
+ @Test
+ public void onWindowsChanged_trustedFocusedNonTouchableWindow_shouldNotHideWindowsBelow() {
+ // Make the focused trusted un-touchable window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.isTouchable()).thenReturn(false);
+ when(window.isTrustedOverlay()).thenReturn(true);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_accessibilityOverlay_shouldNotHideWindowsBelow() {
+ // Make the a11y overlay window fullscreen.
+ final AccessibilityWindow window = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(window, new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ when(window.getType()).thenReturn(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(NUM_OF_WINDOWS));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportFocusedWindowEvenIfOccluded() {
+ // Make the front window fullscreen.
+ final AccessibilityWindow frontWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(frontWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ final int frontWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, frontWindow.getWindowInfo().token);
+
+ final AccessibilityWindow focusedWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX);
+ final int focusedWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, focusedWindow.getWindowInfo().token);
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
+ assertThat(a11yWindows.get(0), windowId(frontWindowId));
+ assertThat(a11yWindows.get(1), windowId(focusedWindowId));
+ }
+
+ @Test
public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
assertNotEquals("new title",
toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
@@ -631,11 +715,11 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -661,7 +745,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -710,12 +794,12 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(initialDisplayId),
- a11yWindowId(initialWindowId),
+ eventWindowId(initialWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(eventDisplayId),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(
AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
}
@@ -771,11 +855,11 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(eventWindowId),
+ eventWindowId(eventWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
assertThat(captor.getAllValues().get(1),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(currentActiveWindowId),
+ eventWindowId(currentActiveWindowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
}
@@ -979,7 +1063,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
}
@@ -1001,7 +1085,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
}
@@ -1019,7 +1103,7 @@
.sendAccessibilityEventForCurrentUserLocked(captor.capture());
assertThat(captor.getAllValues().get(0),
allOf(displayId(Display.DEFAULT_DISPLAY),
- a11yWindowId(windowId),
+ eventWindowId(windowId),
a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
}
@@ -1173,8 +1257,6 @@
private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
final WindowInfo windowInfo = WindowInfo.obtain();
- // TODO(b/325341171): add tests with various kinds of windows such as
- // changing window types, touchable or not, trusted or not, etc.
windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
windowInfo.token = windowToken.asBinder();
@@ -1235,16 +1317,16 @@
}
}
- static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ static class EventWindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
private int mWindowId;
- WindowIdMatcher(int windowId) {
+ EventWindowIdMatcher(int windowId) {
super();
mWindowId = windowId;
}
- static WindowIdMatcher a11yWindowId(int windowId) {
- return new WindowIdMatcher(windowId);
+ static EventWindowIdMatcher eventWindowId(int windowId) {
+ return new EventWindowIdMatcher(windowId);
}
@Override
@@ -1280,4 +1362,27 @@
description.appendText("Matching to window changes " + mWindowChanges);
}
}
+
+ static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityWindowInfo> {
+ private final int mWindowId;
+
+ WindowIdMatcher(int windowId) {
+ super();
+ mWindowId = windowId;
+ }
+
+ static WindowIdMatcher windowId(int windowId) {
+ return new WindowIdMatcher(windowId);
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityWindowInfo window) {
+ return window.getId() == mWindowId;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to windowId " + mWindowId);
+ }
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 26cda65..99ab405 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -325,7 +325,6 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
-import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -6223,6 +6222,52 @@
}
@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);
@@ -13180,35 +13225,6 @@
}
@Test
- public void fixNotification_customAllowlistToken()
- throws Exception {
- Notification n = new Notification.Builder(mContext, "test")
- .build();
- try {
- Field allowlistToken = Class.forName("android.app.Notification").
- getDeclaredField("mAllowlistToken");
- allowlistToken.setAccessible(true);
- allowlistToken.set(n, new Binder());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
- mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true);
-
- IBinder actual = null;
- try {
- Field allowlistToken = Class.forName("android.app.Notification").
- getDeclaredField("mAllowlistToken");
- allowlistToken.setAccessible(true);
- actual = (IBinder) allowlistToken.get(n);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
-
- assertTrue(mService.ALLOWLIST_TOKEN == actual);
- }
-
- @Test
public void testCancelAllNotifications_IgnoreUserInitiatedJob() throws Exception {
when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString()))
.thenReturn(true);
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 6976ec3..07d25df 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 @@
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 @@
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 boolean checkLastSensitiveLog(boolean hasPosted, boolean hasSensitive, int lifetime) {
+ if (lastSensitiveLog == null) {
+ return false;
+ }
+ return hasPosted == lastSensitiveLog.hasPosted
+ && hasSensitive == lastSensitiveLog.hasSensitiveContent
+ && lifetime == lastSensitiveLog.lifetime;
+ }
+
public interface ComponentPermissionChecker {
int check(String permission, int uid, int owningUid, boolean exported);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 961fdfb..10cfb5f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -315,6 +315,23 @@
}
@Test
+ public void testUserLeaving() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity.getTask();
+ mSupervisor.mUserLeaving = true;
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+ task.sleepIfPossible(false /* shuttingDown */);
+ verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+
+ clearInvocations(task);
+ activity.setState(ActivityRecord.State.RESUMED, "test");
+ task.setPausingActivity(null);
+ doReturn(false).when(task).canBeResumed(any());
+ task.pauseActivityIfNeeded(null /* resuming */, "test");
+ verify(task).startPausing(eq(true) /* userLeaving */, anyBoolean(), any(), any());
+ }
+
+ @Test
public void testSwitchUser() {
final Task rootTask = createTask(mDisplayContent);
final Task childTask = createTaskInRootTask(rootTask, 0 /* userId */);
@@ -1823,6 +1840,66 @@
}
@Test
+ public void testAssignChildLayers_boostedDecorSurfacePlacement() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ final ActivityRecord unembeddedActivity = task.getTopMostActivity();
+
+ final TaskFragment fragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final TaskFragment fragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final SurfaceControl.Transaction t = task.getSyncTransaction();
+ final SurfaceControl.Transaction clientTransaction = mock(SurfaceControl.Transaction.class);
+
+ doNothing().when(task).sendTaskFragmentParentInfoChangedIfNeeded();
+ spyOn(unembeddedActivity);
+ spyOn(fragment1);
+ spyOn(fragment2);
+
+ doReturn(true).when(unembeddedActivity).isUid(task.effectiveUid);
+ doReturn(true).when(fragment1).isAllowedToBeEmbeddedInTrustedMode();
+ doReturn(false).when(fragment2).isAllowedToBeEmbeddedInTrustedMode();
+ doReturn(true).when(fragment1).isVisible();
+
+ task.moveOrCreateDecorSurfaceFor(fragment1);
+
+ clearInvocations(t);
+ clearInvocations(unembeddedActivity);
+ clearInvocations(fragment1);
+ clearInvocations(fragment2);
+
+ // The decor surface should be placed above all the windows when boosted and the cover
+ // surface should show.
+ task.setDecorSurfaceBoosted(fragment1, true /* isBoosted */, clientTransaction);
+
+ verify(unembeddedActivity).assignLayer(t, 0);
+ verify(fragment1).assignLayer(t, 1);
+ verify(fragment2).assignLayer(t, 2);
+ verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 3);
+
+ verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+ verify(t).merge(clientTransaction);
+
+ clearInvocations(t);
+ clearInvocations(unembeddedActivity);
+ clearInvocations(fragment1);
+ clearInvocations(fragment2);
+
+ // The decor surface should be placed just above the owner TaskFragment and the cover
+ // surface should hide.
+ task.moveOrCreateDecorSurfaceFor(fragment1);
+ task.setDecorSurfaceBoosted(fragment1, false /* isBoosted */, clientTransaction);
+
+ verify(unembeddedActivity).assignLayer(t, 0);
+ verify(fragment1).assignLayer(t, 1);
+ verify(t).setLayer(task.mDecorSurfaceContainer.mContainerSurface, 2);
+ verify(fragment2).assignLayer(t, 3);
+
+ verify(t).setVisibility(task.mDecorSurfaceContainer.mContainerSurface, true);
+ verify(t).merge(clientTransaction);
+
+ }
+
+ @Test
public void testMoveTaskFragmentsToBottomIfNeeded() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d3a50bb..df32fbd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3315,6 +3315,18 @@
"support_no_reply_timer_for_cfnry_bool";
/**
+ * No reply time value to be sent to network for call forwarding on no reply
+ * (CFNRy 3GPP TS 24.082 version 17.0 section 3).
+ * Controls time in seconds for the no reply condition on in the call forwarding
+ * settings UI.
+ * This is available when {@link #KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL} is true.
+ *
+ * @hide
+ */
+ public static final String KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT =
+ "no_reply_timer_for_cfnry_sec_int";
+
+ /**
* List of the FAC (feature access codes) to dial as a normal call.
* @hide
*/
@@ -10666,6 +10678,7 @@
sDefaults.putBoolean(KEY_VT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_RTT_CALL_BOOL, true);
sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true);
+ sDefaults.putInt(KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT, 20);
sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
sDefaults.putBoolean(KEY_SHOW_PRECISE_FAILED_CAUSE_BOOL, false);
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
deleted file mode 100644
index 8faf224..0000000
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.input.screenshot
-
-import androidx.test.platform.app.InstrumentationRegistry
-import platform.test.screenshot.GoldenImagePathManager
-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,
- ) {
- 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"
- }
-}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
similarity index 62%
copy from packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
copy to tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
index f5fba7f..9f14b136 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenPathManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,31 +14,28 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.screenshot.util
+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 Settings screenshot tests. */
-class SettingsGoldenImagePathManager(
- pathConfig: PathConfig,
- assetsPathRelativeToBuildRoot: String
-) :
- GoldenImagePathManager(
+/** 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() + "/settings_screenshots",
+ 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 "SettingsGoldenImagePathManager"
+ return "InputGoldenPathManager"
}
}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
index 75dab41..2f40896 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -44,7 +44,7 @@
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- InputGoldenImagePathManager(
+ InputGoldenPathManager(
getEmulatedDevicePathConfig(emulationSpec),
assetsPathRelativeToBuildRoot
)