diff options
282 files changed, 7252 insertions, 3223 deletions
diff --git a/ACTIVITY_MANAGER_OWNERS b/ACTIVITY_MANAGER_OWNERS new file mode 100644 index 000000000000..47782d1406c4 --- /dev/null +++ b/ACTIVITY_MANAGER_OWNERS @@ -0,0 +1,4 @@ +mwachens@google.com +sudheersai@google.com +varunshah@google.com +yamasani@google.com diff --git a/AconfigFlags.bp b/AconfigFlags.bp index dd919cacc534..a0f38d9a7bd9 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -680,6 +680,11 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +cc_aconfig_library { + name: "com.android.media.flags.editing-aconfig-cc", + aconfig_declarations: "com.android.media.flags.editing-aconfig", +} + // MediaProjection aconfig_declarations { name: "com.android.media.flags.projection-aconfig", diff --git a/BATTERY_STATS_OWNERS b/BATTERY_STATS_OWNERS index 7728975fcec1..575bded5ad64 100644 --- a/BATTERY_STATS_OWNERS +++ b/BATTERY_STATS_OWNERS @@ -1,4 +1,3 @@ # OWNERS of BatteryStats related files -bookatz@google.com dplotnikov@google.com mwachens@google.com diff --git a/OOM_ADJUSTER_OWNERS b/OOM_ADJUSTER_OWNERS new file mode 100644 index 000000000000..7727f9f014c0 --- /dev/null +++ b/OOM_ADJUSTER_OWNERS @@ -0,0 +1,3 @@ +mwachens@google.com +dplotnikov@google.com +tyk@google.com diff --git a/apex/jobscheduler/ALARM_OWNERS b/apex/jobscheduler/ALARM_OWNERS new file mode 100644 index 000000000000..5c3bff7b42f4 --- /dev/null +++ b/apex/jobscheduler/ALARM_OWNERS @@ -0,0 +1,2 @@ +suprabh@google.com +tetianameronyk@google.com
\ No newline at end of file diff --git a/apex/jobscheduler/DEVICE_IDLE_OWNERS b/apex/jobscheduler/DEVICE_IDLE_OWNERS new file mode 100644 index 000000000000..62db91db98f8 --- /dev/null +++ b/apex/jobscheduler/DEVICE_IDLE_OWNERS @@ -0,0 +1,2 @@ +suprabh@google.com +guanxin@google.com
\ No newline at end of file diff --git a/apex/jobscheduler/JOB_OWNERS b/apex/jobscheduler/JOB_OWNERS new file mode 100644 index 000000000000..05bf25c50d60 --- /dev/null +++ b/apex/jobscheduler/JOB_OWNERS @@ -0,0 +1,3 @@ +suprabh@google.com +varunshah@google.com +guanxin@google.com
\ No newline at end of file diff --git a/apex/jobscheduler/OWNERS b/apex/jobscheduler/OWNERS index 22b648975e5f..ffa58022c4f7 100644 --- a/apex/jobscheduler/OWNERS +++ b/apex/jobscheduler/OWNERS @@ -1,8 +1,25 @@ -ctate@android.com -ctate@google.com -dplotnikov@google.com -jji@google.com -omakoto@google.com -suprabh@google.com -varunshah@google.com -yamasani@google.com +# For Job Scheduler and App Standby changes, JOB_OWNERS +per-file JOB_OWNERS = file:/apex/jobscheduler/JOB_OWNERS +per-file framework/aconfig/job.aconfig = file:/apex/jobscheduler/JOB_OWNERS +per-file service/aconfig/job.aconfig = file:/apex/jobscheduler/JOB_OWNERS +per-file service/aconfig/app_idle.aconfig = file:/apex/jobscheduler/JOB_OWNERS +per-file *Job* = file:/apex/jobscheduler/JOB_OWNERS +per-file *Standby* = file:/apex/jobscheduler/JOB_OWNERS +per-file framework/java/android/app/job/* = file:/apex/jobscheduler/JOB_OWNERS +per-file framework/java/com/android/server/job/* = file:/apex/jobscheduler/JOB_OWNERS +per-file service/java/com/android/server/job/* = file:/apex/jobscheduler/JOB_OWNERS + +# For Alarm Manager changes, ALARM_OWNERS +per-file ALARM_OWNERS = file:/apex/jobscheduler/ALARM_OWNERS +per-file service/aconfig/alarm.aconfig = file:/apex/jobscheduler/ALARM_OWNERS +per-file *Alarm* = file:/apex/jobscheduler/ALARM_OWNERS +per-file service/java/com/android/server/alarm/* = file:/apex/jobscheduler/ALARM_OWNERS + +# For Device Idle changes, DEVICE_IDLE_OWNERS +per-file DEVICE_IDLE_OWNERS = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS +per-file service/aconfig/device_idle.aconfig = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS +per-file *Idle* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS +per-file service/java/com/android/server/deviceidle/* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS +per-file framework/java/com/android/server/deviceidle/* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS + +suprabh@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file diff --git a/apex/jobscheduler/framework/java/android/app/job/OWNERS b/apex/jobscheduler/framework/java/android/app/job/OWNERS deleted file mode 100644 index 0b1e559dda15..000000000000 --- a/apex/jobscheduler/framework/java/android/app/job/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# Bug component: 330738 - -yamasani@google.com -omakoto@google.com -ctate@android.com -ctate@google.com diff --git a/core/api/current.txt b/core/api/current.txt index 542543d11318..d0e20031c74d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -19396,7 +19396,7 @@ package android.hardware.camera2 { public abstract static class CameraExtensionSession.ExtensionCaptureCallback { ctor public CameraExtensionSession.ExtensionCaptureCallback(); method public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, int); + method public void onCaptureFailed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, int); method public void onCaptureProcessProgressed(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, @IntRange(from=0, to=100) int); method public void onCaptureProcessStarted(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest); method public void onCaptureResultAvailable(@NonNull android.hardware.camera2.CameraExtensionSession, @NonNull android.hardware.camera2.CaptureRequest, @NonNull android.hardware.camera2.TotalCaptureResult); @@ -19921,7 +19921,7 @@ package android.hardware.camera2 { field @Deprecated @NonNull public static final android.hardware.camera2.CaptureResult.Key<float[]> LENS_RADIAL_DISTORTION; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> LENS_STATE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.String> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_SENSOR_CROP_REGION; + field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_SENSOR_CROP_REGION; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> NOISE_REDUCTION_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH; @@ -19947,7 +19947,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_FACE_DETECT_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Point[]> STATISTICS_HOT_PIXEL_MAP; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> STATISTICS_HOT_PIXEL_MAP_MODE; - field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.LensIntrinsicsSample[]> STATISTICS_LENS_INTRINSICS_SAMPLES; + field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.LensIntrinsicsSample[]> STATISTICS_LENS_INTRINSICS_SAMPLES; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.LensShadingMap> STATISTICS_LENS_SHADING_CORRECTION_MAP; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_LENS_SHADING_MAP_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> STATISTICS_OIS_DATA_MODE; @@ -20107,10 +20107,10 @@ package android.hardware.camera2.params { method public boolean isMultiResolution(); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class LensIntrinsicsSample { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public LensIntrinsicsSample(long, @NonNull float[]); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public float[] getLensIntrinsics(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getTimestampNanos(); + public final class LensIntrinsicsSample { + ctor public LensIntrinsicsSample(long, @NonNull float[]); + method @NonNull public float[] getLensIntrinsics(); + method public long getTimestampNanos(); } public final class LensShadingMap { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b2a49e11c8df..cc0354c32de2 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4950,94 +4950,94 @@ package android.hardware.camera2 { package android.hardware.camera2.extension { - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class AdvancedExtender { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public AdvancedExtender(@NonNull android.hardware.camera2.CameraManager); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureRequest.Key> getAvailableCaptureRequestKeys(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.List<android.hardware.camera2.CaptureResult.Key> getAvailableCaptureResultKeys(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.camera_extensions_characteristics_get") @NonNull public abstract java.util.List<android.util.Pair<android.hardware.camera2.CameraCharacteristics.Key,java.lang.Object>> getAvailableCharacteristicsKeyValues(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public long getMetadataVendorId(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.SessionProcessor getSessionProcessor(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedCaptureOutputResolutions(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedPreviewOutputResolutions(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void initialize(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean isExtensionAvailable(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap); - } - - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class CameraExtensionService extends android.app.Service { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") protected CameraExtensionService(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.AdvancedExtender onInitializeAdvancedExtension(int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract boolean onRegisterClient(@NonNull android.os.IBinder); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onUnregisterClient(@NonNull android.os.IBinder); - } - - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class CameraOutputSurface { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public CameraOutputSurface(@NonNull android.view.Surface, @NonNull android.util.Size); + public abstract class AdvancedExtender { + ctor public AdvancedExtender(@NonNull android.hardware.camera2.CameraManager); + method @NonNull public abstract java.util.List<android.hardware.camera2.CaptureRequest.Key> getAvailableCaptureRequestKeys(@NonNull String); + method @NonNull public abstract java.util.List<android.hardware.camera2.CaptureResult.Key> getAvailableCaptureResultKeys(@NonNull String); + method @NonNull public abstract java.util.List<android.util.Pair<android.hardware.camera2.CameraCharacteristics.Key,java.lang.Object>> getAvailableCharacteristicsKeyValues(); + method public long getMetadataVendorId(@NonNull String); + method @NonNull public abstract android.hardware.camera2.extension.SessionProcessor getSessionProcessor(); + method @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedCaptureOutputResolutions(@NonNull String); + method @NonNull public abstract java.util.Map<java.lang.Integer,java.util.List<android.util.Size>> getSupportedPreviewOutputResolutions(@NonNull String); + method public abstract void initialize(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap); + method public abstract boolean isExtensionAvailable(@NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap); + } + + public abstract class CameraExtensionService extends android.app.Service { + ctor protected CameraExtensionService(); + method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); + method @NonNull public abstract android.hardware.camera2.extension.AdvancedExtender onInitializeAdvancedExtension(int); + method public abstract boolean onRegisterClient(@NonNull android.os.IBinder); + method public abstract void onUnregisterClient(@NonNull android.os.IBinder); + } + + public final class CameraOutputSurface { + ctor public CameraOutputSurface(@NonNull android.view.Surface, @NonNull android.util.Size); method public int getColorSpace(); method public long getDynamicRangeProfile(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int getImageFormat(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.util.Size getSize(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public android.view.Surface getSurface(); + method public int getImageFormat(); + method @NonNull public android.util.Size getSize(); + method @NonNull public android.view.Surface getSurface(); method public void setDynamicRangeProfile(long); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class CharacteristicsMap { - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @Nullable public android.hardware.camera2.CameraCharacteristics get(@NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.Set<java.lang.String> getCameraIds(); + public class CharacteristicsMap { + method @Nullable public android.hardware.camera2.CameraCharacteristics get(@NonNull String); + method @NonNull public java.util.Set<java.lang.String> getCameraIds(); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionConfiguration { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest); + public class ExtensionConfiguration { + ctor public ExtensionConfiguration(int, int, @NonNull java.util.List<android.hardware.camera2.extension.ExtensionOutputConfiguration>, @Nullable android.hardware.camera2.CaptureRequest); method public void setColorSpace(int); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public class ExtensionOutputConfiguration { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public ExtensionOutputConfiguration(@NonNull java.util.List<android.hardware.camera2.extension.CameraOutputSurface>, int, @Nullable String, int); + public class ExtensionOutputConfiguration { + ctor public ExtensionOutputConfiguration(@NonNull java.util.List<android.hardware.camera2.extension.CameraOutputSurface>, int, @Nullable String, int); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class RequestProcessor { - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void abortCaptures(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int setRepeating(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void stopRepeating(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submit(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public int submitBurst(@NonNull java.util.List<android.hardware.camera2.extension.RequestProcessor.Request>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; + public final class RequestProcessor { + method public void abortCaptures(); + method public int setRepeating(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; + method public void stopRepeating(); + method public int submit(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; + method public int submitBurst(@NonNull java.util.List<android.hardware.camera2.extension.RequestProcessor.Request>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.RequestProcessor.RequestCallback) throws android.hardware.camera2.CameraAccessException; } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final class RequestProcessor.Request { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public RequestProcessor.Request(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>>, int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>> getParameters(); + public static final class RequestProcessor.Request { + ctor public RequestProcessor.Request(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>>, int); + method @NonNull public java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>> getParameters(); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface RequestProcessor.RequestCallback { - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureBufferLost(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureCompleted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable android.hardware.camera2.TotalCaptureResult); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureFailure); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureProgressed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureResult); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceAborted(int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceCompleted(int, long); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureStarted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, long); + public static interface RequestProcessor.RequestCallback { + method public void onCaptureBufferLost(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, int); + method public void onCaptureCompleted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @Nullable android.hardware.camera2.TotalCaptureResult); + method public void onCaptureFailed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureFailure); + method public void onCaptureProgressed(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, @NonNull android.hardware.camera2.CaptureResult); + method public void onCaptureSequenceAborted(int); + method public void onCaptureSequenceCompleted(int, long); + method public void onCaptureStarted(@NonNull android.hardware.camera2.extension.RequestProcessor.Request, long, long); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract class SessionProcessor { - ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public SessionProcessor(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void deInitSession(@NonNull android.os.IBinder); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public abstract android.hardware.camera2.extension.ExtensionConfiguration initSession(@NonNull android.os.IBinder, @NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap, @NonNull android.hardware.camera2.extension.CameraOutputSurface, @NonNull android.hardware.camera2.extension.CameraOutputSurface); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionEnd(); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void onCaptureSessionStart(@NonNull android.hardware.camera2.extension.RequestProcessor, @NonNull String); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void setParameters(@NonNull android.hardware.camera2.CaptureRequest); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startMultiFrameCapture(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startRepeating(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract int startTrigger(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public abstract void stopRepeating(); + public abstract class SessionProcessor { + ctor public SessionProcessor(); + method public abstract void deInitSession(@NonNull android.os.IBinder); + method @NonNull public abstract android.hardware.camera2.extension.ExtensionConfiguration initSession(@NonNull android.os.IBinder, @NonNull String, @NonNull android.hardware.camera2.extension.CharacteristicsMap, @NonNull android.hardware.camera2.extension.CameraOutputSurface, @NonNull android.hardware.camera2.extension.CameraOutputSurface); + method public abstract void onCaptureSessionEnd(); + method public abstract void onCaptureSessionStart(@NonNull android.hardware.camera2.extension.RequestProcessor, @NonNull String); + method public abstract void setParameters(@NonNull android.hardware.camera2.CaptureRequest); + method public abstract int startMultiFrameCapture(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); + method public abstract int startRepeating(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); + method public abstract int startTrigger(@NonNull android.hardware.camera2.CaptureRequest, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.extension.SessionProcessor.CaptureCallback); + method public abstract void stopRepeating(); } - @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface SessionProcessor.CaptureCallback { - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureCompleted(long, int, @NonNull java.util.Map<android.hardware.camera2.CaptureResult.Key,java.lang.Object>); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureFailed(int, int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureProcessStarted(int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceAborted(int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureSequenceCompleted(int); - method @FlaggedApi("com.android.internal.camera.flags.concert_mode") public void onCaptureStarted(int, long); + public static interface SessionProcessor.CaptureCallback { + method public void onCaptureCompleted(long, int, @NonNull java.util.Map<android.hardware.camera2.CaptureResult.Key,java.lang.Object>); + method public void onCaptureFailed(int, int); + method public void onCaptureProcessStarted(int); + method public void onCaptureSequenceAborted(int); + method public void onCaptureSequenceCompleted(int); + method public void onCaptureStarted(int, long); } } diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java index 36daaabad7cb..83b5aa05c383 100644 --- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java +++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java @@ -164,7 +164,13 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { */ @Nullable public Boolean getEnabled() { - return (Boolean) getProperty(PROPERTY_ENABLED); + // We can't use getPropertyBoolean here. getPropertyBoolean returns false instead of null + // if the value is missing. + boolean[] enabled = getPropertyBooleanArray(PROPERTY_ENABLED); + if (enabled == null || enabled.length == 0) { + return null; + } + return enabled[0]; } /** Returns the qualified id linking to the static metadata of the app function. */ @@ -201,11 +207,16 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { /** * Sets an indicator specifying if the function is enabled or not. This would override the * default enabled state in the static metadata ({@link - * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). + * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to + * null to clear the override. */ @NonNull - public Builder setEnabled(boolean enabled) { - setPropertyBoolean(PROPERTY_ENABLED, enabled); + public Builder setEnabled(@Nullable Boolean enabled) { + if (enabled == null) { + setPropertyBoolean(PROPERTY_ENABLED); + } else { + setPropertyBoolean(PROPERTY_ENABLED, enabled); + } return this; } diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 9b87df9ad3a4..3cf508a6db00 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -259,10 +259,8 @@ public final class CameraExtensionCharacteristics { private static final String PROXY_SERVICE_NAME = "com.android.cameraextensions.CameraExtensionsProxyService"; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) private static final int FALLBACK_PACKAGE_NAME = com.android.internal.R.string.config_extensionFallbackPackageName; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) private static final int FALLBACK_SERVICE_NAME = com.android.internal.R.string.config_extensionFallbackServiceName; @@ -309,7 +307,7 @@ public final class CameraExtensionCharacteristics { intent.setClassName(vendorProxyPackage, vendorProxyService); } - if (Flags.concertMode() && useFallback) { + if (useFallback) { String packageName = ctx.getResources().getString(FALLBACK_PACKAGE_NAME); String serviceName = ctx.getResources().getString(FALLBACK_SERVICE_NAME); @@ -433,7 +431,7 @@ public final class CameraExtensionCharacteristics { releaseProxyConnectionLocked(ctx, extension); } - if (Flags.concertMode() && ret && useFallback && mIsFallbackEnabled) { + if (ret && useFallback && mIsFallbackEnabled) { try { InitializeSessionHandler cb = new InitializeSessionHandler(ctx); initializeSession(cb, extension); @@ -462,26 +460,24 @@ public final class CameraExtensionCharacteristics { boolean ret = registerClientHelper(ctx, token, extension, false /*useFallback*/); - if (Flags.concertMode()) { - // Check if user enabled fallback impl - ContentResolver resolver = ctx.getContentResolver(); - int userEnabled = Settings.Secure.getInt(resolver, - Settings.Secure.CAMERA_EXTENSIONS_FALLBACK, 1); - - boolean vendorImpl = true; - if (ret && (mConnectionManager.getProxy(extension) != null) && (userEnabled == 1)) { - // At this point, we are connected to either CameraExtensionsProxyService or - // the vendor extension proxy service. If the vendor does not support the - // extension, unregisterClient and re-register client with the proxy service - // containing the fallback impl - vendorImpl = isExtensionSupported(cameraId, extension, - characteristicsMapNative); - } + // Check if user enabled fallback impl + ContentResolver resolver = ctx.getContentResolver(); + int userEnabled = Settings.Secure.getInt(resolver, + Settings.Secure.CAMERA_EXTENSIONS_FALLBACK, 1); - if (!vendorImpl) { - unregisterClient(ctx, token, extension); - ret = registerClientHelper(ctx, token, extension, true /*useFallback*/); - } + boolean vendorImpl = true; + if (ret && (mConnectionManager.getProxy(extension) != null) && (userEnabled == 1)) { + // At this point, we are connected to either CameraExtensionsProxyService or + // the vendor extension proxy service. If the vendor does not support the + // extension, unregisterClient and re-register client with the proxy service + // containing the fallback impl + vendorImpl = isExtensionSupported(cameraId, extension, + characteristicsMapNative); + } + + if (!vendorImpl) { + unregisterClient(ctx, token, extension); + ret = registerClientHelper(ctx, token, extension, true /*useFallback*/); } return ret; diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java index 2d9433e31ab2..20f89a55dd3b 100644 --- a/core/java/android/hardware/camera2/CameraExtensionSession.java +++ b/core/java/android/hardware/camera2/CameraExtensionSession.java @@ -156,7 +156,6 @@ public abstract class CameraExtensionSession implements AutoCloseable { * @see #capture * @see #setRepeatingRequest */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public void onCaptureFailed(@NonNull CameraExtensionSession session, @NonNull CaptureRequest request, @CaptureFailure.FailureReason int failure) { // default empty implementation diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 75d617c89522..a18a634918f9 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -5293,7 +5293,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { @PublicKey @NonNull @SyntheticKey - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public static final Key<android.hardware.camera2.params.LensIntrinsicsSample[]> STATISTICS_LENS_INTRINSICS_SAMPLES = new Key<android.hardware.camera2.params.LensIntrinsicsSample[]>("android.statistics.lensIntrinsicsSamples", android.hardware.camera2.params.LensIntrinsicsSample[].class); @@ -5307,7 +5306,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CaptureResult#SENSOR_TIMESTAMP * @hide */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public static final Key<long[]> STATISTICS_LENS_INTRINSIC_TIMESTAMPS = new Key<long[]>("android.statistics.lensIntrinsicTimestamps", long[].class); @@ -5323,7 +5321,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE * @hide */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public static final Key<float[]> STATISTICS_LENS_INTRINSIC_SAMPLES = new Key<float[]>("android.statistics.lensIntrinsicSamples", float[].class); @@ -5814,7 +5811,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { */ @PublicKey @NonNull - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public static final Key<android.graphics.Rect> LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_SENSOR_CROP_REGION = new Key<android.graphics.Rect>("android.logicalMultiCamera.activePhysicalSensorCropRegion", android.graphics.Rect.class); diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java index 8fa09a802aa4..df66f590148f 100644 --- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java +++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java @@ -53,7 +53,6 @@ import java.util.Map; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract class AdvancedExtender { private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>(); private final CameraManager mCameraManager; @@ -66,7 +65,6 @@ public abstract class AdvancedExtender { * * @param cameraManager the system camera manager */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public AdvancedExtender(@NonNull CameraManager cameraManager) { mCameraManager = cameraManager; try { @@ -101,7 +99,6 @@ public abstract class AdvancedExtender { * @param cameraId The camera2 id string of the camera. * @return the camera metadata vendor Id associated with the given camera */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public long getMetadataVendorId(@NonNull String cameraId) { long vendorId = mMetadataVendorIdMap.containsKey(cameraId) ? mMetadataVendorIdMap.get(cameraId) : Long.MAX_VALUE; @@ -123,7 +120,6 @@ public abstract class AdvancedExtender { * CameraCharacteristics. * @return true if the extension is supported, otherwise false */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract boolean isExtensionAvailable(@NonNull String cameraId, @NonNull CharacteristicsMap charsMap); @@ -144,7 +140,6 @@ public abstract class AdvancedExtender { * physical camera ids and their * CameraCharacteristics. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void initialize(@NonNull String cameraId, @NonNull CharacteristicsMap map); /** @@ -159,7 +154,6 @@ public abstract class AdvancedExtender { * be identical to the supported preview output format returned here. * @param cameraId The camera2 id string of the camera. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract Map<Integer, List<Size>> getSupportedPreviewOutputResolutions( @NonNull String cameraId); @@ -179,7 +173,6 @@ public abstract class AdvancedExtender { * writes the output to the output surface. * @param cameraId The camera2 id string of the camera. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract Map<Integer, List<Size>> getSupportedCaptureOutputResolutions( @NonNull String cameraId); @@ -189,7 +182,6 @@ public abstract class AdvancedExtender { * implements all the interactions required for starting an extension * and cleanup. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract SessionProcessor getSessionProcessor(); @@ -227,7 +219,6 @@ public abstract class AdvancedExtender { * @return The list of supported orthogonal capture keys, or empty * list if no capture settings are not supported. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract List<CaptureRequest.Key> getAvailableCaptureRequestKeys( @NonNull String cameraId); @@ -245,7 +236,6 @@ public abstract class AdvancedExtender { * @return The list of supported capture result keys, or * empty list if capture results are not supported. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract List<CaptureResult.Key> getAvailableCaptureResultKeys( @NonNull String cameraId); @@ -270,7 +260,6 @@ public abstract class AdvancedExtender { * {@link CameraCharacteristics#REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES_MAP} and * {@link CameraCharacteristics#REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP}. */ - @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET) @NonNull public abstract List<Pair<CameraCharacteristics.Key, Object>> getAvailableCharacteristicsKeyValues(); @@ -377,7 +366,6 @@ public abstract class AdvancedExtender { return false; } - @FlaggedApi(Flags.FLAG_CAMERA_EXTENSIONS_CHARACTERISTICS_GET) @Override public CameraMetadataNative getAvailableCharacteristicsKeyValues(String cameraId) { List<Pair<CameraCharacteristics.Key, Object>> entries = diff --git a/core/java/android/hardware/camera2/extension/CameraExtensionService.java b/core/java/android/hardware/camera2/extension/CameraExtensionService.java index 01698d54150c..9cc4f16fa627 100644 --- a/core/java/android/hardware/camera2/extension/CameraExtensionService.java +++ b/core/java/android/hardware/camera2/extension/CameraExtensionService.java @@ -42,7 +42,6 @@ interface CameraUsageTracker { * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract class CameraExtensionService extends Service { private static final String TAG = "CameraExtensionService"; private CameraUsageTracker mCameraUsageTracker; @@ -87,10 +86,8 @@ public abstract class CameraExtensionService extends Service { } }; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) protected CameraExtensionService() { } - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @Override @NonNull public final IBinder onBind(@Nullable Intent intent) { @@ -186,7 +183,6 @@ public abstract class CameraExtensionService extends Service { * unexpectedly. * @return true if the registration is successful, false otherwise */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract boolean onRegisterClient(@NonNull IBinder token); /** @@ -194,7 +190,6 @@ public abstract class CameraExtensionService extends Service { * * @param token Binder token */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void onUnregisterClient(@NonNull IBinder token); /** @@ -204,7 +199,6 @@ public abstract class CameraExtensionService extends Service { * extension type * @return Valid advanced extender of the requested type */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract AdvancedExtender onInitializeAdvancedExtension(@Extension int extensionType); } diff --git a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java index 32139b8e314b..e8b20e37f40c 100644 --- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java +++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java @@ -42,11 +42,9 @@ import com.android.internal.camera.flags.Flags; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public final class CameraOutputSurface { private final OutputSurface mOutputSurface; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) CameraOutputSurface(@NonNull OutputSurface surface) { mOutputSurface = surface; } @@ -59,7 +57,6 @@ public final class CameraOutputSurface { * @param size Requested size of the camera * output */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public CameraOutputSurface(@NonNull Surface surface, @NonNull Size size) { mOutputSurface = new OutputSurface(); @@ -75,7 +72,6 @@ public final class CameraOutputSurface { /** * Return the current output {@link Surface} */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public Surface getSurface() { return mOutputSurface.surface; @@ -84,7 +80,6 @@ public final class CameraOutputSurface { /** * Return the current requested output size */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public android.util.Size getSize() { if (mOutputSurface.size != null) { @@ -96,7 +91,6 @@ public final class CameraOutputSurface { /** * Return the current surface output {@link android.graphics.ImageFormat} */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public @ImageFormat.Format int getImageFormat() { return mOutputSurface.imageFormat; } diff --git a/core/java/android/hardware/camera2/extension/CharacteristicsMap.java b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java index 495abc8100ae..e578f6ef9064 100644 --- a/core/java/android/hardware/camera2/extension/CharacteristicsMap.java +++ b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java @@ -36,7 +36,6 @@ import java.util.Set; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public class CharacteristicsMap { private final HashMap<String, CameraCharacteristics> mCharMap; @@ -46,7 +45,6 @@ public class CharacteristicsMap { * @param charsMap Maps camera ids to respective * {@link CameraCharacteristics} */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) CharacteristicsMap(@NonNull Map<String, CameraMetadataNative> charsMap) { mCharMap = new HashMap<>(); for (Map.Entry<String, CameraMetadataNative> entry : charsMap.entrySet()) { @@ -59,7 +57,6 @@ public class CharacteristicsMap { * * @return Set of the camera ids stored in the map */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public Set<String> getCameraIds() { return mCharMap.keySet(); @@ -74,7 +71,6 @@ public class CharacteristicsMap { * @return Valid {@link CameraCharacteristics} instance of null * in case the camera id is not part of the map */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @Nullable public CameraCharacteristics get(@NonNull String cameraId) { return mCharMap.get(cameraId); diff --git a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java index 32de1ce8f0d6..43dfaa58554f 100644 --- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java +++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java @@ -43,7 +43,6 @@ import java.util.List; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public class ExtensionConfiguration { private final int mSessionType; private final int mSessionTemplateId; @@ -65,7 +64,6 @@ public class ExtensionConfiguration { * @param sessionParams An optional set of camera capture * session parameter values */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public ExtensionConfiguration(@CameraDevice.SessionOperatingMode int sessionType, @CameraDevice.RequestTemplate int sessionTemplateId, @NonNull List<ExtensionOutputConfiguration> outputs, @@ -87,7 +85,6 @@ public class ExtensionConfiguration { mColorSpace = colorSpace; } - @FlaggedApi(Flags.FLAG_CONCERT_MODE) CameraSessionConfig getCameraSessionConfig() { if (mOutputs.isEmpty()) { return null; diff --git a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java index 8a47430e7eb4..ff0d8d7b53a5 100644 --- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java +++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java @@ -35,7 +35,6 @@ import java.util.List; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public class ExtensionOutputConfiguration { private final List<CameraOutputSurface> mSurfaces; private final String mPhysicalCameraId; @@ -57,7 +56,6 @@ public class ExtensionOutputConfiguration { * @param surfaceGroupId In case of surface group, this field must * contain the surface group id */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public ExtensionOutputConfiguration(@NonNull List<CameraOutputSurface> outputs, int outputConfigId, @Nullable String physicalCameraId, int surfaceGroupId) { mSurfaces = outputs; diff --git a/core/java/android/hardware/camera2/extension/RequestProcessor.java b/core/java/android/hardware/camera2/extension/RequestProcessor.java index 0ad27c212d67..936d57baadd3 100644 --- a/core/java/android/hardware/camera2/extension/RequestProcessor.java +++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java @@ -45,19 +45,16 @@ import java.util.concurrent.Executor; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public final class RequestProcessor { private final static String TAG = "RequestProcessor"; private final IRequestProcessorImpl mRequestProcessor; private final long mVendorId; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) RequestProcessor (@NonNull IRequestProcessorImpl requestProcessor, long vendorId) { mRequestProcessor = requestProcessor; mVendorId = vendorId; } - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public interface RequestCallback { /** * This method is called when the camera device has started @@ -76,7 +73,6 @@ public final class RequestProcessor { * @param frameNumber the frame number for this capture * */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureStarted(@NonNull Request request, long frameNumber, long timestamp); /** @@ -117,7 +113,6 @@ public final class RequestProcessor { * which includes a subset of the {@link * TotalCaptureResult} fields. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureProgressed(@NonNull Request request, @NonNull CaptureResult partialResult); /** @@ -138,7 +133,6 @@ public final class RequestProcessor { * parameters and the state of the camera * system during capture. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureCompleted(@NonNull Request request, @Nullable TotalCaptureResult totalCaptureResult); @@ -163,7 +157,6 @@ public final class RequestProcessor { * @param failure The output failure from the capture, including the * failure reason and the frame number. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureFailed(@NonNull Request request, @NonNull CaptureFailure failure); /** @@ -182,7 +175,6 @@ public final class RequestProcessor { * @param outputStreamId The output stream id that the buffer will not * be produced for */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureBufferLost(@NonNull Request request, long frameNumber, int outputStreamId); /** @@ -203,7 +195,6 @@ public final class RequestProcessor { * or {@link CaptureFailure#getFrameNumber}) in * the capture sequence. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureSequenceCompleted(int sequenceId, long frameNumber); /** @@ -221,17 +212,14 @@ public final class RequestProcessor { * @param sequenceId A sequence ID returned by the RequestProcessor * capture family of methods */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureSequenceAborted(int sequenceId); } - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public final static class Request { private final List<Integer> mOutputIds; private final List<Pair<CaptureRequest.Key, Object>> mParameters; private final @CameraDevice.RequestTemplate int mTemplateId; - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public Request(@NonNull List<Integer> outputConfigIds, @NonNull List<Pair<CaptureRequest.Key, Object>> parameters, @CameraDevice.RequestTemplate int templateId) { @@ -244,7 +232,6 @@ public final class RequestProcessor { * Gets the target ids of {@link ExtensionOutputConfiguration} which identifies * corresponding Surface to be the targeted for the request. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull List<Integer> getOutputConfigIds() { return mOutputIds; @@ -253,7 +240,6 @@ public final class RequestProcessor { /** * Gets all the parameters. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public List<Pair<CaptureRequest.Key, Object>> getParameters() { return mParameters; @@ -265,7 +251,6 @@ public final class RequestProcessor { * * @see CameraDevice.RequestTemplate */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) Integer getTemplateId() { return mTemplateId; } @@ -322,7 +307,6 @@ public final class RequestProcessor { * @param callback Request callback implementation * @return the id of the capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public int submit(@NonNull Request request, @NonNull Executor executor, @NonNull RequestCallback callback) throws CameraAccessException { ArrayList<Request> requests = new ArrayList<>(1); @@ -355,7 +339,6 @@ public final class RequestProcessor { * @param callback Request callback implementation * @return the id of the capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public int submitBurst(@NonNull List<Request> requests, @NonNull Executor executor, @NonNull RequestCallback callback) throws CameraAccessException { List<android.hardware.camera2.extension.Request> parcelableRequests = @@ -386,7 +369,6 @@ public final class RequestProcessor { * @param callback Request callback implementation * @return the id of the capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public int setRepeating(@NonNull Request request, @NonNull Executor executor, @NonNull RequestCallback callback) throws CameraAccessException { ArrayList<Request> requests = new ArrayList<>(1); @@ -413,7 +395,6 @@ public final class RequestProcessor { /** * Abort all ongoing capture requests. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public void abortCaptures() { try { mRequestProcessor.abortCaptures(); @@ -425,7 +406,6 @@ public final class RequestProcessor { /** * Stop the current repeating request. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public void stopRepeating() { try { mRequestProcessor.stopRepeating(); diff --git a/core/java/android/hardware/camera2/extension/SessionProcessor.java b/core/java/android/hardware/camera2/extension/SessionProcessor.java index eead4ef15ae8..cf9f30d89762 100644 --- a/core/java/android/hardware/camera2/extension/SessionProcessor.java +++ b/core/java/android/hardware/camera2/extension/SessionProcessor.java @@ -76,7 +76,6 @@ import java.util.concurrent.Executor; * @hide */ @SystemApi -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract class SessionProcessor { private static final String TAG = "SessionProcessor"; private CameraUsageTracker mCameraUsageTracker; @@ -84,7 +83,6 @@ public abstract class SessionProcessor { /** * Initialize a session process instance */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public SessionProcessor() {} void setCameraUsageTracker(CameraUsageTracker tracker) { @@ -97,7 +95,6 @@ public abstract class SessionProcessor { * @hide */ @SystemApi - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public interface CaptureCallback { /** * This method is called when the camera device has started @@ -116,7 +113,6 @@ public abstract class SessionProcessor { * first frame in a multi-frame capture, * in nanoseconds. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureStarted(int captureSequenceId, long timestamp); /** @@ -126,7 +122,6 @@ public abstract class SessionProcessor { * * @param captureSequenceId id of the current capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureProcessStarted(int captureSequenceId); /** @@ -139,7 +134,6 @@ public abstract class SessionProcessor { * @param captureSequenceId id of the current capture sequence * @param failure The capture failure reason */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureFailed(int captureSequenceId, @CaptureFailure.FailureReason int failure); /** @@ -154,7 +148,6 @@ public abstract class SessionProcessor { * * @param captureSequenceId id of the current capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureSequenceCompleted(int captureSequenceId); /** @@ -162,7 +155,6 @@ public abstract class SessionProcessor { * * @param captureSequenceId id of the current capture sequence */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureSequenceAborted(int captureSequenceId); /** @@ -192,7 +184,6 @@ public abstract class SessionProcessor { * settings and results are always supported and * applied by the corresponding framework. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) void onCaptureCompleted(long shutterTimestamp, int requestId, @NonNull Map<CaptureResult.Key, Object> results); } @@ -231,7 +222,6 @@ public abstract class SessionProcessor { * ensure this list will always produce a valid camera capture * session. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public abstract ExtensionConfiguration initSession(@NonNull IBinder token, @NonNull String cameraId, @NonNull CharacteristicsMap map, @@ -247,7 +237,6 @@ public abstract class SessionProcessor { * @param token Binder token that can be used to unlink any previously * linked death notifier callbacks */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void deInitSession(@NonNull IBinder token); /** @@ -268,7 +257,6 @@ public abstract class SessionProcessor { * * */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void onCaptureSessionStart(@NonNull RequestProcessor requestProcessor, @NonNull String statsKey); @@ -279,7 +267,6 @@ public abstract class SessionProcessor { * will no longer accept any requests after onCaptureSessionEnd() * returns. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void onCaptureSessionEnd(); /** @@ -293,7 +280,6 @@ public abstract class SessionProcessor { * @param callback a callback to report the status. * @return the id of the capture sequence. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract int startRepeating(@NonNull Executor executor, @NonNull CaptureCallback callback); @@ -305,7 +291,6 @@ public abstract class SessionProcessor { * forward calling {@link RequestProcessor#setRepeating} will simply * do nothing. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void stopRepeating(); /** @@ -326,7 +311,6 @@ public abstract class SessionProcessor { * @param callback a callback to report the status. * @return the id of the capture sequence. */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract int startMultiFrameCapture(@NonNull Executor executor, @NonNull CaptureCallback callback); @@ -338,7 +322,6 @@ public abstract class SessionProcessor { * the value. *@param captureRequest Request that includes all client parameter */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract void setParameters(@NonNull CaptureRequest captureRequest); /** @@ -357,7 +340,6 @@ public abstract class SessionProcessor { * @return the id of the capture sequence. * */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public abstract int startTrigger(@NonNull CaptureRequest captureRequest, @NonNull Executor executor, @NonNull CaptureCallback callback); diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 323712d817af..fc03b517e6d4 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -873,17 +873,15 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @Override public void onCaptureProcessFailed(int captureSequenceId, int captureFailureReason) { - if (Flags.concertMode()) { - final long ident = Binder.clearCallingIdentity(); - try { - mClientExecutor.execute( - () -> mClientCallbacks.onCaptureFailed( - CameraAdvancedExtensionSessionImpl.this, mClientRequest, - captureFailureReason - )); - } finally { - Binder.restoreCallingIdentity(ident); - } + final long ident = Binder.clearCallingIdentity(); + try { + mClientExecutor.execute( + () -> mClientCallbacks.onCaptureFailed( + CameraAdvancedExtensionSessionImpl.this, mClientRequest, + captureFailureReason + )); + } finally { + Binder.restoreCallingIdentity(ident); } } diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index 0cd1c8ca89fe..ef7f3f8ab58b 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -1797,57 +1797,49 @@ public class CameraMetadataNative implements Parcelable { return false; } - if (Flags.concertMode()) { - long[] tsArray = new long[samples.length]; - float[] intrinsicsArray = new float[samples.length * 5]; - for (int i = 0; i < samples.length; i++) { - tsArray[i] = samples[i].getTimestampNanos(); - System.arraycopy(samples[i].getLensIntrinsics(), 0, intrinsicsArray, 5 * i, 5); + long[] tsArray = new long[samples.length]; + float[] intrinsicsArray = new float[samples.length * 5]; + for (int i = 0; i < samples.length; i++) { + tsArray[i] = samples[i].getTimestampNanos(); + System.arraycopy(samples[i].getLensIntrinsics(), 0, intrinsicsArray, 5 * i, 5); - } - setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES, intrinsicsArray); - setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS, tsArray); - - return true; - } else { - return false; } + setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES, intrinsicsArray); + setBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS, tsArray); + + return true; } private LensIntrinsicsSample[] getLensIntrinsicSamples() { - if (Flags.concertMode()) { - long[] timestamps = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS); - float[] intrinsics = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES); + long[] timestamps = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_TIMESTAMPS); + float[] intrinsics = getBase(CaptureResult.STATISTICS_LENS_INTRINSIC_SAMPLES); - if (timestamps == null) { - if (intrinsics != null) { - throw new AssertionError("timestamps is null but intrinsics is not"); - } - - return null; + if (timestamps == null) { + if (intrinsics != null) { + throw new AssertionError("timestamps is null but intrinsics is not"); } - if (intrinsics == null) { - throw new AssertionError("timestamps is not null but intrinsics is"); - } else if ((intrinsics.length % 5) != 0) { - throw new AssertionError("intrinsics are not multiple of 5"); - } + return null; + } - if ((intrinsics.length / 5) != timestamps.length) { - throw new AssertionError(String.format( - "timestamps has %d entries but intrinsics has %d", timestamps.length, - intrinsics.length / 5)); - } + if (intrinsics == null) { + throw new AssertionError("timestamps is not null but intrinsics is"); + } else if ((intrinsics.length % 5) != 0) { + throw new AssertionError("intrinsics are not multiple of 5"); + } - LensIntrinsicsSample[] samples = new LensIntrinsicsSample[timestamps.length]; - for (int i = 0; i < timestamps.length; i++) { - float[] currentIntrinsic = Arrays.copyOfRange(intrinsics, 5 * i, 5 * i + 5); - samples[i] = new LensIntrinsicsSample(timestamps[i], currentIntrinsic); - } - return samples; - } else { - return null; + if ((intrinsics.length / 5) != timestamps.length) { + throw new AssertionError(String.format( + "timestamps has %d entries but intrinsics has %d", timestamps.length, + intrinsics.length / 5)); } + + LensIntrinsicsSample[] samples = new LensIntrinsicsSample[timestamps.length]; + for (int i = 0; i < timestamps.length; i++) { + float[] currentIntrinsic = Arrays.copyOfRange(intrinsics, 5 * i, 5 * i + 5); + samples[i] = new LensIntrinsicsSample(timestamps[i], currentIntrinsic); + } + return samples; } private Capability[] getExtendedSceneModeCapabilities() { diff --git a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java index 9a4ec5c94b5b..9157ef109356 100644 --- a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java +++ b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java @@ -31,7 +31,6 @@ import java.util.Arrays; * Immutable class to store an * {@link CaptureResult#STATISTICS_LENS_INTRINSICS_SAMPLES lens intrinsics intra-frame sample}. */ -@FlaggedApi(Flags.FLAG_CONCERT_MODE) public final class LensIntrinsicsSample { /** * Create a new {@link LensIntrinsicsSample}. @@ -46,7 +45,6 @@ public final class LensIntrinsicsSample { * * @throws IllegalArgumentException if lensIntrinsics length is different from 5 */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public LensIntrinsicsSample(final long timestampNs, @NonNull final float[] lensIntrinsics) { mTimestampNs = timestampNs; Preconditions.checkArgument(lensIntrinsics.length == 5); @@ -61,7 +59,6 @@ public final class LensIntrinsicsSample { * * @return a long value (guaranteed to be finite) */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) public long getTimestampNanos() { return mTimestampNs; } @@ -72,7 +69,6 @@ public final class LensIntrinsicsSample { * @return a floating point value (guaranteed to be finite) * @see CaptureResult#LENS_INTRINSIC_CALIBRATION */ - @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull public float[] getLensIntrinsics() { return mLensIntrinsics; diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java index 0cabc4c629fb..bdbec5596ade 100644 --- a/core/java/android/hardware/input/KeyGestureEvent.java +++ b/core/java/android/hardware/input/KeyGestureEvent.java @@ -22,8 +22,6 @@ import android.annotation.Nullable; import android.view.Display; import android.view.KeyCharacterMap; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.AnnotationValidations; import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; @@ -171,6 +169,14 @@ public final class KeyGestureEvent { } /** + * Tests whether this keyboard shortcut event has the given modifiers (i.e. all of the given + * modifiers were pressed when this shortcut was triggered). + */ + public boolean hasModifiers(int modifiers) { + return (getModifierState() & modifiers) == modifiers; + } + + /** * Key gesture event builder used to create a KeyGestureEvent for tests in Java. * * @hide diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 00ba3bf27c65..18f9b2b9d74f 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -139,7 +139,7 @@ interface IUserManager { boolean isUserForeground(int userId); boolean isUserVisible(int userId); int[] getVisibleUsers(); - int getMainDisplayIdAssignedToUser(); + int getMainDisplayIdAssignedToUser(int userId); boolean isForegroundUserAdmin(); boolean isUserNameSet(int userId); boolean hasRestrictedProfiles(int userId); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 1ca4574e79b4..461f1e00c415 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -3706,9 +3706,13 @@ public class UserManager { * @hide */ @TestApi + @UserHandleAware( + requiresAnyOfPermissionsIfNotCaller = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}) public int getMainDisplayIdAssignedToUser() { try { - return mService.getMainDisplayIdAssignedToUser(); + return mService.getMainDisplayIdAssignedToUser(mUserId); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 384add5cf929..2ab16e91d987 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -2397,7 +2397,11 @@ public abstract class WallpaperService extends Service { // it hasn't changed and there is no need to update. ret = mBlastBufferQueue.createSurface(); } else { - mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format); + if (mBbqSurfaceControl != null && mBbqSurfaceControl.isValid()) { + mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format); + } else { + Log.w(TAG, "Skipping BlastBufferQueue update - invalid surface control"); + } } return ret; diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 15a4715bd059..5c415165137e 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -146,7 +146,7 @@ public class InsetsState implements Parcelable { forceConsumingTypes |= type; } - if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled() + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue() && (flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) { forceConsumingOpaqueCaptionBar = true; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 33e79059c7e5..d46e1f29597e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3214,10 +3214,10 @@ public final class ViewRootImpl implements ViewParent, typesToShow |= Type.navigationBars(); } if (captionIsHiddenByFlags && !captionWasHiddenByFlags - && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { + && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) { typesToHide |= Type.captionBar(); } else if (!captionIsHiddenByFlags && captionWasHiddenByFlags - && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { + && ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) { typesToShow |= Type.captionBar(); } if (typesToHide != 0) { diff --git a/core/java/android/window/flags/DesktopModeFlags.java b/core/java/android/window/flags/DesktopModeFlags.java index 701b6be06e72..944a106bf441 100644 --- a/core/java/android/window/flags/DesktopModeFlags.java +++ b/core/java/android/window/flags/DesktopModeFlags.java @@ -43,11 +43,28 @@ public enum DesktopModeFlags { // All desktop mode related flags to be overridden by developer option toggle will be added here ENABLE_DESKTOP_WINDOWING_MODE( Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true), - ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false), + ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true), ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION( Flags::enableCaptionCompatInsetForceConsumption, true), ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS( - Flags::enableCaptionCompatInsetForceConsumptionAlways, true); + Flags::enableCaptionCompatInsetForceConsumptionAlways, true), + ENABLE_CASCADING_WINDOWS(Flags::enableCascadingWindows, true), + ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY( + Flags::enableDesktopWindowingWallpaperActivity, true), + ENABLE_DESKTOP_WINDOWING_MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true), + ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true), + ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true), + ENABLE_APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true), + ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true), + ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true), + DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true), + ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true), + ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true), + ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true), + ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true), + ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS( + Flags::enableDesktopWindowingTaskbarRunningApps, true), + ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false); private static final String TAG = "DesktopModeFlagsUtil"; // Function called to obtain aconfig flag value. @@ -68,7 +85,7 @@ public enum DesktopModeFlags { * Determines state of flag based on the actual flag and desktop mode developer option * overrides. */ - public boolean isEnabled() { + public boolean isTrue() { Application application = ActivityThread.currentApplication(); if (!Flags.showDesktopWindowingDevOption() || !mShouldOverrideByDevOption @@ -112,12 +129,13 @@ public enum DesktopModeFlags { } /** Override state of desktop mode developer option toggle. */ - private enum ToggleOverride { + public enum ToggleOverride { OVERRIDE_UNSET, OVERRIDE_OFF, OVERRIDE_ON; - int getSetting() { + /** Returns the integer representation of this {@code ToggleOverride}. */ + public int getSetting() { return switch (this) { case OVERRIDE_ON -> 1; case OVERRIDE_OFF -> 0; @@ -125,7 +143,8 @@ public enum DesktopModeFlags { }; } - static ToggleOverride fromSetting(int setting, @Nullable ToggleOverride fallback) { + /** Returns the {@code ToggleOverride} corresponding to a given integer setting. */ + public static ToggleOverride fromSetting(int setting, @Nullable ToggleOverride fallback) { return switch (setting) { case 1 -> OVERRIDE_ON; case 0 -> OVERRIDE_OFF; diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index e1402f8224eb..b6aad1145880 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -266,3 +266,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "remove_starting_window_wait_for_multi_transitions" + namespace: "windowing_frontend" + description: "Avoid remove starting window too early when playing multiple transitions" + bug: "362347290" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 84dfc497dc84..6faea17f24b2 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1219,14 +1219,14 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final boolean hideCaptionBar = fullscreen || (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0; final boolean consumingCaptionBar = - ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled() + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue() && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0 && hideCaptionBar); final boolean isOpaqueCaptionBar = customizableWindowHeaders() && (appearance & APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) == 0; final boolean consumingOpaqueCaptionBar = - ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled() + ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue() && mLastForceConsumingOpaqueCaptionBar && isOpaqueCaptionBar; diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index fbc058cc0330..b0e38e256430 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -122,18 +122,20 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto private final Lock mBackgroundServiceLock = new ReentrantLock(); private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor(); - public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) { + public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) + throws ServiceManager.ServiceNotFoundException { this(null, null, null, () -> {}, groups); } - public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) { + public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) + throws ServiceManager.ServiceNotFoundException { this(null, null, null, cacheUpdater, groups); } public PerfettoProtoLogImpl( @NonNull String viewerConfigFilePath, @NonNull Runnable cacheUpdater, - @NonNull IProtoLogGroup[] groups) { + @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException { this(viewerConfigFilePath, null, new ProtoLogViewerConfigReader(() -> { @@ -177,12 +179,14 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, @Nullable ProtoLogViewerConfigReader viewerConfigReader, @NonNull Runnable cacheUpdater, - @NonNull IProtoLogGroup[] groups) { + @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException { this(viewerConfigFilePath, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater, groups, ProtoLogDataSource::new, - IProtoLogConfigurationService.Stub - .asInterface(ServiceManager.getService(PROTOLOG_CONFIGURATION_SERVICE)) + android.tracing.Flags.clientSideProtoLogging() ? + IProtoLogConfigurationService.Stub.asInterface( + ServiceManager.getServiceOrThrow(PROTOLOG_CONFIGURATION_SERVICE) + ) : null ); } @@ -222,7 +226,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto if (android.tracing.Flags.clientSideProtoLogging()) { mProtoLogConfigurationService = configurationService; Objects.requireNonNull(mProtoLogConfigurationService, - "ServiceManager returned a null ProtoLog Configuration Service"); + "A null ProtoLog Configuration Service was provided!"); try { var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs(); diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java index bf77db7b6a33..adf03fe5f775 100644 --- a/core/java/com/android/internal/protolog/ProtoLog.java +++ b/core/java/com/android/internal/protolog/ProtoLog.java @@ -16,6 +16,8 @@ package com.android.internal.protolog; +import android.os.ServiceManager; + import com.android.internal.protolog.common.IProtoLog; import com.android.internal.protolog.common.IProtoLogGroup; import com.android.internal.protolog.common.LogLevel; @@ -76,7 +78,11 @@ public class ProtoLog { groups = allGroups.toArray(new IProtoLogGroup[0]); } - sProtoLogInstance = new PerfettoProtoLogImpl(groups); + try { + sProtoLogInstance = new PerfettoProtoLogImpl(groups); + } catch (ServiceManager.ServiceNotFoundException e) { + throw new RuntimeException(e); + } } } else { sProtoLogInstance = new LogcatOnlyProtoLogImpl(); diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java index 7bdcf2d14b19..5d67534b1b44 100644 --- a/core/java/com/android/internal/protolog/ProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java @@ -23,6 +23,7 @@ import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LO import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH; import android.annotation.Nullable; +import android.os.ServiceManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -106,18 +107,23 @@ public class ProtoLogImpl { final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]); if (android.tracing.Flags.perfettoProtologTracing()) { - File f = new File(sViewerConfigPath); - if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) { - // TODO(b/353530422): Remove - temporary fix to unblock b/352290057 - // In some tests the viewer config file might not exist in which we don't - // want to provide config path to the user - Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up " - + ProtoLogImpl.class.getSimpleName() + ". " - + "Setting up without a viewer config instead..."); - sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups); - } else { - sServiceInstance = - new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups); + try { + File f = new File(sViewerConfigPath); + if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) { + // TODO(b/353530422): Remove - temporary fix to unblock b/352290057 + // In some tests the viewer config file might not exist in which we don't + // want to provide config path to the user + Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up " + + ProtoLogImpl.class.getSimpleName() + ". " + + "Setting up without a viewer config instead..."); + + sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups); + } else { + sServiceInstance = + new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups); + } + } catch (ServiceManager.ServiceNotFoundException e) { + throw new RuntimeException(e); } } else { var protologImpl = new LegacyProtoLogImpl( diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto index bb654f05b02d..d8365631c270 100644 --- a/core/proto/android/service/graphicsstats.proto +++ b/core/proto/android/service/graphicsstats.proto @@ -62,6 +62,9 @@ message GraphicsStatsProto { // HWUI renders pipeline type: GL or Vulkan optional PipelineType pipeline = 8; + + // The UID of the app + optional int32 uid = 9; } message GraphicsStatsJankSummaryProto { diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 421b7d2c041d..fe3d4f6b39bf 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -7136,4 +7136,8 @@ <!-- Whether to enable scaling and fading animation to scrollviews while scrolling. P.S this is a change only intended for wear devices. --> <bool name="config_enableViewGroupScalingFading">false</bool> + <!-- Action for opening identity check settings page [CHAR LIMIT=NONE] [DO NOT TRANSLATE] --> + <string name="identity_check_settings_action"></string> + <!-- Package for opening identity check settings page [CHAR LIMIT=NONE] [DO NOT TRANSLATE] --> + <string name="identity_check_settings_package_name">com\u002eandroid\u002esettings</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 039665982482..06b36b8f74af 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5611,4 +5611,8 @@ <java-symbol type="string" name="fingerprint_loe_notification_msg" /> <java-symbol type="bool" name="config_enableViewGroupScalingFading"/> + + <!-- Identity check strings --> + <java-symbol type="string" name="identity_check_settings_action" /> + <java-symbol type="string" name="identity_check_settings_package_name" /> </resources> diff --git a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java index dd406955785b..a311d0d81010 100644 --- a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java +++ b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java @@ -17,9 +17,13 @@ package android.window.flags; import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE; +import static android.window.flags.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF; +import static android.window.flags.DesktopModeFlags.ToggleOverride.OVERRIDE_ON; +import static android.window.flags.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET; +import static android.window.flags.DesktopModeFlags.ToggleOverride.fromSetting; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE; -import static com.android.window.flags.Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS; +import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS; import static com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION; import static com.google.common.truth.Truth.assertThat; @@ -72,267 +76,287 @@ public class DesktopModeFlagsTest { @Test @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() { + public void isTrue_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() { setOverride(OVERRIDE_OFF_SETTING); // In absence of dev options, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); } @Test @DisableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() { + public void isTrue_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() { setOverride(OVERRIDE_ON_SETTING); - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_overrideUnset_featureFlagOn_returnsTrue() { + public void isTrue_overrideUnset_featureFlagOn_returnsTrue() { setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_overrideUnset_featureFlagOff_returnsFalse() { + public void isTrue_overrideUnset_featureFlagOff_returnsFalse() { setOverride(OVERRIDE_UNSET_SETTING); // For overridableFlag, for unset overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_noOverride_featureFlagOn_returnsTrue() { + public void isTrue_noOverride_featureFlagOn_returnsTrue() { setOverride(null); // For overridableFlag, in absence of overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_noOverride_featureFlagOff_returnsFalse() { + public void isTrue_noOverride_featureFlagOff_returnsFalse() { setOverride(null); // For overridableFlag, in absence of overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_unrecognizableOverride_featureFlagOn_returnsTrue() { + public void isTrue_unrecognizableOverride_featureFlagOn_returnsTrue() { setOverride(-2); // For overridableFlag, for unrecognized overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_unrecognizableOverride_featureFlagOff_returnsFalse() { + public void isTrue_unrecognizableOverride_featureFlagOff_returnsFalse() { setOverride(-2); // For overridableFlag, for unrecognizable overrides, follow flag - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_overrideOff_featureFlagOn_returnsFalse() { + public void isTrue_overrideOff_featureFlagOn_returnsFalse() { setOverride(OVERRIDE_OFF_SETTING); // For overridableFlag, follow override if they exist - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_overrideOn_featureFlagOff_returnsTrue() { + public void isTrue_overrideOn_featureFlagOff_returnsTrue() { setOverride(OVERRIDE_ON_SETTING); // For overridableFlag, follow override if they exist - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - public void isEnabled_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() { + public void isTrue_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() { setOverride(OVERRIDE_OFF_SETTING); // For overridableFlag, follow override if they exist - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); setOverride(OVERRIDE_ON_SETTING); // Keep overrides constant through the process - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isFalse(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isFalse(); } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() { + public void isTrue_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() { setOverride(OVERRIDE_ON_SETTING); // For overridableFlag, follow override if they exist - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); setOverride(OVERRIDE_OFF_SETTING); // Keep overrides constant through the process - assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isEnabled()).isTrue(); + assertThat(ENABLE_DESKTOP_WINDOWING_MODE.isTrue()).isTrue(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS}) - public void isEnabled_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() { + FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS}) + public void isTrue_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - public void isEnabled_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() { + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS) + public void isTrue_dwFlagOn_overrideUnset_featureFlagOff_returnsFalse() { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); } @Test @EnableFlags({ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS + FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS }) - public void isEnabled_dwFlagOn_overrideOn_featureFlagOn_returnsTrue() { + public void isTrue_dwFlagOn_overrideOn_featureFlagOn_returnsTrue() { setOverride(OVERRIDE_ON_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - public void isEnabled_dwFlagOn_overrideOn_featureFlagOff_returnsFalse() { + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS) + public void isTrue_dwFlagOn_overrideOn_featureFlagOff_returnsFalse() { setOverride(OVERRIDE_ON_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); } @Test @EnableFlags({ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS + FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS }) - public void isEnabled_dwFlagOn_overrideOff_featureFlagOn_returnsTrue() { + public void isTrue_dwFlagOn_overrideOff_featureFlagOn_returnsTrue() { setOverride(OVERRIDE_OFF_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); } @Test @EnableFlags({FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE}) - @DisableFlags(FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) - public void isEnabled_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() { + @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS) + public void isTrue_dwFlagOn_overrideOff_featureFlagOff_returnsFalse() { setOverride(OVERRIDE_OFF_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); } @Test @EnableFlags({ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS + FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() { + public void isTrue_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags({ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS + FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS }) - public void isEnabled_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() { + public void isTrue_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() { setOverride(OVERRIDE_UNSET_SETTING); // For unset overrides, follow flag - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); } @Test @EnableFlags({ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS + FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() { + public void isTrue_dwFlagOff_overrideOn_featureFlagOn_returnsTrue() { setOverride(OVERRIDE_ON_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags({ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS + FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS }) - public void isEnabled_dwFlagOff_overrideOn_featureFlagOff_returnFalse() { + public void isTrue_dwFlagOff_overrideOn_featureFlagOff_returnFalse() { setOverride(OVERRIDE_ON_SETTING); // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); } @Test @EnableFlags({ FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS + FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS }) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void isEnabled_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() { + public void isTrue_dwFlagOff_overrideOff_featureFlagOn_returnsTrue() { setOverride(OVERRIDE_OFF_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isTrue(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isTrue(); } @Test @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) @DisableFlags({ FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS + FLAG_ENABLE_DESKTOP_WINDOWING_TRANSITIONS }) - public void isEnabled_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() { + public void isTrue_dwFlagOff_overrideOff_featureFlagOff_returnsFalse() { setOverride(OVERRIDE_OFF_SETTING); // When toggle override matches its default state (dw flag), don't override flags - assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()).isFalse(); + assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse(); + } + + @Test + public void fromSetting_validInt_returnsToggleOverride() { + assertThat(fromSetting(0, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_OFF); + assertThat(fromSetting(1, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_ON); + assertThat(fromSetting(-1, OVERRIDE_ON)).isEqualTo(OVERRIDE_UNSET); + } + + @Test + public void fromSetting_invalidInt_returnsFallback() { + assertThat(fromSetting(2, OVERRIDE_ON)).isEqualTo(OVERRIDE_ON); + assertThat(fromSetting(-2, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_UNSET); + } + + @Test + public void getSetting_returnsToggleOverrideInteger() { + assertThat(OVERRIDE_OFF.getSetting()).isEqualTo(0); + assertThat(OVERRIDE_ON.getSetting()).isEqualTo(1); + assertThat(OVERRIDE_UNSET.getSetting()).isEqualTo(-1); } private void setOverride(Integer setting) { diff --git a/graphics/java/android/graphics/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java index dc785c5b0309..7a012bcde799 100644 --- a/graphics/java/android/graphics/GraphicsStatsService.java +++ b/graphics/java/android/graphics/GraphicsStatsService.java @@ -311,7 +311,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'"); return; } - nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mPackageName, + nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mUid, buffer.mInfo.mPackageName, buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime, buffer.mData); } @@ -405,7 +405,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { HistoricalBuffer buffer = buffers.get(i); File path = pathForApp(buffer.mInfo); skipFiles.add(path); - nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mPackageName, + nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mUid, buffer.mInfo.mPackageName, buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime, buffer.mData); } @@ -469,21 +469,23 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { private static native int nGetAshmemSize(); private static native long nCreateDump(int outFd, boolean isProto); - private static native void nAddToDump(long dump, String path, String packageName, + private static native void nAddToDump(long dump, String path, int uid, String packageName, long versionCode, long startTime, long endTime, byte[] data); private static native void nAddToDump(long dump, String path); private static native void nFinishDump(long dump); private static native void nFinishDumpInMemory(long dump, long pulledData, boolean lastFullDay); - private static native void nSaveBuffer(String path, String packageName, long versionCode, - long startTime, long endTime, byte[] data); + private static native void nSaveBuffer(String path, int uid, String packageName, + long versionCode, long startTime, long endTime, byte[] data); private final class BufferInfo { + final int mUid; final String mPackageName; final long mVersionCode; long mStartTime; long mEndTime; - BufferInfo(String packageName, long versionCode, long startTime) { + BufferInfo(int uid, String packageName, long versionCode, long startTime) { + this.mUid = uid; this.mPackageName = packageName; this.mVersionCode = versionCode; this.mStartTime = startTime; @@ -502,7 +504,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName, long versionCode) throws RemoteException, IOException { - mInfo = new BufferInfo(packageName, versionCode, System.currentTimeMillis()); + mInfo = new BufferInfo(uid, packageName, versionCode, System.currentTimeMillis()); mUid = uid; mPid = pid; mCallback = token; diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt deleted file mode 100644 index b5d63bd6addc..000000000000 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt +++ /dev/null @@ -1,143 +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 com.android.wm.shell.shared.desktopmode - -import android.content.Context -import android.provider.Settings -import android.util.Log -import com.android.window.flags.Flags - -/* - * An enum to check desktop mode flags state. - * - * This enum provides a centralized way to control the behavior of flags related to desktop - * windowing features which are aiming for developer preview before their release. It allows - * developer option to override the default behavior of these flags. - * - * NOTE: Flags should only be added to this enum when they have received Product and UX - * alignment that the feature is ready for developer preview, otherwise just do a flag check. - */ -enum class DesktopModeFlags( - // Function called to obtain aconfig flag value. - private val flagFunction: () -> Boolean, - // Whether the flag state should be affected by developer option. - private val shouldOverrideByDevOption: Boolean -) { - // All desktop mode related flags will be added here - DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true), - CASCADING_WINDOWS(Flags::enableCascadingWindows, true), - WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true), - MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true), - THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true), - QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true), - APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true), - TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true), - SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true), - DISABLE_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true), - DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false), - SCALED_RESIZING(Flags::enableWindowingScaledResizing, false), - ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true), - BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true), - EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true), - TASKBAR_RUNNING_APPS(Flags::enableDesktopWindowingTaskbarRunningApps, true); - - /** - * Determines state of flag based on the actual flag and desktop mode developer option - * overrides. - */ - fun isEnabled(context: Context): Boolean = - if (!Flags.showDesktopWindowingDevOption() || - !shouldOverrideByDevOption || - context.contentResolver == null) { - flagFunction() - } else { - val shouldToggleBeEnabledByDefault = - DesktopModeStatus.shouldDevOptionBeEnabledByDefault() - when (getToggleOverride(context)) { - ToggleOverride.OVERRIDE_UNSET -> flagFunction() - // When toggle override matches its default state, don't override flags. This helps - // users reset their feature overrides. - ToggleOverride.OVERRIDE_OFF -> - if (shouldToggleBeEnabledByDefault) false else flagFunction() - ToggleOverride.OVERRIDE_ON -> - if (shouldToggleBeEnabledByDefault) flagFunction() else true - } - } - - private fun getToggleOverride(context: Context): ToggleOverride { - val override = - cachedToggleOverride - ?: run { - val override = getToggleOverrideFromSystem(context) - // Cache toggle override the first time we encounter context. Override does not - // change with context, as context is just used to fetch Settings.Global - cachedToggleOverride = override - Log.d(TAG, "Toggle override initialized to: $override") - override - } - - return override - } - - private fun getToggleOverrideFromSystem(context: Context): ToggleOverride = - convertToToggleOverrideWithFallback( - Settings.Global.getInt( - context.contentResolver, - Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, - ToggleOverride.OVERRIDE_UNSET.setting), - ToggleOverride.OVERRIDE_UNSET) - - /** - * Override state of desktop mode developer option toggle. - * - * @property setting The integer value that is associated with the developer option toggle - * override - */ - enum class ToggleOverride(val setting: Int) { - /** No override is set. */ - OVERRIDE_UNSET(-1), - /** Override to off. */ - OVERRIDE_OFF(0), - /** Override to on. */ - OVERRIDE_ON(1) - } - - companion object { - private const val TAG = "DesktopModeFlags" - - /** - * Local cache for toggle override, which is initialized once on its first access. It needs - * to be refreshed only on reboots as overridden state is expected to take effect on - * reboots. - */ - private var cachedToggleOverride: ToggleOverride? = null - - private val settingToToggleOverrideMap = ToggleOverride.entries.associateBy { it.setting } - - @JvmStatic - fun convertToToggleOverrideWithFallback( - overrideInt: Int, - fallbackOverride: ToggleOverride - ): ToggleOverride { - return settingToToggleOverrideMap[overrideInt] - ?: run { - Log.w(TAG, "Unknown toggleOverride int $overrideInt") - fallbackOverride - } - } - } -} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java index 83619efefd3b..647a555ad169 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java @@ -19,6 +19,7 @@ package com.android.wm.shell.shared.desktopmode; import android.annotation.NonNull; import android.content.Context; import android.os.SystemProperties; +import android.window.flags.DesktopModeFlags; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -177,7 +178,7 @@ public class DesktopModeStatus { public static boolean canEnterDesktopMode(@NonNull Context context) { if (!isDeviceEligibleForDesktopMode(context)) return false; - return DesktopModeFlags.DESKTOP_WINDOWING_MODE.isEnabled(context); + return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue(); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index 17869e918d67..4d15605c756a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -27,6 +27,7 @@ import android.graphics.Rect; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; +import android.window.flags.DesktopModeFlags; import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; @@ -37,7 +38,6 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import com.android.wm.shell.compatui.api.CompatUIEvent; import com.android.wm.shell.compatui.impl.CompatUIEvents.SizeCompatRestartButtonAppeared; -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import java.util.function.Consumer; @@ -83,7 +83,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { mCallback = callback; mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat(); if (DesktopModeStatus.canEnterDesktopMode(context) - && DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) { + && DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) { // Don't show the SCM button for freeform tasks mHasSizeCompat &= !taskInfo.isFreeform(); } @@ -139,7 +139,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { final boolean prevHasSizeCompat = mHasSizeCompat; mHasSizeCompat = taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat(); if (DesktopModeStatus.canEnterDesktopMode(mContext) - && DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(mContext)) { + && DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) { // Don't show the SCM button for freeform tasks mHasSizeCompat &= !taskInfo.isFreeform(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 722fe1f59388..bec2ea58e106 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -1019,11 +1019,10 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static TaskStackTransitionObserver provideTaskStackTransitionObserver( - Context context, Lazy<Transitions> transitions, ShellInit shellInit ) { - return new TaskStackTransitionObserver(context, transitions, shellInit); + return new TaskStackTransitionObserver(transitions, shellInit); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 584f2721772a..80a9b675ebd8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -16,7 +16,7 @@ package com.android.wm.shell.dagger; -import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; +import static android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; import android.annotation.NonNull; import android.annotation.Nullable; @@ -636,7 +636,7 @@ public abstract class WMShellModule { @ShellMainThread Handler handler) { int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context); if (!DesktopModeStatus.canEnterDesktopMode(context) - || !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isEnabled(context) + || !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isTrue() || maxTaskLimit <= 0) { return Optional.empty(); } 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 b8ebbcdbfb9d..f3ae3ed45f41 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 @@ -84,8 +84,10 @@ import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.shared.ShellSharedConstants import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY +import android.window.flags.DesktopModeFlags +import android.window.flags.DesktopModeFlags.DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE +import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY +import android.window.flags.DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity @@ -364,7 +366,7 @@ class DesktopTasksController( wct: WindowContainerTransaction = WindowContainerTransaction(), transitionSource: DesktopModeTransitionSource, ) { - if (DesktopModeFlags.MODALS_POLICY.isEnabled(context) + if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() && isTopActivityExemptFromDesktopWindowing(context, task)) { logW("Cannot enter desktop for taskId %d, ineligible top activity found", task.taskId) return @@ -642,7 +644,7 @@ class DesktopTasksController( if (taskBoundsBeforeMaximize != null) { destinationBounds.set(taskBoundsBeforeMaximize) } else { - if (DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) { + if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) { destinationBounds.set(calculateInitialBounds(displayLayout, taskInfo)) } else { destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout)) @@ -796,7 +798,7 @@ class DesktopTasksController( dragStartBounds: Rect ) { releaseVisualIndicator() - if (!taskInfo.isResizeable && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(context)) { + if (!taskInfo.isResizeable && DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE.isTrue()) { interactionJankMonitor.begin( taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable" ) @@ -885,7 +887,8 @@ class DesktopTasksController( moveHomeTask(wct, toTop = true) // Currently, we only handle the desktop on the default display really. - if (displayId == DEFAULT_DISPLAY && WALLPAPER_ACTIVITY.isEnabled(context)) { + if (displayId == DEFAULT_DISPLAY + && ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) { // Add translucent wallpaper activity to show the wallpaper underneath addWallpaperActivity(wct) } @@ -1084,11 +1087,11 @@ class DesktopTasksController( && taskRepository.isActiveTask(triggerTask.taskId)) private fun isIncompatibleTask(task: TaskInfo) = - DesktopModeFlags.MODALS_POLICY.isEnabled(context) + DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() && isTopActivityExemptFromDesktopWindowing(context, task) private fun shouldHandleTaskClosing(request: TransitionRequestInfo): Boolean { - return WALLPAPER_ACTIVITY.isEnabled(context) && + return ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue() && TransitionUtil.isClosingType(request.type) && request.triggerTask != null } @@ -1210,7 +1213,7 @@ class DesktopTasksController( } // If task is already visible, it must have been handled already and added to desktop mode. // Cascade task only if it's not visible yet. - if (DesktopModeFlags.CASCADING_WINDOWS.isEnabled(context) + if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue() && !taskRepository.isVisibleTask(task.taskId)) { val displayLayout = displayController.getDisplayLayout(task.displayId) if (displayLayout != null) { @@ -1282,7 +1285,7 @@ class DesktopTasksController( } taskRepository.addClosingTask(task.displayId, task.taskId) // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task. - if (DesktopModeFlags.BACK_NAVIGATION.isEnabled(context) && + if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() && taskRepository.isVisibleTask(task.taskId) ) { wct.removeTask(task.token) @@ -1311,13 +1314,13 @@ class DesktopTasksController( } else { WINDOWING_MODE_FREEFORM } - val initialBounds = if (DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) { + val initialBounds = if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) { calculateInitialBounds(displayLayout, taskInfo) } else { getDefaultDesktopTaskBounds(displayLayout) } - if (DesktopModeFlags.CASCADING_WINDOWS.isEnabled(context)) { + if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue()) { cascadeWindow(taskInfo, initialBounds, displayLayout) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index dae37f4926f5..d84349b1ce1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -38,7 +38,7 @@ import com.android.wm.shell.transition.Transitions.TransitionObserver * Limits the number of tasks shown in Desktop Mode. * * This class should only be used if - * [com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT] + * [android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT] * is enabled and [maxTasksLimit] is strictly greater than 0. */ class DesktopTasksLimiter ( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index 74e53facf9c8..0841628853a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -25,7 +25,7 @@ import android.window.WindowContainerTransaction import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY +import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions @@ -79,7 +79,7 @@ class DesktopTasksTransitionObserver( } private fun updateWallpaperToken(info: TransitionInfo) { - if (!WALLPAPER_ACTIVITY.isEnabled(context)) { + if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) { return } info.changes.forEach { change -> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index dfc5ab377817..2bc01b2f310e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -304,14 +304,14 @@ sealed class DragToDesktopTransitionHandler( val leafTaskFilter = TransitionUtil.LeafTaskFilter() info.changes.withIndex().forEach { (i, change) -> if (TransitionUtil.isWallpaper(change)) { - val layer = layers.wallpaperLayers - i + val layer = layers.topWallpaperLayer - i startTransaction.apply { setLayer(change.leash, layer) show(change.leash) } } else if (isHomeChange(change)) { state.homeChange = change - val layer = layers.homeLayers - i + val layer = layers.topHomeLayer - i startTransaction.apply { setLayer(change.leash, layer) show(change.leash) @@ -325,7 +325,7 @@ sealed class DragToDesktopTransitionHandler( if (state.cancelState == CancelState.NO_CANCEL) { // Normal case, split root goes to the bottom behind everything // else. - layers.appLayers - i + layers.topAppLayer - i } else { // Cancel-early case, pretend nothing happened so split root stays // top. @@ -357,7 +357,7 @@ sealed class DragToDesktopTransitionHandler( state.otherRootChanges.add(change) val bounds = change.endAbsBounds startTransaction.apply { - setLayer(change.leash, layers.appLayers - i) + setLayer(change.leash, layers.topAppLayer - i) setWindowCrop(change.leash, bounds.width(), bounds.height()) show(change.leash) } @@ -398,6 +398,7 @@ sealed class DragToDesktopTransitionHandler( } } } + state.surfaceLayers = layers state.startTransitionFinishCb = finishCallback state.startTransitionFinishTransaction = finishTransaction startTransaction.apply() @@ -522,6 +523,10 @@ sealed class DragToDesktopTransitionHandler( startTransaction.show(change.leash) finishTransaction.show(change.leash) state.draggedTaskChange = change + // Restoring the dragged leash layer as it gets reset in the merge transition + state.surfaceLayers?.let { + startTransaction.setLayer(change.leash, it.dragLayer) + } } change.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM -> { // Other freeform tasks that are being restored go behind the dragged task. @@ -647,8 +652,15 @@ sealed class DragToDesktopTransitionHandler( } } - private fun isHomeChange(change: Change): Boolean { - return change.taskInfo?.activityType == ACTIVITY_TYPE_HOME + /** Checks if the change is a home task change */ + @VisibleForTesting + fun isHomeChange(change: Change): Boolean { + return change.taskInfo?.let { + it.activityType == ACTIVITY_TYPE_HOME && + // Skip translucent wizard task with type home + // TODO(b/368334295): Remove when the multiple home changes issue is resolved + !(it.isTopActivityTransparent && it.numActivities == 1) + } ?: false } private fun startCancelAnimation() { @@ -765,12 +777,18 @@ sealed class DragToDesktopTransitionHandler( /** * Represents the layering (Z order) that will be given to any window based on its type during - * the "start" transition of the drag-to-desktop transition + * the "start" transition of the drag-to-desktop transition. + * + * @param topAppLayer Used to calculate the app layer z-order = `topAppLayer - changeIndex`. + * @param topHomeLayer Used to calculate the home layer z-order = `topHomeLayer - changeIndex`. + * @param topWallpaperLayer Used to calculate the wallpaper layer z-order = `topWallpaperLayer - + * changeIndex` + * @param dragLayer Defines the drag layer z-order */ - protected data class DragToDesktopLayers( - val appLayers: Int, - val homeLayers: Int, - val wallpaperLayers: Int, + data class DragToDesktopLayers( + val topAppLayer: Int, + val topHomeLayer: Int, + val topWallpaperLayer: Int, val dragLayer: Int, ) @@ -790,6 +808,7 @@ sealed class DragToDesktopTransitionHandler( abstract var homeChange: Change? abstract var draggedTaskChange: Change? abstract var freeformTaskChanges: List<Change> + abstract var surfaceLayers: DragToDesktopLayers? abstract var cancelState: CancelState abstract var startAborted: Boolean @@ -803,6 +822,7 @@ sealed class DragToDesktopTransitionHandler( override var homeChange: Change? = null, override var draggedTaskChange: Change? = null, override var freeformTaskChanges: List<Change> = emptyList(), + override var surfaceLayers: DragToDesktopLayers? = null, override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, var otherRootChanges: MutableList<Change> = mutableListOf() @@ -818,6 +838,7 @@ sealed class DragToDesktopTransitionHandler( override var homeChange: Change? = null, override var draggedTaskChange: Change? = null, override var freeformTaskChanges: List<Change> = emptyList(), + override var surfaceLayers: DragToDesktopLayers? = null, override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, var splitRootChange: Change? = null, @@ -872,9 +893,9 @@ constructor( */ override fun calculateStartDragToDesktopLayers(info: TransitionInfo): DragToDesktopLayers = DragToDesktopLayers( - appLayers = info.changes.size, - homeLayers = info.changes.size * 2, - wallpaperLayers = info.changes.size * 3, + topAppLayer = info.changes.size, + topHomeLayer = info.changes.size * 2, + topWallpaperLayer = info.changes.size * 3, dragLayer = info.changes.size * 3 ) } @@ -914,9 +935,9 @@ constructor( */ override fun calculateStartDragToDesktopLayers(info: TransitionInfo): DragToDesktopLayers = DragToDesktopLayers( - appLayers = -1, - homeLayers = info.changes.size - 1, - wallpaperLayers = info.changes.size * 2 - 1, + topAppLayer = -1, + topHomeLayer = info.changes.size - 1, + topWallpaperLayer = info.changes.size * 2 - 1, dragLayer = info.changes.size * 2 ) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index a6e25a95e1eb..03ff1aac794c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -38,6 +38,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.window.WindowContainerToken; +import android.window.flags.DesktopModeFlags; import androidx.annotation.BinderThread; import androidx.annotation.NonNull; @@ -53,11 +54,9 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.recents.IRecentsAnimationRunner; import com.android.wm.shell.shared.GroupedRecentTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.shared.annotations.ShellMainThread; -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.split.SplitBounds; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -356,7 +355,7 @@ public class RecentTasksController implements TaskStackListenerCallback, private void notifyTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { if (mListener == null - || !DesktopModeFlags.TASK_STACK_OBSERVER_IN_SHELL.isEnabled(mContext) + || !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue() || taskInfo.realActivity == null) { return; } @@ -370,7 +369,7 @@ public class RecentTasksController implements TaskStackListenerCallback, private boolean shouldEnableRunningTasksForDesktopMode() { return mPcFeatureEnabled || (DesktopModeStatus.canEnterDesktopMode(mContext) - && DesktopModeFlags.TASKBAR_RUNNING_APPS.isEnabled(mContext)); + && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS.isTrue()); } @VisibleForTesting diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt index 3a0bdb94e1a7..e5bfccf0682e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt @@ -18,14 +18,13 @@ package com.android.wm.shell.recents import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM -import android.content.Context import android.os.IBinder import android.util.ArrayMap import android.view.SurfaceControl import android.view.WindowManager import android.window.TransitionInfo import com.android.wm.shell.shared.TransitionUtil -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags +import android.window.flags.DesktopModeFlags import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import dagger.Lazy @@ -38,7 +37,6 @@ import java.util.concurrent.Executor * TODO(346588978) Move split/pip signals here as well so that launcher don't need to handle it */ class TaskStackTransitionObserver( - private val context: Context, private val transitions: Lazy<Transitions>, shellInit: ShellInit ) : Transitions.TransitionObserver { @@ -64,7 +62,7 @@ class TaskStackTransitionObserver( startTransaction: SurfaceControl.Transaction, finishTransaction: SurfaceControl.Transaction ) { - if (DesktopModeFlags.TASK_STACK_OBSERVER_IN_SHELL.isEnabled(context)) { + if (DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue) { val taskInfoList = mutableListOf<RunningTaskInfo>() val transitionTypeList = mutableListOf<Int>() 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 1539b5fb34e5..d0eba23f7d45 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,8 @@ package com.android.wm.shell.windowdecor; +import static android.window.flags.DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING; + import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize; import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize; @@ -55,7 +57,6 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; @@ -234,7 +235,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) { final boolean isFreeform = taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM; - final boolean isDragResizeable = DesktopModeFlags.SCALED_RESIZING.isEnabled(mContext) + final boolean isDragResizeable = ENABLE_WINDOWING_SCALED_RESIZING.isTrue() ? isFreeform : isFreeform && taskInfo.isResizeable; final WindowDecorLinearLayout oldRootView = mResult.mRootView; @@ -284,7 +285,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL final Resources res = mResult.mRootView.getResources(); mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */, - new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(mContext, res), + new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res), getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop); } 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 75ea44d70fa4..c59d92912e80 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 @@ -84,6 +84,7 @@ import android.widget.Toast; import android.window.TaskSnapshot; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import android.window.flags.DesktopModeFlags; import androidx.annotation.Nullable; @@ -114,7 +115,6 @@ import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition; @@ -521,7 +521,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } if (!decoration.mTaskInfo.isResizeable - && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(mContext)) { + && DesktopModeFlags.DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE.isTrue()) { Toast.makeText(mContext, R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show(); } else { @@ -1373,7 +1373,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && mSplitScreenController.isTaskRootOrStageRoot(taskInfo.taskId)) { return false; } - if (DesktopModeFlags.MODALS_POLICY.isEnabled(mContext) + if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() && isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) { return false; } @@ -1666,7 +1666,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { dragStartListener, transactionFactory); - if (DesktopModeFlags.SCALED_RESIZING.isEnabled(windowDecoration.mContext)) { + if (DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING.isTrue()) { return new FixedAspectRatioTaskPositionerDecorator(windowDecoration, taskPositioner); } 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 6817f1df4873..1537a1ea4d45 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 @@ -69,6 +69,7 @@ import android.view.WindowManager; import android.widget.ImageButton; import android.window.TaskSnapshot; import android.window.WindowContainerTransaction; +import android.window.flags.DesktopModeFlags; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; @@ -89,7 +90,6 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.CaptionState; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer; @@ -471,7 +471,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } private void updateDragResizeListener(SurfaceControl oldDecorationSurface) { - if (!isDragResizable(mTaskInfo, mContext)) { + if (!isDragResizable(mTaskInfo)) { if (!mTaskInfo.positionInParent.equals(mPositionInParent)) { // We still want to track caption bar's exclusion region on a non-resizeable task. updateExclusionRegion(); @@ -505,16 +505,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (mDragResizeListener.setGeometry( new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius, new Size(mResult.mWidth, mResult.mHeight), - getResizeEdgeHandleSize(mContext, res), getResizeHandleEdgeInset(res), + getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res), getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop) || !mTaskInfo.positionInParent.equals(mPositionInParent)) { updateExclusionRegion(); } } - private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo, - Context context) { - if (DesktopModeFlags.SCALED_RESIZING.isEnabled(context)) { + private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo) { + if (DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING.isTrue()) { return taskInfo.isFreeform(); } return taskInfo.isFreeform() && taskInfo.isResizeable; @@ -577,12 +576,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState); } - private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo) { - return taskInfo.isFreeform() && taskInfo.isResizeable; - } - private void updateMaximizeMenu(SurfaceControl.Transaction startT) { - if (!isDragResizable(mTaskInfo, mContext) || !isMaximizeMenuActive()) { + if (!isDragResizable(mTaskInfo) || !isMaximizeMenuActive()) { return; } if (!mTaskInfo.isVisible()) { @@ -680,13 +675,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // their custom content. relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY; } else { - if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) { // Force-consume the caption bar insets when the app tries to hide the caption. // This improves app compatibility of immersive apps. relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING; } } - if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue()) { // Always force-consume the caption bar insets for maximum app compatibility, // including non-immersive apps that just don't handle caption insets properly. relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; @@ -732,7 +727,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // TODO(b/301119301): consider moving the config data needed for diffs to relayout params // instead of using a whole Configuration as a parameter. final Configuration windowDecorConfig = new Configuration(); - if (DesktopModeFlags.APP_HEADER_WITH_TASK_DENSITY.isEnabled(context) && isAppHeader) { + if (DesktopModeFlags.ENABLE_APP_HEADER_WITH_TASK_DENSITY.isTrue() && isAppHeader) { // Should match the density of the task. The task may have had its density overridden // to be different that SysUI's. windowDecorConfig.setTo(taskInfo.configuration); @@ -1058,7 +1053,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin loadAppInfoIfNeeded(); updateGenericLink(); final boolean supportsMultiInstance = mMultiInstanceHelper - .supportsMultiInstanceSplit(mTaskInfo.baseActivity); + .supportsMultiInstanceSplit(mTaskInfo.baseActivity) + && Flags.enableDesktopWindowingMultiInstanceFeatures(); final boolean shouldShowManageWindowsButton = supportsMultiInstance && mMinimumInstancesFound; mHandleMenu = mHandleMenuFactory.create( @@ -1375,7 +1371,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ private Region getGlobalExclusionRegion() { Region exclusionRegion; - if (mDragResizeListener != null && mTaskInfo.isResizeable) { + if (mDragResizeListener != null && isDragResizable(mTaskInfo)) { exclusionRegion = mDragResizeListener.getCornersRegion(); } else { exclusionRegion = new Region(); 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 cad34621c82a..38f9cfaca7ae 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 @@ -27,12 +27,12 @@ import android.graphics.PointF; import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.SurfaceControl; +import android.window.flags.DesktopModeFlags; import androidx.annotation.NonNull; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; /** @@ -145,7 +145,7 @@ public class DragPositioningCallbackUtility { // If the application is unresizeable and any bounds have been set back to their old // location or to a stable bound edge, reset all the bounds to maintain the applications // aspect ratio. - if (DesktopModeFlags.SCALED_RESIZING.isEnabled(windowDecoration.mDecorWindowContext) + if (DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING.isTrue() && !isAspectRatioMaintained && !windowDecoration.mTaskInfo.isResizeable) { repositionTaskBounds.top = oldTop; repositionTaskBounds.bottom = oldBottom; @@ -275,7 +275,7 @@ public class DragPositioningCallbackUtility { private static boolean isSizeConstraintForDesktopModeEnabled(Context context) { return DesktopModeStatus.canEnterDesktopMode(context) - && DesktopModeFlags.SIZE_CONSTRAINTS.isEnabled(context); + && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS.isTrue(); } interface DragStartListener { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index b5ed32bbd4e9..4ff394e2b1a9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -183,7 +183,7 @@ class DragResizeInputListener implements AutoCloseable { mTouchRegion.setEmpty(); // Apply the geometry to the touch region. - geometry.union(mContext, mTouchRegion); + geometry.union(mTouchRegion); mInputEventReceiver.setGeometry(geometry); mInputEventReceiver.setTouchRegion(mTouchRegion); @@ -358,7 +358,7 @@ class DragResizeInputListener implements AutoCloseable { */ @NonNull Region getCornersRegion() { Region region = new Region(); - mDragResizeWindowGeometry.union(mContext, region); + mDragResizeWindowGeometry.union(region); return region; } @@ -409,8 +409,8 @@ class DragResizeInputListener implements AutoCloseable { float y = e.getY(0); float rawX = e.getRawX(0); float rawY = e.getRawY(0); - final int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(mContext, - isEventFromTouchscreen(e), isEdgeResizePermitted(mContext, e), x, + final int ctrlType = mDragResizeWindowGeometry.calculateCtrlType( + isEventFromTouchscreen(e), isEdgeResizePermitted(e), x, y); ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: Handling action down, update ctrlType to %d", TAG, ctrlType); @@ -501,8 +501,8 @@ class DragResizeInputListener implements AutoCloseable { // Since we are handling cursor, we know that this is not a touchscreen event, and // that edge resizing should always be allowed. @DragPositioningCallback.CtrlType int ctrlType = - mDragResizeWindowGeometry.calculateCtrlType(mContext, /* isTouchscreen= */ - false, /* isEdgeResizePermitted= */ true, x, y); + mDragResizeWindowGeometry.calculateCtrlType(/* isTouchscreen= */ false, + /* isEdgeResizePermitted= */ true, x, y); int cursorType = PointerIcon.TYPE_DEFAULT; switch (ctrlType) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java index 6dedf6da3ab7..d726f5083eb6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java @@ -18,8 +18,8 @@ package com.android.wm.shell.windowdecor; import static android.view.InputDevice.SOURCE_MOUSE; import static android.view.InputDevice.SOURCE_TOUCHSCREEN; +import static android.window.flags.DesktopModeFlags.ENABLE_WINDOWING_EDGE_DRAG_RESIZE; -import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.EDGE_DRAG_RESIZE; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT; @@ -76,8 +76,8 @@ final class DragResizeWindowGeometry { /** * Returns the resource value to use for the resize handle on the edge of the window. */ - static int getResizeEdgeHandleSize(@NonNull Context context, @NonNull Resources res) { - return EDGE_DRAG_RESIZE.isEnabled(context) + static int getResizeEdgeHandleSize(@NonNull Resources res) { + return ENABLE_WINDOWING_EDGE_DRAG_RESIZE.isTrue() ? res.getDimensionPixelSize(R.dimen.freeform_edge_handle_outset) : res.getDimensionPixelSize(R.dimen.freeform_resize_handle); } @@ -118,11 +118,11 @@ final class DragResizeWindowGeometry { * Returns the union of all regions that can be touched for drag resizing; the corners window * and window edges. */ - void union(@NonNull Context context, @NonNull Region region) { + void union(@NonNull Region region) { // Apply the edge resize regions. mTaskEdges.union(region); - if (EDGE_DRAG_RESIZE.isEnabled(context)) { + if (ENABLE_WINDOWING_EDGE_DRAG_RESIZE.isTrue()) { // Apply the corners as well for the larger corners, to ensure we capture all possible // touches. mLargeTaskCorners.union(region); @@ -140,7 +140,7 @@ final class DragResizeWindowGeometry { final float x = e.getX(0) + offset.x; final float y = e.getY(0) + offset.y; - if (EDGE_DRAG_RESIZE.isEnabled(context)) { + if (ENABLE_WINDOWING_EDGE_DRAG_RESIZE.isTrue()) { // First check if touch falls within a corner. // Large corner bounds are used for course input like touch, otherwise fine bounds. boolean result = isEventFromTouchscreen(e) @@ -148,7 +148,7 @@ final class DragResizeWindowGeometry { : isInCornerBounds(mFineTaskCorners, x, y); // Check if touch falls within the edge resize handle. Limit edge resizing to stylus and // mouse input. - if (!result && isEdgeResizePermitted(context, e)) { + if (!result && isEdgeResizePermitted(e)) { result = isInEdgeResizeBounds(x, y); } return result; @@ -164,8 +164,8 @@ final class DragResizeWindowGeometry { return (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; } - static boolean isEdgeResizePermitted(@NonNull Context context, @NonNull MotionEvent e) { - if (EDGE_DRAG_RESIZE.isEnabled(context)) { + static boolean isEdgeResizePermitted(@NonNull MotionEvent e) { + if (ENABLE_WINDOWING_EDGE_DRAG_RESIZE.isTrue()) { return e.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS || e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE // Touchpad input @@ -193,9 +193,8 @@ final class DragResizeWindowGeometry { * resize region. */ @DragPositioningCallback.CtrlType - int calculateCtrlType(@NonNull Context context, boolean isTouchscreen, - boolean isEdgeResizePermitted, float x, float y) { - if (EDGE_DRAG_RESIZE.isEnabled(context)) { + int calculateCtrlType(boolean isTouchscreen, boolean isEdgeResizePermitted, float x, float y) { + if (ENABLE_WINDOWING_EDGE_DRAG_RESIZE.isTrue()) { // First check if touch falls within a corner. // Large corner bounds are used for course input like touch, otherwise fine bounds. int ctrlType = isTouchscreen diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt index 4faed01e9e82..2d97dc06cf89 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt @@ -32,7 +32,7 @@ import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import androidx.core.content.ContextCompat import com.android.wm.shell.R -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags +import android.window.flags.DesktopModeFlags private const val OPEN_MAXIMIZE_MENU_DELAY_ON_HOVER_MS = 350 private const val MAX_DRAWABLE_ALPHA = 255 @@ -108,7 +108,7 @@ class MaximizeButtonView( baseForegroundColor: Int? = null, rippleDrawable: RippleDrawable? = null ) { - if (DesktopModeFlags.THEMED_APP_HEADERS.isEnabled(context)) { + if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) { requireNotNull(iconForegroundColor) { "Icon foreground color must be non-null" } requireNotNull(baseForegroundColor) { "Base foreground color must be non-null" } requireNotNull(rippleDrawable) { "Ripple drawable must be non-null" } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index 4a8cabca98cf..af6a819bb705 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -47,7 +47,7 @@ import com.android.internal.R.attr.materialColorSurfaceContainerLow import com.android.internal.R.attr.materialColorSurfaceDim import com.android.window.flags.Flags.enableMinimizeButton import com.android.wm.shell.R -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags +import android.window.flags.DesktopModeFlags import com.android.wm.shell.windowdecor.MaximizeButtonView import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.OPACITY_100 @@ -157,7 +157,7 @@ class AppHeaderViewHolder( height: Int, isCaptionVisible: Boolean ) { - if (DesktopModeFlags.THEMED_APP_HEADERS.isEnabled(context)) { + if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) { bindDataWithThemedHeaders(taskInfo) } else { bindDataLegacy(taskInfo) diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt index c7cbc3e44553..22adf6c9ee2f 100644 --- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt +++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/SwitchBackToSplitFromRecent.kt @@ -48,6 +48,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { fun setup() { tapl.workspace.switchToOverview().dismissAllTasks() + tapl.setExpectedRotationCheckEnabled(false) tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java index 24f4d92af9d7..e6bd05b82be9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java @@ -47,6 +47,8 @@ public final class TestRunningTaskInfoBuilder { private ActivityManager.TaskDescription.Builder mTaskDescriptionBuilder = null; private final Point mPositionInParent = new Point(); private boolean mIsVisible = false; + private boolean mIsTopActivityTransparent = false; + private int mNumActivities = 1; private long mLastActiveTime; public static WindowContainerToken createMockWCToken() { @@ -113,6 +115,16 @@ public final class TestRunningTaskInfoBuilder { return this; } + public TestRunningTaskInfoBuilder setTopActivityTransparent(boolean isTopActivityTransparent) { + mIsTopActivityTransparent = isTopActivityTransparent; + return this; + } + + public TestRunningTaskInfoBuilder setNumActivities(int numActivities) { + mNumActivities = numActivities; + return this; + } + public TestRunningTaskInfoBuilder setLastActiveTime(long lastActiveTime) { mLastActiveTime = lastActiveTime; return this; @@ -134,6 +146,8 @@ public final class TestRunningTaskInfoBuilder { mTaskDescriptionBuilder != null ? mTaskDescriptionBuilder.build() : null; info.positionInParent = mPositionInParent; info.isVisible = mIsVisible; + info.isTopActivityTransparent = mIsTopActivityTransparent; + info.numActivities = mNumActivities; info.lastActiveTime = mLastActiveTime; return info; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 497d0e51e553..d9387d2f08dd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -35,6 +35,7 @@ import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import java.util.function.Supplier import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue import org.junit.After import org.junit.Before import org.junit.Test @@ -212,6 +213,60 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { } @Test + fun isHomeChange_withoutTaskInfo_returnsFalse() { + val change = + TransitionInfo.Change(mock(), homeTaskLeash).apply { + parent = null + taskInfo = null + } + + assertFalse(defaultHandler.isHomeChange(change)) + assertFalse(springHandler.isHomeChange(change)) + } + + @Test + fun isHomeChange_withStandardActivityTaskInfo_returnsFalse() { + val change = + TransitionInfo.Change(mock(), homeTaskLeash).apply { + parent = null + taskInfo = + TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_STANDARD).build() + } + + assertFalse(defaultHandler.isHomeChange(change)) + assertFalse(springHandler.isHomeChange(change)) + } + + @Test + fun isHomeChange_withHomeActivityTaskInfo_returnsTrue() { + val change = + TransitionInfo.Change(mock(), homeTaskLeash).apply { + parent = null + taskInfo = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build() + } + + assertTrue(defaultHandler.isHomeChange(change)) + assertTrue(springHandler.isHomeChange(change)) + } + + @Test + fun isHomeChange_withSingleTranslucentHomeActivityTaskInfo_returnsFalse() { + val change = + TransitionInfo.Change(mock(), homeTaskLeash).apply { + parent = null + taskInfo = + TestRunningTaskInfoBuilder() + .setActivityType(ACTIVITY_TYPE_HOME) + .setTopActivityTransparent(true) + .setNumActivities(1) + .build() + } + + assertFalse(defaultHandler.isHomeChange(change)) + assertFalse(springHandler.isHomeChange(change)) + } + + @Test fun cancelDragToDesktop_startWasReady_cancel() { startDrag(defaultHandler) @@ -343,6 +398,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { // Should show dragged task layer in start and finish transaction verify(mergedStartTransaction).show(draggedTaskLeash) verify(playingFinishTransaction).show(draggedTaskLeash) + // Should update the dragged task layer + verify(mergedStartTransaction).setLayer(eq(draggedTaskLeash), anyInt()) // Should merge animation verify(finishCallback).onTransitionFinished(null) } @@ -373,6 +430,8 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { // Should show dragged task layer in start and finish transaction verify(mergedStartTransaction).show(draggedTaskLeash) verify(playingFinishTransaction).show(draggedTaskLeash) + // Should update the dragged task layer + verify(mergedStartTransaction).setLayer(eq(draggedTaskLeash), anyInt()) // Should hide home task leash in finish transaction verify(playingFinishTransaction).hide(homeTaskLeash) // Should merge animation diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt index bc9b44e59d89..0e5efa650cc4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.recents import android.app.ActivityManager import android.app.WindowConfiguration -import android.content.Context import android.os.IBinder import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule @@ -60,7 +59,6 @@ class TaskStackTransitionObserverTest { @JvmField @Rule val setFlagsRule = SetFlagsRule() - @Mock private lateinit var context: Context @Mock private lateinit var shellInit: ShellInit @Mock lateinit var testExecutor: ShellExecutor @Mock private lateinit var transitionsLazy: Lazy<Transitions> @@ -74,7 +72,7 @@ class TaskStackTransitionObserverTest { MockitoAnnotations.initMocks(this) shellInit = Mockito.spy(ShellInit(testExecutor)) whenever(transitionsLazy.get()).thenReturn(transitions) - transitionObserver = TaskStackTransitionObserver(context, transitionsLazy, shellInit) + transitionObserver = TaskStackTransitionObserver(transitionsLazy, shellInit) if (Transitions.ENABLE_SHELL_TRANSITIONS) { val initRunnableCaptor = ArgumentCaptor.forClass(Runnable::class.java) verify(shellInit) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt deleted file mode 100644 index 571bdd4ea32f..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlagsTest.kt +++ /dev/null @@ -1,347 +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 com.android.wm.shell.shared.desktopmode - -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags -import android.platform.test.flag.junit.SetFlagsRule -import android.provider.Settings -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE -import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY -import com.android.window.flags.Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION -import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.Companion.convertToToggleOverrideWithFallback -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.DESKTOP_WINDOWING_MODE -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_OFF -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_ON -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ToggleOverride.OVERRIDE_UNSET -import com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY -import com.google.common.truth.Truth.assertThat -import org.junit.After -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Test class for [DesktopModeFlags] - * - * Usage: atest WMShellUnitTests:DesktopModeFlagsTest - */ -@SmallTest -@RunWith(AndroidTestingRunner::class) -class DesktopModeFlagsTest : ShellTestCase() { - - @JvmField @Rule val setFlagsRule = SetFlagsRule() - - @After - fun tearDown() { - resetToggleOverrideCache() - } - - // TODO(b/348193756): Add tests - // isEnabled_flagNotOverridable_overrideOff_featureFlagOn_returnsTrue and - // isEnabled_flagNotOverridable_overrideOn_featureFlagOff_returnsFalse after adding non - // overridable flags to DesktopModeFlags. - - @Test - @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_devOptionFlagDisabled_overrideOff_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_OFF.setting) - - // In absence of dev options, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - } - - @Test - @DisableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_devOptionFlagDisabled_overrideOn_featureFlagOff_returnsFalse() { - setOverride(OVERRIDE_ON.setting) - - // In absence of dev options, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_overrideUnset_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_UNSET.setting) - - // For overridableFlag, for unset overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_overrideUnset_featureFlagOff_returnsFalse() { - setOverride(OVERRIDE_UNSET.setting) - - // For overridableFlag, for unset overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_noOverride_featureFlagOn_returnsTrue() { - setOverride(null) - - // For overridableFlag, in absence of overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_noOverride_featureFlagOff_returnsFalse() { - setOverride(null) - - // For overridableFlag, in absence of overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_unrecognizableOverride_featureFlagOn_returnsTrue() { - setOverride(-2) - - // For overridableFlag, for recognizable overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_unrecognizableOverride_featureFlagOff_returnsFalse() { - setOverride(-2) - - // For overridableFlag, for recognizable overrides, follow flag - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_overrideOff_featureFlagOn_returnsFalse() { - setOverride(OVERRIDE_OFF.setting) - - // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_overrideOn_featureFlagOff_returnsTrue() { - setOverride(OVERRIDE_ON.setting) - - // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_overrideOffThenOn_featureFlagOn_returnsFalseAndFalse() { - setOverride(OVERRIDE_OFF.setting) - - // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - - setOverride(OVERRIDE_ON.setting) - - // Keep overrides constant through the process - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isFalse() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_overrideOnThenOff_featureFlagOff_returnsTrueAndTrue() { - setOverride(OVERRIDE_ON.setting) - - // For overridableFlag, follow override if they exist - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - - setOverride(OVERRIDE_OFF.setting) - - // Keep overrides constant through the process - assertThat(DESKTOP_WINDOWING_MODE.isEnabled(mContext)).isTrue() - } - - @Test - @EnableFlags( - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun isEnabled_dwFlagEnabled_overrideUnset_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_UNSET.setting) - - // For unset overrides, follow flag - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun isEnabled_dwFlagEnabled_overrideUnset_featureFlagOff_returnsFalse() { - setOverride(OVERRIDE_UNSET.setting) - - // For unset overrides, follow flag - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() - } - - @Test - @EnableFlags( - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun isEnabled_dwFlagEnabled_overrideOn_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_ON.setting) - - // When toggle override matches its default state (dw flag), don't override flags - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun isEnabled_dwFlagEnabled_overrideOn_featureFlagOff_returnFalse() { - setOverride(OVERRIDE_ON.setting) - - // When toggle override matches its default state (dw flag), don't override flags - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() - } - - @Test - @EnableFlags( - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, - FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun isEnabled_dwFlagEnabled_overrideOff_featureFlagOn_returnsFalse() { - setOverride(OVERRIDE_OFF.setting) - - // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun isEnabled_dwFlagEnabled_overrideOff_featureFlagOff_returnsFalse() { - setOverride(OVERRIDE_OFF.setting) - - // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() - } - - @Test - @EnableFlags( - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_dwFlagDisabled_overrideUnset_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_UNSET.setting) - - // For unset overrides, follow flag - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags( - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun isEnabled_dwFlagDisabled_overrideUnset_featureFlagOff_returnsFalse() { - setOverride(OVERRIDE_UNSET.setting) - - // For unset overrides, follow flag - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() - } - - @Test - @EnableFlags( - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_dwFlagDisabled_overrideOn_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_ON.setting) - - // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags( - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun isEnabled_dwFlagDisabled_overrideOn_featureFlagOff_returnTrue() { - setOverride(OVERRIDE_ON.setting) - - // Follow override if they exist, and is not equal to default toggle state (dw flag) - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() - } - - @Test - @EnableFlags( - FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - fun isEnabled_dwFlagDisabled_overrideOff_featureFlagOn_returnsTrue() { - setOverride(OVERRIDE_OFF.setting) - - // When toggle override matches its default state (dw flag), don't override flags - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isTrue() - } - - @Test - @EnableFlags(FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION) - @DisableFlags( - FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun isEnabled_dwFlagDisabled_overrideOff_featureFlagOff_returnsFalse() { - setOverride(OVERRIDE_OFF.setting) - - // When toggle override matches its default state (dw flag), don't override flags - assertThat(WALLPAPER_ACTIVITY.isEnabled(mContext)).isFalse() - } - - @Test - fun convertToToggleOverrideWithFallback_validInt_returnsToggleOverride() { - assertThat(convertToToggleOverrideWithFallback(0, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_OFF) - assertThat(convertToToggleOverrideWithFallback(1, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_ON) - assertThat(convertToToggleOverrideWithFallback(-1, OVERRIDE_ON)).isEqualTo(OVERRIDE_UNSET) - } - - @Test - fun convertToToggleOverrideWithFallback_invalidInt_returnsFallback() { - assertThat(convertToToggleOverrideWithFallback(2, OVERRIDE_ON)).isEqualTo(OVERRIDE_ON) - assertThat(convertToToggleOverrideWithFallback(-2, OVERRIDE_UNSET)).isEqualTo(OVERRIDE_UNSET) - } - - private fun setOverride(setting: Int?) { - val contentResolver = mContext.contentResolver - val key = Settings.Global.DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES - if (setting == null) { - Settings.Global.putString(contentResolver, key, null) - } else { - Settings.Global.putInt(contentResolver, key, setting) - } - } - - private fun resetToggleOverrideCache() { - val cachedToggleOverride = - DesktopModeFlags::class.java.getDeclaredField("cachedToggleOverride") - cachedToggleOverride.isAccessible = true - cachedToggleOverride.set(null, null) - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS deleted file mode 100644 index 2fabd4a33586..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/OWNERS +++ /dev/null @@ -1 +0,0 @@ -file:/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java index 1691f077a030..57469bf8c6e2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java @@ -128,7 +128,7 @@ public class DragResizeWindowGeometryTests extends ShellTestCase { @Test public void testRegionUnionContainsEdges() { Region region = new Region(); - GEOMETRY.union(mContext, region); + GEOMETRY.union(region); assertThat(region.isComplex()).isTrue(); // Region excludes task area. Note that coordinates start from top left. assertThat(region.contains(TASK_SIZE.getWidth() / 2, TASK_SIZE.getHeight() / 2)).isFalse(); @@ -168,7 +168,7 @@ public class DragResizeWindowGeometryTests extends ShellTestCase { @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() { Region region = new Region(); - GEOMETRY.union(mContext, region); + GEOMETRY.union(region); final int cornerRadius = LARGE_CORNER_SIZE / 2; new TestPoints(mContext, TASK_SIZE, cornerRadius).validateRegion(region); @@ -182,7 +182,7 @@ public class DragResizeWindowGeometryTests extends ShellTestCase { @DisableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() { Region region = new Region(); - GEOMETRY.union(mContext, region); + GEOMETRY.union(region); final int cornerRadius = FINE_CORNER_SIZE / 2; new TestPoints(mContext, TASK_SIZE, cornerRadius).validateRegion(region); @@ -215,7 +215,7 @@ public class DragResizeWindowGeometryTests extends ShellTestCase { CTRL_TYPE_BOTTOM); for (int i = 0; i < points.size(); i++) { - assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, + assertThat(GEOMETRY.calculateCtrlType(isTouchscreen, isEdgeResizePermitted, points.get(i).x, points.get(i).y)).isEqualTo( isEdgeResizePermitted ? expectedCtrlType.get(i) : CTRL_TYPE_UNDEFINED); } @@ -366,17 +366,17 @@ public class DragResizeWindowGeometryTests extends ShellTestCase { public void validateCtrlTypeForInnerPoints(@NonNull DragResizeWindowGeometry geometry, boolean isTouchscreen, boolean isEdgeResizePermitted, boolean expectedWithinGeometry) { - assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, + assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted, mTopLeftPoint.x, mTopLeftPoint.y)).isEqualTo( expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED); - assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, + assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted, mTopRightPoint.x, mTopRightPoint.y)).isEqualTo( expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED); - assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, + assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted, mBottomLeftPoint.x, mBottomLeftPoint.y)).isEqualTo( expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM : CTRL_TYPE_UNDEFINED); - assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, + assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted, mBottomRightPoint.x, mBottomRightPoint.y)).isEqualTo( expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM : CTRL_TYPE_UNDEFINED); @@ -389,17 +389,17 @@ public class DragResizeWindowGeometryTests extends ShellTestCase { public void validateCtrlTypeForOutsidePoints(@NonNull DragResizeWindowGeometry geometry, boolean isTouchscreen, boolean isEdgeResizePermitted, boolean expectedWithinGeometry) { - assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, + assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted, mTopLeftPointOutside.x, mTopLeftPointOutside.y)).isEqualTo( expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED); - assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, + assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted, mTopRightPointOutside.x, mTopRightPointOutside.y)).isEqualTo( expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED); - assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, + assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted, mBottomLeftPointOutside.x, mBottomLeftPointOutside.y)).isEqualTo( expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM : CTRL_TYPE_UNDEFINED); - assertThat(geometry.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted, + assertThat(geometry.calculateCtrlType(isTouchscreen, isEdgeResizePermitted, mBottomRightPointOutside.x, mBottomRightPointOutside.y)).isEqualTo( expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM : CTRL_TYPE_UNDEFINED); diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp index 09e2f423c3ba..c6cee07d1946 100644 --- a/libs/appfunctions/Android.bp +++ b/libs/appfunctions/Android.bp @@ -29,3 +29,11 @@ java_sdk_library { no_dist: true, unsafe_ignore_missing_latest_api: true, } + +prebuilt_etc { + name: "appfunctions.sidecar.xml", + system_ext_specific: true, + sub_dir: "permissions", + src: "appfunctions.sidecar.xml", + filename_from_src: true, +} diff --git a/libs/appfunctions/appfunctions.sidecar.xml b/libs/appfunctions/appfunctions.sidecar.xml new file mode 100644 index 000000000000..bef8b6ec7ce6 --- /dev/null +++ b/libs/appfunctions/appfunctions.sidecar.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<permissions> + <library + name="com.google.android.appfunctions.sidecar" + file="/system_ext/framework/com.google.android.appfunctions.sidecar.jar"/> +</permissions>
\ No newline at end of file diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 589abb4d87f4..2c23864317a4 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -404,13 +404,19 @@ void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) { } } +inline bool RenderNode::isForceInvertDark(TreeInfo& info) { + return CC_UNLIKELY( + info.forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK); +} + inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) { return CC_UNLIKELY( info && - (!info->disableForceDark || - info->forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK)); + (!info->disableForceDark || isForceInvertDark(*info))); } + + void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { if (!shouldEnableForceDark(info)) { return; @@ -421,7 +427,7 @@ void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { children.push_back(node); }); if (mDisplayList.hasText()) { - if (mDisplayList.hasFill()) { + if (isForceInvertDark(*info) && mDisplayList.hasFill()) { // Handle a special case for custom views that draw both text and background in the // same RenderNode, which would otherwise be altered to white-on-white text. usage = UsageHint::Container; diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index c9045427bd42..afbbce7e27ee 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -234,6 +234,7 @@ private: void syncDisplayList(TreeObserver& observer, TreeInfo* info); void handleForceDark(TreeInfo* info); bool shouldEnableForceDark(TreeInfo* info); + bool isForceInvertDark(TreeInfo& info); void prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer); void pushStagingPropertiesChanges(TreeInfo& info); diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp index 54369b9e4384..80a8ae1d8c17 100644 --- a/libs/hwui/jni/GraphicsStatsService.cpp +++ b/libs/hwui/jni/GraphicsStatsService.cpp @@ -42,8 +42,9 @@ static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) { return reinterpret_cast<jlong>(dump); } -static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage, - jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { +static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jint uid, + jstring jpackage, jlong versionCode, jlong startTime, jlong endTime, + jbyteArray jdata) { std::string path; const ProfileData* data = nullptr; LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null"); @@ -68,7 +69,8 @@ static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstrin LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer"); const std::string package(packageChars.c_str(), packageChars.size()); - GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data); + GraphicsStatsService::addToDump(dump, path, static_cast<uid_t>(uid), package, versionCode, + startTime, endTime, data); } static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) { @@ -91,7 +93,7 @@ static void finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr, jlong pulled GraphicsStatsService::finishDumpInMemory(dump, data, lastFullDay == JNI_TRUE); } -static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage, +static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jint uid, jstring jpackage, jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { ScopedByteArrayRO buffer(env, jdata); LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), @@ -106,7 +108,8 @@ static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpacka const std::string path(pathChars.c_str(), pathChars.size()); const std::string package(packageChars.c_str(), packageChars.size()); const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get()); - GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data); + GraphicsStatsService::saveBuffer(path, static_cast<uid_t>(uid), package, versionCode, startTime, + endTime, data); } static jobject gGraphicsStatsServiceObject = nullptr; @@ -173,16 +176,16 @@ static void nativeDestructor(JNIEnv* env, jobject javaObject) { } // namespace android using namespace android; -static const JNINativeMethod sMethods[] = - {{"nGetAshmemSize", "()I", (void*)getAshmemSize}, - {"nCreateDump", "(IZ)J", (void*)createDump}, - {"nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)addToDump}, - {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump}, - {"nFinishDump", "(J)V", (void*)finishDump}, - {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory}, - {"nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)saveBuffer}, - {"nativeInit", "()V", (void*)nativeInit}, - {"nativeDestructor", "()V", (void*)nativeDestructor}}; +static const JNINativeMethod sMethods[] = { + {"nGetAshmemSize", "()I", (void*)getAshmemSize}, + {"nCreateDump", "(IZ)J", (void*)createDump}, + {"nAddToDump", "(JLjava/lang/String;ILjava/lang/String;JJJ[B)V", (void*)addToDump}, + {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump}, + {"nFinishDump", "(J)V", (void*)finishDump}, + {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory}, + {"nSaveBuffer", "(Ljava/lang/String;ILjava/lang/String;JJJ[B)V", (void*)saveBuffer}, + {"nativeInit", "()V", (void*)nativeInit}, + {"nativeDestructor", "()V", (void*)nativeDestructor}}; int register_android_graphics_GraphicsStatsService(JNIEnv* env) { jclass graphicsStatsService_class = diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto index 745393ce1a3d..a6e786c07053 100644 --- a/libs/hwui/protos/graphicsstats.proto +++ b/libs/hwui/protos/graphicsstats.proto @@ -58,6 +58,9 @@ message GraphicsStatsProto { // HWUI renders pipeline type: GL or Vulkan optional PipelineType pipeline = 8; + + // The UID of the app + optional int32 uid = 9; } message GraphicsStatsJankSummaryProto { diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp index ece59051dae7..702f2a5626f7 100644 --- a/libs/hwui/service/GraphicsStatsService.cpp +++ b/libs/hwui/service/GraphicsStatsService.cpp @@ -22,6 +22,7 @@ #include <google/protobuf/io/zero_copy_stream_impl_lite.h> #include <inttypes.h> #include <log/log.h> +#include <stats_annotations.h> #include <stats_event.h> #include <statslog_hwui.h> #include <sys/mman.h> @@ -45,9 +46,9 @@ static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong" constexpr int sHistogramSize = ProfileData::HistogramSize(); constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize(); -static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package, - int64_t versionCode, int64_t startTime, int64_t endTime, - const ProfileData* data); +static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, uid_t uid, + const std::string& package, int64_t versionCode, + int64_t startTime, int64_t endTime, const ProfileData* data); static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd); class FileDescriptor { @@ -159,15 +160,16 @@ bool GraphicsStatsService::parseFromFile(const std::string& path, return success; } -bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package, - int64_t versionCode, int64_t startTime, int64_t endTime, - const ProfileData* data) { +bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, uid_t uid, + const std::string& package, int64_t versionCode, int64_t startTime, + int64_t endTime, const ProfileData* data) { if (proto->stats_start() == 0 || proto->stats_start() > startTime) { proto->set_stats_start(startTime); } if (proto->stats_end() == 0 || proto->stats_end() < endTime) { proto->set_stats_end(endTime); } + proto->set_uid(static_cast<int32_t>(uid)); proto->set_package_name(package); proto->set_version_code(versionCode); proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ? @@ -286,6 +288,7 @@ void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int fd) { proto->package_name().c_str(), proto->has_summary()); return; } + dprintf(fd, "\nUID: %d", proto->uid()); dprintf(fd, "\nPackage: %s", proto->package_name().c_str()); dprintf(fd, "\nVersion: %" PRId64, proto->version_code()); dprintf(fd, "\nStats since: %" PRId64 "ns", proto->stats_start()); @@ -319,14 +322,15 @@ void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int fd) { dprintf(fd, "\n"); } -void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package, - int64_t versionCode, int64_t startTime, int64_t endTime, - const ProfileData* data) { +void GraphicsStatsService::saveBuffer(const std::string& path, uid_t uid, + const std::string& package, int64_t versionCode, + int64_t startTime, int64_t endTime, const ProfileData* data) { protos::GraphicsStatsProto statsProto; if (!parseFromFile(path, &statsProto)) { statsProto.Clear(); } - if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) { + if (!mergeProfileDataIntoProto(&statsProto, uid, package, versionCode, startTime, endTime, + data)) { return; } // Although we might not have read any data from the file, merging the existing data @@ -383,7 +387,7 @@ public: private: // use package name and app version for a key - typedef std::pair<std::string, int64_t> DumpKey; + typedef std::tuple<uid_t, std::string, int64_t> DumpKey; std::map<DumpKey, protos::GraphicsStatsProto> mStats; int mFd; @@ -392,7 +396,8 @@ private: }; void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) { - auto dumpKey = std::make_pair(stat.package_name(), stat.version_code()); + auto dumpKey = std::make_tuple(static_cast<uid_t>(stat.uid()), stat.package_name(), + stat.version_code()); auto findIt = mStats.find(dumpKey); if (findIt == mStats.end()) { mStats[dumpKey] = stat; @@ -437,15 +442,15 @@ GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType return new Dump(outFd, type); } -void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, +void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, uid_t uid, const std::string& package, int64_t versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) { protos::GraphicsStatsProto statsProto; if (!path.empty() && !parseFromFile(path, &statsProto)) { statsProto.Clear(); } - if (data && - !mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) { + if (data && !mergeProfileDataIntoProto(&statsProto, uid, package, versionCode, startTime, + endTime, data)) { return; } if (!statsProto.IsInitialized()) { @@ -556,6 +561,8 @@ void GraphicsStatsService::finishDumpInMemory(Dump* dump, AStatsEventList* data, // TODO: fill in UI mainline module version, when the feature is available. AStatsEvent_writeInt64(event, (int64_t)0); AStatsEvent_writeBool(event, !lastFullDay); + AStatsEvent_writeInt32(event, stat.uid()); + AStatsEvent_addBoolAnnotation(event, ASTATSLOG_ANNOTATION_ID_IS_UID, true); AStatsEvent_build(event); } delete dump; diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h index 4063f749f808..68c735586f4b 100644 --- a/libs/hwui/service/GraphicsStatsService.h +++ b/libs/hwui/service/GraphicsStatsService.h @@ -44,13 +44,14 @@ public: ProtobufStatsd, }; - static void saveBuffer(const std::string& path, const std::string& package, int64_t versionCode, - int64_t startTime, int64_t endTime, const ProfileData* data); + static void saveBuffer(const std::string& path, uid_t uid, const std::string& package, + int64_t versionCode, int64_t startTime, int64_t endTime, + const ProfileData* data); static Dump* createDump(int outFd, DumpType type); - static void addToDump(Dump* dump, const std::string& path, const std::string& package, - int64_t versionCode, int64_t startTime, int64_t endTime, - const ProfileData* data); + static void addToDump(Dump* dump, const std::string& path, uid_t uid, + const std::string& package, int64_t versionCode, int64_t startTime, + int64_t endTime, const ProfileData* data); static void addToDump(Dump* dump, const std::string& path); static void finishDump(Dump* dump); static void finishDumpInMemory(Dump* dump, AStatsEventList* data, bool lastFullDay); diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp index c2d23e6d1101..eb164f97c9a2 100644 --- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp +++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp @@ -62,6 +62,7 @@ TEST(GraphicsStats, findRootPath) { TEST(GraphicsStats, saveLoad) { std::string path = findRootPath() + "/test_saveLoad"; + uid_t uid = 123; std::string packageName = "com.test.saveLoad"; MockProfileData mockData; mockData.editJankFrameCount() = 20; @@ -75,12 +76,13 @@ TEST(GraphicsStats, saveLoad) { for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) { mockData.editSlowFrameCounts()[i] = (i % 5) + 1; } - GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData); + GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 3000, 7000, &mockData); protos::GraphicsStatsProto loadedProto; EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto)); // Clean up the file unlink(path.c_str()); + EXPECT_EQ(uid, loadedProto.uid()); EXPECT_EQ(packageName, loadedProto.package_name()); EXPECT_EQ(5, loadedProto.version_code()); EXPECT_EQ(3000, loadedProto.stats_start()); @@ -109,6 +111,7 @@ TEST(GraphicsStats, saveLoad) { TEST(GraphicsStats, merge) { std::string path = findRootPath() + "/test_merge"; std::string packageName = "com.test.merge"; + uid_t uid = 123; MockProfileData mockData; mockData.editJankFrameCount() = 20; mockData.editTotalFrameCount() = 100; @@ -121,7 +124,7 @@ TEST(GraphicsStats, merge) { for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) { mockData.editSlowFrameCounts()[i] = (i % 5) + 1; } - GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData); + GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 3000, 7000, &mockData); mockData.editJankFrameCount() = 50; mockData.editTotalFrameCount() = 500; for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) { @@ -130,13 +133,15 @@ TEST(GraphicsStats, merge) { for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) { mockData.editSlowFrameCounts()[i] = ((i % 10) + 1) * 2; } - GraphicsStatsService::saveBuffer(path, packageName, 5, 7050, 10000, &mockData); + + GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 7050, 10000, &mockData); protos::GraphicsStatsProto loadedProto; EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto)); // Clean up the file unlink(path.c_str()); + EXPECT_EQ(uid, loadedProto.uid()); EXPECT_EQ(packageName, loadedProto.package_name()); EXPECT_EQ(5, loadedProto.version_code()); EXPECT_EQ(3000, loadedProto.stats_start()); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 9af6b2842988..8394daf5966c 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -709,6 +709,10 @@ interface IAudioService { @EnforcePermission("MODIFY_AUDIO_ROUTING") List<AudioFocusInfo> getFocusStack(); + @EnforcePermission("MODIFY_AUDIO_ROUTING") + oneway void sendFocusLossAndUpdate(in AudioFocusInfo focusLoser, in IAudioPolicyCallback apcb); + + @EnforcePermission("MODIFY_AUDIO_ROUTING") boolean sendFocusLoss(in AudioFocusInfo focusLoser, in IAudioPolicyCallback apcb); @EnforcePermission("MODIFY_AUDIO_ROUTING") diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 293a8f89fbca..2c8e3522c7ed 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -927,6 +927,29 @@ public class AudioPolicy { } /** + * @hide + * Causes the given audio focus owner to lose audio focus with + * {@link android.media.AudioManager#AUDIOFOCUS_LOSS}, and be removed from the focus stack. + * Unlike {@link #sendFocusLoss(AudioFocusInfo)}, the method causes the focus stack + * to be reevaluated as the discarded focus owner may have been at the top of stack, + * and now the new owner needs to be notified of the gain. + * @param focusLoser identifies the focus owner to discard from the focus stack + * @throws IllegalStateException if used on an unregistered policy, or a registered policy + * with no {@link AudioPolicyFocusListener} set + * @see #getFocusStack() + * @see #sendFocusLoss(AudioFocusInfo) + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void sendFocusLossAndUpdate(@NonNull AudioFocusInfo focusLoser) + throws IllegalStateException { + try { + getService().sendFocusLossAndUpdate(Objects.requireNonNull(focusLoser), cb()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Send AUDIOFOCUS_LOSS to a specific stack entry, causing it to be notified of the focus * loss, and for it to exit the focus stack (its focus listener will not be invoked after that). * This operation is only valid for a registered policy (with diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig index bf6ec9635912..185f579df4b9 100644 --- a/media/java/android/media/flags/editing.aconfig +++ b/media/java/android/media/flags/editing.aconfig @@ -8,3 +8,10 @@ flag { description: "Add media metrics for transcoding/editing events." bug: "297487694" } + +flag { + name: "stagefrightrecorder_enable_b_frames" + namespace: "media_solutions" + description: "Enable B frames for Stagefright recorder." + bug: "341121900" +} diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index 244292d15e81..bfc13243af68 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -393,6 +393,16 @@ public final class MidiManager { /** * Opens a Bluetooth MIDI device for reading and writing. + * Bluetooth MIDI devices are only available after openBluetoothDevice() is called. + * Once that happens anywhere in the system, then the BLE-MIDI device will appear as just + * another MidiDevice to other apps. + * + * If the device opened using openBluetoothDevice() is closed, then it will no longer be + * available. To other apps, it will appear as if the BLE MidiDevice had been unplugged. + * If a MidiDevice is garbage collected then it will be closed automatically. + * If you want the BLE-MIDI device to remain available you should keep the object alive. + * + * You may close the device with MidiDevice.close(). * * @param bluetoothDevice a {@link android.bluetooth.BluetoothDevice} to open as a MIDI device * @param listener a {@link MidiManager.OnDeviceOpenedListener} to be called to receive the diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp index baeff7e6c006..1661dfb2a86b 100644 --- a/packages/SettingsLib/SettingsTheme/Android.bp +++ b/packages/SettingsLib/SettingsTheme/Android.bp @@ -15,7 +15,10 @@ android_library { "src/**/*.kt", ], resource_dirs: ["res"], - static_libs: ["androidx.preference_preference"], + static_libs: [ + "androidx.preference_preference", + "com.google.android.material_material", + ], sdk_version: "system_current", min_sdk_version: "21", apex_available: [ diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_check.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_check.xml new file mode 100644 index 000000000000..309dbdf1ea96 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_check.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M382,720L154,492L211,435L382,606L749,239L806,296L382,720Z"/> +</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml new file mode 100644 index 000000000000..16ca18ae2200 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="18dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="@color/settingslib_materialColorOnSurfaceVariant" + android:autoMirrored="true"> + <path + android:fillColor="@android:color/white" + android:pathData="M321,880L250,809L579,480L250,151L321,80L721,480L321,880Z"/> +</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_close.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_close.xml new file mode 100644 index 000000000000..e6df8a416922 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_close.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/> +</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_switch_thumb_icon.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_switch_thumb_icon.xml new file mode 100644 index 000000000000..342729d7ee5a --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_switch_thumb_icon.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_checked="true" android:drawable="@drawable/settingslib_expressive_icon_check"/> + <item android:state_checked="false" android:drawable="@drawable/settingslib_expressive_icon_close"/> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml index f4766ee7b0a9..543b237373fb 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml @@ -26,7 +26,10 @@ <solid android:color="@color/settingslib_materialColorSurfaceContainer" /> <corners - android:radius="@dimen/settingslib_preference_corner_radius_selected" /> + android:topLeftRadius="4dp" + android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius" + android:topRightRadius="4dp" + android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" /> <padding android:bottom="16dp"/> </shape> diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml index 40eafc224d31..6d2cd1a51620 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml @@ -25,7 +25,7 @@ <solid android:color="@color/settingslib_materialColorSurfaceContainer" /> <corners - android:radius="@dimen/settingslib_preference_corner_radius_selected" /> + android:radius="4dp" /> </shape> </item> </ripple>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml index f4766ee7b0a9..bcdbf1d19545 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml @@ -26,7 +26,7 @@ <solid android:color="@color/settingslib_materialColorSurfaceContainer" /> <corners - android:radius="@dimen/settingslib_preference_corner_radius_selected" /> + android:radius="@dimen/settingslib_preference_corner_radius" /> <padding android:bottom="16dp"/> </shape> diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml index 40eafc224d31..d4b658c384e6 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml @@ -25,7 +25,10 @@ <solid android:color="@color/settingslib_materialColorSurfaceContainer" /> <corners - android:radius="@dimen/settingslib_preference_corner_radius_selected" /> + android:topLeftRadius="@dimen/settingslib_preference_corner_radius" + android:bottomLeftRadius="4dp" + android:topRightRadius="@dimen/settingslib_preference_corner_radius" + android:bottomRightRadius="4dp" /> </shape> </item> </ripple>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml new file mode 100644 index 000000000000..2475dfd90e6e --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:gravity="center_vertical" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:background="?android:attr/selectableItemBackground" + android:clipToPadding="false" + android:baselineAligned="false"> + + <include layout="@layout/settingslib_expressive_preference_icon_frame"/> + + <include layout="@layout/settingslib_expressive_preference_text_frame"/> + + <!-- Preference should place its actual preference widget here. --> + <LinearLayout + android:id="@android:id/widget_frame" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="end|center_vertical" + android:paddingStart="@dimen/settingslib_expressive_space_small1" + android:paddingEnd="0dp" + android:orientation="vertical"/> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml new file mode 100644 index 000000000000..f5017a5ae368 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/icon_frame" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="@dimen/settingslib_expressive_space_medium3" + android:minHeight="@dimen/settingslib_expressive_space_medium3" + android:gravity="center" + android:layout_marginEnd="-8dp"> + + <androidx.preference.internal.PreferenceImageView + android:id="@android:id/icon" + android:layout_width="@dimen/settingslib_expressive_space_medium3" + android:layout_height="@dimen/settingslib_expressive_space_medium3" + android:scaleType="centerInside"/> + +</LinearLayout> diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_switch.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_switch.xml new file mode 100644 index 000000000000..4cbdfd5b7c36 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_switch.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<com.google.android.material.materialswitch.MaterialSwitch + xmlns:android="http://schemas.android.com/apk/res/android" + android:theme="@style/Theme.Material3.DynamicColors.DayNight" + android:id="@+id/switchWidget" + style="@style/SettingslibSwitchStyle.Expressive"/>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml new file mode 100644 index 000000000000..e3e689b4838c --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="@dimen/settingslib_expressive_space_none" + android:layout_height="wrap_content" + android:layout_weight="1" + android:padding="@dimen/settingslib_expressive_space_small1"> + + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:textAlignment="viewStart" + android:textAppearance="?android:attr/textAppearanceListItem" + android:maxLines="2" + android:ellipsize="marquee"/> + + <TextView + android:id="@android:id/summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@android:id/title" + android:layout_alignLeft="@android:id/title" + android:layout_alignStart="@android:id/title" + android:layout_gravity="start" + android:textAlignment="viewStart" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" + android:textColor="?android:attr/textColorSecondary" + android:maxLines="10"/> +</RelativeLayout> diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml new file mode 100644 index 000000000000..3f751812ee40 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/two_target_divider" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="start|center_vertical" + android:orientation="horizontal" + android:paddingStart="?android:attr/listPreferredItemPaddingEnd" + android:paddingLeft="?android:attr/listPreferredItemPaddingEnd" + android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall7"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingEnd="@dimen/settingslib_expressive_space_extrasmall6" + android:src="@drawable/settingslib_expressive_icon_chevron"/> + + <View + android:layout_width="1dp" + android:layout_height="40dp" + android:background="?android:attr/listDivider" /> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml new file mode 100644 index 000000000000..2320aab8f459 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<resources> + <!-- Expressive design start --> + <!-- corner radius token --> + <dimen name="settingslib_expressive_radius_none">0dp</dimen> + <dimen name="settingslib_expressive_radius_full">360dp</dimen> + <dimen name="settingslib_expressive_radius_extrasmall1">2dp</dimen> + <dimen name="settingslib_expressive_radius_extrasmall2">4dp</dimen> + <dimen name="settingslib_expressive_radius_small">8dp</dimen> + <dimen name="settingslib_expressive_radius_medium">12dp</dimen> + <dimen name="settingslib_expressive_radius_large1">16dp</dimen> + <dimen name="settingslib_expressive_radius_large2">20dp</dimen> + <dimen name="settingslib_expressive_radius_large3">24dp</dimen> + <dimen name="settingslib_expressive_radius_extralarge1">28dp</dimen> + <dimen name="settingslib_expressive_radius_extralarge2">32dp</dimen> + <dimen name="settingslib_expressive_radius_extralarge3">42dp</dimen> + + <!-- space token --> + <dimen name="settingslib_expressive_space_none">0dp</dimen> + <dimen name="settingslib_expressive_space_extrasmall1">2dp</dimen> + <dimen name="settingslib_expressive_space_extrasmall2">4dp</dimen> + <dimen name="settingslib_expressive_space_extrasmall3">6dp</dimen> + <dimen name="settingslib_expressive_space_extrasmall4">8dp</dimen> + <dimen name="settingslib_expressive_space_extrasmall5">10dp</dimen> + <dimen name="settingslib_expressive_space_extrasmall6">12dp</dimen> + <dimen name="settingslib_expressive_space_extrasmall7">14dp</dimen> + <dimen name="settingslib_expressive_space_small1">16dp</dimen> + <dimen name="settingslib_expressive_space_small2">18dp</dimen> + <dimen name="settingslib_expressive_space_small3">20dp</dimen> + <dimen name="settingslib_expressive_space_small4">24dp</dimen> + <dimen name="settingslib_expressive_space_medium1">32dp</dimen> + <dimen name="settingslib_expressive_space_medium2">36dp</dimen> + <dimen name="settingslib_expressive_space_medium3">40dp</dimen> + <dimen name="settingslib_expressive_space_medium4">48dp</dimen> + <dimen name="settingslib_expressive_space_large1">60dp</dimen> + <dimen name="settingslib_expressive_space_large2">64dp</dimen> + <dimen name="settingslib_expressive_space_large3">72dp</dimen> + <dimen name="settingslib_expressive_space_large4">80dp</dimen> + <dimen name="settingslib_expressive_space_large5">96dp</dimen> + <!-- Expressive theme end --> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml new file mode 100644 index 000000000000..04ae80e72401 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml @@ -0,0 +1,172 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<resources> + <style name="SettingsLibTextAppearance" parent="@android:style/TextAppearance.DeviceDefault"> + <!--item name="android:fontFamily"></item--> + <item name="android:hyphenationFrequency">normalFast</item> + <item name="android:lineBreakWordStyle">phrase</item> + </style> + + <style name="SettingsLibTextAppearance.Primary"> + <!--item name="android:fontFamily"></item--> + </style> + + <style name="SettingsLibTextAppearance.Primary.Display"> + <!--item name="android:fontFamily"></item--> + </style> + <style name="SettingsLibTextAppearance.Primary.Display.Large"> + <item name="android:textSize">57sp</item> + </style> + <style name="SettingsLibTextAppearance.Primary.Display.Medium"> + <item name="android:textSize">45sp</item> + </style> + <style name="SettingsLibTextAppearance.Primary.Display.Small"> + <item name="android:textSize">36sp</item> + </style> + + <style name="SettingsLibTextAppearance.Primary.Headline"> + <!--item name="android:fontFamily"></item--> + </style> + <style name="SettingsLibTextAppearance.Primary.Headline.Large"> + <item name="android:textSize">32sp</item> + </style> + <style name="SettingsLibTextAppearance.Primary.Headline.Medium"> + <item name="android:textSize">28sp</item> + </style> + <style name="SettingsLibTextAppearance.Primary.Headline.Small"> + <item name="android:textSize">24sp</item> + </style> + + <style name="SettingsLibTextAppearance.Primary.Title"> + <!--item name="android:fontFamily"></item--> + </style> + <style name="SettingsLibTextAppearance.Primary.Title.Large"> + <item name="android:textSize">22sp</item> + </style> + <style name="SettingsLibTextAppearance.Primary.Title.Medium"> + <item name="android:textSize">16sp</item> + </style> + <style name="SettingsLibTextAppearance.Primary.Title.Small"> + <item name="android:textSize">14sp</item> + </style> + + <style name="SettingsLibTextAppearance.Primary.Label"> + <!--item name="android:fontFamily"></item--> + </style> + <style name="SettingsLibTextAppearance.Primary.Label.Large"> + <item name="android:textSize">14sp</item> + </style> + <style name="SettingsLibTextAppearance.Primary.Label.Medium"> + <item name="android:textSize">12sp</item> + </style> + <style name="SettingsLibTextAppearance.Primary.Label.Small"> + <item name="android:textSize">11sp</item> + </style> + + <style name="SettingsLibTextAppearance.Primary.Body"> + <!--item name="android:fontFamily"></item--> + </style> + <style name="SettingsLibTextAppearance.Primary.Body.Large"> + <item name="android:textSize">16sp</item> + </style> + <style name="SettingsLibTextAppearance.Primary.Body.Medium"> + <item name="android:textSize">14sp</item> + </style> + <style name="SettingsLibTextAppearance.Primary.Body.Small"> + <item name="android:textSize">12sp</item> + </style> + + <style name="SettingsLibTextAppearance.Emphasized"> + <!--item name="android:fontFamily"></item--> + </style> + + <style name="SettingsLibTextAppearance.Emphasized.Display"> + <!--item name="android:fontFamily"></item--> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Display.Large"> + <item name="android:textSize">57sp</item> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Display.Medium"> + <item name="android:textSize">45sp</item> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Display.Small"> + <item name="android:textSize">36sp</item> + </style> + + <style name="SettingsLibTextAppearance.Emphasized.Headline"> + <!--item name="android:fontFamily"></item--> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Headline.Large"> + <item name="android:textSize">32sp</item> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Headline.Medium"> + <item name="android:textSize">28sp</item> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Headline.Small"> + <item name="android:textSize">24sp</item> + </style> + + <style name="SettingsLibTextAppearance.Emphasized.Title"> + <!--item name="android:fontFamily"></item--> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Title.Large"> + <item name="android:textSize">22sp</item> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Title.Medium"> + <item name="android:textSize">16sp</item> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Title.Small"> + <item name="android:textSize">14sp</item> + </style> + + <style name="SettingsLibTextAppearance.Emphasized.Label"> + <!--item name="android:fontFamily"></item--> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Label.Large"> + <item name="android:textSize">14sp</item> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Label.Medium"> + <item name="android:textSize">12sp</item> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Label.Small"> + <item name="android:textSize">11sp</item> + </style> + + <style name="SettingsLibTextAppearance.Emphasized.Body"> + <!--item name="android:fontFamily"></item--> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Body.Large"> + <item name="android:textSize">16sp</item> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Body.Medium"> + <item name="android:textSize">14sp</item> + </style> + <style name="SettingsLibTextAppearance.Emphasized.Body.Small"> + <item name="android:textSize">12sp</item> + </style> + + <style name="SettingslibSwitchStyle.Expressive" parent=""> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item> + <item name="android:background">@null</item> + <item name="android:clickable">false</item> + <item name="android:focusable">false</item> + <item name="thumbIcon">@drawable/settingslib_expressive_switch_thumb_icon</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml new file mode 100644 index 000000000000..3c69027c2080 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_preference_expressive.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> +<resources> + <style name="SettingsLibPreference" parent="SettingsPreference.SettingsLib"/> + + <style name="SettingsLibPreference.Category" parent="SettingsCategoryPreference.SettingsLib"/> + + <style name="SettingsLibPreference.CheckBoxPreference" parent="SettingsCheckBoxPreference.SettingsLib"/> + + <style name="SettingsLibPreference.SwitchPreferenceCompat" parent="SettingsSwitchPreferenceCompat.SettingsLib"/> + + <style name="SettingsLibPreference.SeekBarPreference" parent="SettingsSeekbarPreference.SettingsLib"/> + + <style name="SettingsLibPreference.PreferenceScreen" parent="SettingsPreferenceScreen.SettingsLib"/> + + <style name="SettingsLibPreference.DialogPreference" parent="SettingsPreference.SettingsLib"/> + + <style name="SettingsLibPreference.DialogPreference.EditTextPreference" parent="SettingsEditTextPreference.SettingsLib"/> + + <style name="SettingsLibPreference.DropDown" parent="SettingsDropdownPreference.SettingsLib"/> + + <style name="SettingsLibPreference.SwitchPreference" parent="SettingsSwitchPreference.SettingsLib"/> + + <style name="SettingsLibPreference.Expressive"> + <item name="android:layout">@layout/settingslib_expressive_preference</item> + </style> + + <style name="SettingsLibPreference.Category.Expressive"> + </style> + + <style name="SettingsLibPreference.CheckBoxPreference.Expressive"> + <item name="android:layout">@layout/settingslib_expressive_preference</item> + </style> + + <style name="SettingsLibPreference.SwitchPreferenceCompat.Expressive"> + <item name="android:layout">@layout/settingslib_expressive_preference</item> + <item name="android:widgetLayout">@layout/settingslib_expressive_preference_switch</item> + </style> + + <style name="SettingsLibPreference.SeekBarPreference.Expressive"/> + + <style name="SettingsLibPreference.PreferenceScreen.Expressive"> + <item name="android:layout">@layout/settingslib_expressive_preference</item> + </style> + + <style name="SettingsLibPreference.DialogPreference.Expressive"> + </style> + + <style name="SettingsLibPreference.DialogPreference.EditTextPreference.Expressive"> + <item name="android:layout">@layout/settingslib_expressive_preference</item> + <item name="android:dialogLayout">@layout/settingslib_preference_dialog_edittext</item> + </style> + + <style name="SettingsLibPreference.DropDown.Expressive"> + </style> + + <style name="SettingsLibPreference.SwitchPreference.Expressive"/> +</resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml new file mode 100644 index 000000000000..fea8739ab37d --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<resources> + <style name="Theme.SettingsBase.Expressive"> + <!-- Set up Preference title text style --> + <!--item name="android:textAppearanceListItem">@style/TextAppearance.PreferenceTitle.SettingsLib</item--> + <!--item name="android:textAppearanceListItemSecondary">@style/textAppearanceListItemSecondary</item--> + + <!-- Set up list item padding --> + <item name="android:listPreferredItemPaddingStart">@dimen/settingslib_expressive_space_small1</item> + <item name="android:listPreferredItemPaddingLeft">@dimen/settingslib_expressive_space_small1</item> + <item name="android:listPreferredItemPaddingEnd">@dimen/settingslib_expressive_space_small1</item> + <item name="android:listPreferredItemPaddingRight">@dimen/settingslib_expressive_space_small1</item> + <item name="android:listPreferredItemHeightSmall">@dimen/settingslib_expressive_space_large3</item> + + <!-- Set up preference theme --> + <item name="preferenceTheme">@style/PreferenceTheme.SettingsLib.Expressive</item> + + <!-- Set up Spinner style --> + <!--item name="android:spinnerStyle"></item> + <item name="android:spinnerItemStyle"></item> + <item name="android:spinnerDropDownItemStyle"></item--> + + <!-- Set up edge-to-edge configuration for top app bar --> + <item name="android:clipToPadding">false</item> + <item name="android:clipChildren">false</item> + </style> + + <!-- Using in SubSettings page including injected settings page --> + <style name="Theme.SubSettingsBase.Expressive" parent="Theme.SettingsBase.Expressive"> + <!-- Suppress the built-in action bar --> + <item name="android:windowActionBar">false</item> + <item name="android:windowNoTitle">true</item> + + <!-- Set up edge-to-edge configuration for top app bar --> + <item name="android:navigationBarColor">@android:color/transparent</item> + <item name="android:statusBarColor">@android:color/transparent</item> + <item name="colorControlNormal">?android:attr/colorControlNormal</item> + + <!-- For AndroidX AlertDialog --> + <!--item name="alertDialogTheme">@style/Theme.AlertDialog.SettingsLib</item--> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes_preference_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes_preference_expressive.xml new file mode 100644 index 000000000000..41fe2250f0ad --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes_preference_expressive.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<resources> + <style name="PreferenceTheme.SettingsLib.Expressive"> + <item name="checkBoxPreferenceStyle">@style/SettingsLibPreference.CheckBoxPreference.Expressive</item> + <item name="dialogPreferenceStyle">@style/SettingsLibPreference.DialogPreference.Expressive</item> + <item name="dropdownPreferenceStyle">@style/SettingsLibPreference.DropDown.Expressive</item> + <item name="editTextPreferenceStyle">@style/SettingsLibPreference.DialogPreference.EditTextPreference.Expressive</item> + <item name="seekBarPreferenceStyle">@style/SettingsLibPreference.SeekBarPreference.Expressive</item> + <item name="preferenceCategoryStyle">@style/SettingsLibPreference.Category.Expressive</item> + <item name="preferenceScreenStyle">@style/SettingsLibPreference.PreferenceScreen.Expressive</item> + <item name="preferenceStyle">@style/SettingsLibPreference.Expressive</item> + <item name="switchPreferenceCompatStyle">@style/SettingsLibPreference.SwitchPreferenceCompat.Expressive</item> + <item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle.SettingsLib</item> + <item name="preferenceCategoryTitleTextColor">@color/settingslib_materialColorPrimary</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index 3011ce05c3a5..b69912a3fd36 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.7.0-rc01" + extra["jetpackComposeVersion"] = "1.7.0" } subprojects { diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml index 18a6db035070..f942fd0662a5 100644 --- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml +++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml @@ -26,6 +26,8 @@ <string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string> <!-- Footer text with two links. [DO NOT TRANSLATE] --> <string name="footer_with_two_links" translatable="false">Annotated string with <a href="https://www.android.com/">link 1</a> and <a href="https://source.android.com/">link 2</a>.</string> + <!-- TopIntroPreference preview text. [DO NOT TRANSLATE] --> + <string name="label_with_two_links" translatable="false"><a href="https://www.android.com/">Label</a></string> <!-- Sample title --> <string name="sample_title" translatable="false">Lorem ipsum</string> diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 83d657ef380d..7139f5b468ca 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -42,12 +42,15 @@ import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider import com.android.settingslib.spa.gallery.scaffold.NonScrollablePagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider +import com.android.settingslib.spa.gallery.preference.IntroPreferencePageProvider import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider import com.android.settingslib.spa.gallery.preference.PreferencePageProvider import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider +import com.android.settingslib.spa.gallery.preference.TopIntroPreferencePageProvider import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider +import com.android.settingslib.spa.gallery.preference.ZeroStatePreferencePageProvider import com.android.settingslib.spa.gallery.scaffold.PagerMainPageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider @@ -82,6 +85,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { MainSwitchPreferencePageProvider, ListPreferencePageProvider, TwoTargetSwitchPreferencePageProvider, + ZeroStatePreferencePageProvider, ArgumentPageProvider, SliderPageProvider, SpinnerPageProvider, @@ -109,6 +113,8 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { SuwScaffoldPageProvider, BannerPageProvider, CopyablePageProvider, + IntroPreferencePageProvider, + TopIntroPreferencePageProvider, ), rootPages = listOf( HomePageProvider.createSettingsPage(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt new file mode 100644 index 000000000000..603fceed9900 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt @@ -0,0 +1,78 @@ +/* + * 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.settingslib.spa.gallery.preference + +import android.os.Bundle +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.AirplanemodeActive +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.android.settingslib.spa.framework.common.SettingsEntry +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.widget.preference.IntroPreference +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel + +private const val TITLE = "Sample IntroPreference" + +object IntroPreferencePageProvider : SettingsPageProvider { + override val name = "IntroPreference" + private val owner = createSettingsPage() + + override fun buildEntry(arguments: Bundle?): List<SettingsEntry> { + val entryList = mutableListOf<SettingsEntry>() + entryList.add( + SettingsEntryBuilder.create("IntroPreference", owner) + .setUiLayoutFn { SampleIntroPreference() } + .build() + ) + + return entryList + } + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner).setUiLayoutFn { + Preference( + object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + } + ) + } + } + + override fun getTitle(arguments: Bundle?): String { + return TITLE + } +} + +@Composable +private fun SampleIntroPreference() { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + IntroPreference( + title = "Preferred network type", + descriptions = listOf("Description"), + imageVector = Icons.Outlined.AirplanemodeActive, + ) + } +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt index ce9678bab684..1626b025e2f7 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt @@ -39,6 +39,9 @@ object PreferenceMainPageProvider : SettingsPageProvider { ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), TwoTargetSwitchPreferencePageProvider.buildInjectEntry() .setLink(fromPage = owner).build(), + ZeroStatePreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + IntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + TopIntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ) } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt new file mode 100644 index 000000000000..b251266e0574 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt @@ -0,0 +1,83 @@ +/* + * 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.settingslib.spa.gallery.preference + +import android.os.Bundle +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.android.settingslib.spa.gallery.R +import com.android.settingslib.spa.framework.common.SettingsEntry +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.preference.TopIntroPreference +import com.android.settingslib.spa.widget.preference.TopIntroPreferenceModel + +private const val TITLE = "Sample TopIntroPreference" + +object TopIntroPreferencePageProvider : SettingsPageProvider { + override val name = "TopIntroPreference" + private val owner = createSettingsPage() + + override fun buildEntry(arguments: Bundle?): List<SettingsEntry> { + val entryList = mutableListOf<SettingsEntry>() + entryList.add( + SettingsEntryBuilder.create("TopIntroPreference", owner) + .setUiLayoutFn { SampleTopIntroPreference() } + .build() + ) + + return entryList + } + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner).setUiLayoutFn { + Preference( + object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + } + ) + } + } + + override fun getTitle(arguments: Bundle?): String { + return TITLE + } +} + +@Composable +private fun SampleTopIntroPreference() { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + TopIntroPreference( + object : TopIntroPreferenceModel { + override val text = + "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" + + "Example collapsed text area that you will not see until you expand this block." + override val expandText = "Expand" + override val collapseText = "Collapse" + override val labelText = R.string.label_with_two_links + } + ) + } +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt new file mode 100644 index 000000000000..4a9c5c8fad4f --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt @@ -0,0 +1,92 @@ +/* + * 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.settingslib.spa.gallery.preference + +import android.os.Bundle +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.History +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.common.SettingsEntry +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.preference.ZeroStatePreference + +private const val TITLE = "Sample ZeroStatePreference" + +object ZeroStatePreferencePageProvider : SettingsPageProvider { + override val name = "ZeroStatePreference" + private val owner = createSettingsPage() + + override fun buildEntry(arguments: Bundle?): List<SettingsEntry> { + val entryList = mutableListOf<SettingsEntry>() + entryList.add( + SettingsEntryBuilder.create("ZeroStatePreference", owner) + .setUiLayoutFn { + SampleZeroStatePreference() + }.build() + ) + + return entryList + } + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + } + + override fun getTitle(arguments: Bundle?): String { + return TITLE + } +} + +@Composable +private fun SampleZeroStatePreference() { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + ZeroStatePreference( + Icons.Filled.History, + "No recent search history", + "Description" + ) + } +} + + +@Preview(showBackground = true) +@Composable +private fun SwitchPreferencePagePreview() { + SettingsTheme { + ZeroStatePreferencePageProvider.Page(null) + } +} diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index f0c2ea6f5353..790aa9ff8d3f 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -54,15 +54,16 @@ android { dependencies { api(project(":SettingsLibColor")) api("androidx.appcompat:appcompat:1.7.0") - api("androidx.compose.material3:material3:1.3.0-rc01") + api("androidx.compose.material3:material3:1.3.0") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") api("androidx.lifecycle:lifecycle-livedata-ktx") api("androidx.lifecycle:lifecycle-runtime-compose") - api("androidx.navigation:navigation-compose:2.8.0-rc01") + api("androidx.navigation:navigation-compose:2.8.1") api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha") api("com.google.android.material:material:1.11.0") + api("androidx.graphics:graphics-shapes-android:1.0.1") debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion") implementation("com.airbnb.android:lottie-compose:6.4.0") diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index 1f3e24254027..f8c791aab0d0 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -21,7 +21,9 @@ import androidx.compose.ui.unit.dp object SettingsDimension { val paddingTiny = 2.dp - val paddingSmall = 4.dp + val paddingExtraSmall = 4.dp + val paddingSmall = if (isSpaExpressiveEnabled) 8.dp else 4.dp + val paddingExtraSmall5 = 10.dp val paddingLarge = 16.dp val paddingExtraLarge = 24.dp @@ -56,6 +58,7 @@ object SettingsDimension { val itemDividerHeight = 32.dp val iconLarge = 48.dp + val introIconSize = 40.dp /** The size when app icon is displayed in list. */ val appIconItemSize = 32.dp diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt index 15def728d8b3..f948d5163177 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt @@ -21,6 +21,7 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import com.android.settingslib.spa.framework.util.SystemProperties /** * The Material 3 Theme for Settings. @@ -41,4 +42,5 @@ fun SettingsTheme(content: @Composable () -> Unit) { } } -const val isSpaExpressiveEnabled = false
\ No newline at end of file +val isSpaExpressiveEnabled + by lazy { SystemProperties.getBoolean("is_expressive_design_enabled", false) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt new file mode 100644 index 000000000000..ed4936bbf8c9 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt @@ -0,0 +1,35 @@ +/* + * 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.settingslib.spa.framework.util + +import android.annotation.SuppressLint +import android.util.Log + +@SuppressLint("PrivateApi") +object SystemProperties { + private const val TAG = "SystemProperties" + + fun getBoolean(key: String, default: Boolean): Boolean = try { + val systemProperties = Class.forName("android.os.SystemProperties") + systemProperties + .getMethod("getBoolean", String::class.java, Boolean::class.java) + .invoke(systemProperties, key, default) as Boolean + } catch (e: Exception) { + Log.e(TAG, "getBoolean: $key", e) + default + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt new file mode 100644 index 000000000000..22a57554eeaf --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt @@ -0,0 +1,145 @@ +/* + * 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.settingslib.spa.widget.preference + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.AirplanemodeActive +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.theme.SettingsDimension + +@Composable +fun IntroPreference( + title: String, + descriptions: List<String>? = null, + imageVector: ImageVector? = null, +) { + IntroPreference(title = title, descriptions = descriptions, icon = { IntroIcon(imageVector) }) +} + +@Composable +fun IntroAppPreference( + title: String, + descriptions: List<String>? = null, + appIcon: @Composable (() -> Unit), +) { + IntroPreference(title = title, descriptions = descriptions, icon = { IntroAppIcon(appIcon) }) +} + +@Composable +internal fun IntroPreference( + title: String, + descriptions: List<String>?, + icon: @Composable (() -> Unit), +) { + Column( + modifier = + Modifier.fillMaxWidth() + .padding( + horizontal = SettingsDimension.paddingExtraLarge, + vertical = SettingsDimension.paddingLarge, + ), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + icon() + IntroTitle(title) + IntroDescription(descriptions) + } +} + +@Composable +private fun IntroIcon(imageVector: ImageVector?) { + if (imageVector != null) { + Box( + modifier = + Modifier.size(SettingsDimension.itemIconContainerSize) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.secondaryContainer), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = imageVector, + contentDescription = null, + modifier = Modifier.size(SettingsDimension.introIconSize), + tint = MaterialTheme.colorScheme.onSecondary, + ) + } + } +} + +@Composable +private fun IntroAppIcon(appIcon: @Composable () -> Unit) { + Box( + modifier = Modifier.size(SettingsDimension.itemIconContainerSize).clip(CircleShape), + contentAlignment = Alignment.Center, + ) { + appIcon() + } +} + +@Composable +private fun IntroTitle(title: String) { + Box(modifier = Modifier.padding(top = SettingsDimension.paddingLarge)) { + Text( + text = title, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + ) + } +} + +@Composable +private fun IntroDescription(descriptions: List<String>?) { + if (descriptions != null) { + for (description in descriptions) { + if (description.isEmpty()) continue + Text( + text = description, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = SettingsDimension.paddingExtraSmall), + ) + } + } +} + +@Preview +@Composable +private fun IntroPreferencePreview() { + IntroPreference( + title = "Preferred network type", + descriptions = listOf("Description", "Version"), + imageVector = Icons.Outlined.AirplanemodeActive, + ) +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt new file mode 100644 index 000000000000..7e619591c8a9 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt @@ -0,0 +1,152 @@ +/* + * 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.settingslib.spa.widget.preference + +import androidx.annotation.StringRes +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.toMediumWeight +import com.android.settingslib.spa.framework.util.annotatedStringResource + +/** The widget model for [TopIntroPreference] widget. */ +interface TopIntroPreferenceModel { + /** The content of this [TopIntroPreference]. */ + val text: String + + /** The text clicked to expand this [TopIntroPreference]. */ + val expandText: String + + /** The text clicked to collapse this [TopIntroPreference]. */ + val collapseText: String + + /** The text clicked to open other resources. Should be a resource Id. */ + val labelText: Int? +} + +@Composable +fun TopIntroPreference(model: TopIntroPreferenceModel) { + var expanded by remember { mutableStateOf(false) } + Column(Modifier.background(MaterialTheme.colorScheme.surfaceContainer)) { + // TopIntroPreference content. + Column( + modifier = + Modifier.padding( + horizontal = SettingsDimension.paddingExtraLarge, + vertical = SettingsDimension.paddingSmall, + ) + .animateContentSize() + ) { + Text( + text = model.text, + style = MaterialTheme.typography.bodyLarge, + maxLines = if (expanded) MAX_LINE else MIN_LINE, + ) + if (expanded) TopIntroAnnotatedText(model.labelText) + } + + // TopIntroPreference collapse bar. + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier.fillMaxWidth() + .clickable(onClick = { expanded = !expanded }) + .padding( + top = SettingsDimension.paddingSmall, + bottom = SettingsDimension.paddingLarge, + start = SettingsDimension.paddingExtraLarge, + end = SettingsDimension.paddingExtraLarge, + ), + ) { + Icon( + imageVector = + if (expanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown, + contentDescription = null, + modifier = + Modifier.size(SettingsDimension.itemIconSize) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.surfaceContainerHighest), + ) + Text( + text = if (expanded) model.collapseText else model.expandText, + modifier = Modifier.padding(start = SettingsDimension.paddingSmall), + style = MaterialTheme.typography.bodyLarge.toMediumWeight(), + color = MaterialTheme.colorScheme.onSurface, + ) + } + } +} + +@Composable +private fun TopIntroAnnotatedText(@StringRes id: Int?) { + if (id != null) { + Box( + Modifier.padding( + top = SettingsDimension.paddingExtraSmall5, + bottom = SettingsDimension.paddingExtraSmall5, + end = SettingsDimension.paddingLarge, + ) + ) { + Text( + text = annotatedStringResource(id), + style = MaterialTheme.typography.bodyLarge.toMediumWeight(), + color = MaterialTheme.colorScheme.primary, + ) + } + } +} + +@Preview +@Composable +private fun TopIntroPreferencePreview() { + TopIntroPreference( + object : TopIntroPreferenceModel { + override val text = + "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" + + "Example collapsed text area that you will not see until you expand this block." + override val expandText = "Expand" + override val collapseText = "Collapse" + override val labelText = androidx.appcompat.R.string.abc_prepend_shortcut_label + } + ) +} + +const val MIN_LINE = 2 +const val MAX_LINE = 10 diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt new file mode 100644 index 000000000000..3f2e7723c585 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt @@ -0,0 +1,132 @@ +/* + * 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.settingslib.spa.widget.preference + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.History +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Matrix +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.asComposePath +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.graphics.shapes.CornerRounding +import androidx.graphics.shapes.RoundedPolygon +import androidx.graphics.shapes.star +import androidx.graphics.shapes.toPath + +@Composable +fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: String? = null) { + val zeroStateShape = remember { + RoundedPolygon.star( + numVerticesPerRadius = 6, + innerRadius = 0.75f, + rounding = CornerRounding(0.3f) + ) + } + val clip = remember(zeroStateShape) { + RoundedPolygonShape(polygon = zeroStateShape) + } + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Box( + modifier = Modifier + .clip(clip) + .background(MaterialTheme.colorScheme.primary) + .size(160.dp) + ) { + Icon( + imageVector = icon, + modifier = Modifier + .align(Alignment.Center) + .size(72.dp), + tint = MaterialTheme.colorScheme.onPrimary, + contentDescription = null, + ) + } + if (text != null) { + Text( + text = text, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(top = 24.dp), + ) + } + if (description != null) { + Box { + Text( + text = description, + textAlign = TextAlign.Center, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } +} + +@Preview +@Composable +private fun ZeroStatePreferencePreview() { + ZeroStatePreference( + Icons.Filled.History, + "No recent search history", + "Description" + ) +} + +class RoundedPolygonShape( + private val polygon: RoundedPolygon, + private var matrix: Matrix = Matrix() +) : Shape { + private var path = Path() + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density + ): Outline { + path.rewind() + path = polygon.toPath().asComposePath() + + matrix.reset() + matrix.scale(size.width / 2f, size.height / 2f) + matrix.translate(1f, 1f) + matrix.rotateZ(30.0f) + + path.transform(matrix) + return Outline.Generic(path) + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml index fb8f878230d5..346f69bb7a42 100644 --- a/packages/SettingsLib/Spa/tests/res/values/strings.xml +++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml @@ -28,5 +28,7 @@ <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.android.com/">link</a>.</string> + <string name="test_top_intro_preference_label"><a href="https://www.android.com/">Label</a></string> + <string name="test_link"><a href="https://www.android.com/">link</a></string> </resources> diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt new file mode 100644 index 000000000000..0827fa9e0ae0 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt @@ -0,0 +1,30 @@ +/* + * 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.settingslib.spa.framework.util + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SystemPropertiesTest { + + @Test + fun getBoolean_noCrash() { + SystemProperties.getBoolean("is_expressive_design_enabled", false) + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt new file mode 100644 index 000000000000..5d801451adcb --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt @@ -0,0 +1,51 @@ +/* + * 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.settingslib.spa.widget.preference + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class IntroPreferenceTest { + @get:Rule val composeTestRule = createComposeRule() + + @Test + fun title_displayed() { + composeTestRule.setContent { IntroPreference(title = TITLE) } + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed() + } + + @Test + fun description_displayed() { + composeTestRule.setContent { IntroPreference(title = TITLE, descriptions = DESCRIPTION) } + + composeTestRule.onNodeWithText(DESCRIPTION.component1()).assertIsDisplayed() + composeTestRule.onNodeWithText(DESCRIPTION.component2()).assertIsNotDisplayed() + } + + private companion object { + const val TITLE = "Title" + val DESCRIPTION = listOf("Description", "") + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt new file mode 100644 index 000000000000..62a71d4763b3 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.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.settingslib.spa.widget.preference + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.test.R +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TopIntroPreferenceTest { + @get:Rule val composeTestRule = createComposeRule() + + @Test + fun content_collapsed_displayed() { + composeTestRule.setContent { + TopIntroPreference( + object : TopIntroPreferenceModel { + override val text = TEXT + override val expandText = EXPAND_TEXT + override val collapseText = COLLAPSE_TEXT + override val labelText = R.string.test_top_intro_preference_label + } + ) + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed() + } + + @Test + fun content_expended_displayed() { + composeTestRule.setContent { + TopIntroPreference( + object : TopIntroPreferenceModel { + override val text = TEXT + override val expandText = EXPAND_TEXT + override val collapseText = COLLAPSE_TEXT + override val labelText = R.string.test_top_intro_preference_label + } + ) + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed().performClick() + composeTestRule.onNodeWithText(COLLAPSE_TEXT).assertIsDisplayed() + composeTestRule.onNodeWithText(LABEL_TEXT).assertIsDisplayed() + } + + private companion object { + const val TEXT = "Text" + const val EXPAND_TEXT = "Expand" + const val COLLAPSE_TEXT = "Collapse" + const val LABEL_TEXT = "Label" + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt new file mode 100644 index 000000000000..99ac27c36e46 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt @@ -0,0 +1,56 @@ +/* + * 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.settingslib.spa.widget.preference + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.History +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ZeroStatePreferenceTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun title_displayed() { + composeTestRule.setContent { + ZeroStatePreference(Icons.Filled.History, TITLE) + } + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed() + } + + @Test + fun description_displayed() { + composeTestRule.setContent { + ZeroStatePreference(Icons.Filled.History, TITLE, DESCRIPTION) + } + + composeTestRule.onNodeWithText(DESCRIPTION).assertIsDisplayed() + } + + private companion object { + const val TITLE = "Title" + const val DESCRIPTION = "Description" + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml new file mode 100644 index 000000000000..4347ef29037d --- /dev/null +++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:gravity="center_vertical" + android:background="?android:attr/selectableItemBackground" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:clipToPadding="false"> + + <include layout="@layout/settingslib_expressive_preference_icon_frame"/> + + <include layout="@layout/settingslib_expressive_preference_text_frame" /> + + <include layout="@layout/settingslib_expressive_two_target_divider" /> + + <!-- Preference should place its actual preference widget here. --> + <LinearLayout + android:id="@android:id/widget_frame" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="@dimen/two_target_min_width" + android:gravity="center" + android:orientation="vertical" /> + +</LinearLayout> diff --git a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java index b125f716fe52..58ff0ce1932a 100644 --- a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java +++ b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java @@ -72,7 +72,10 @@ public class TwoTargetPreference extends Preference { } private void init(Context context) { - setLayoutResource(R.layout.preference_two_target); + int resID = SettingsThemeHelper.isExpressiveTheme(context) + ? R.layout.settingslib_expressive_preference_two_target + : R.layout.preference_two_target; + setLayoutResource(resID); mSmallIconSize = context.getResources().getDimensionPixelSize( R.dimen.two_target_pref_small_icon_size); mMediumIconSize = context.getResources().getDimensionPixelSize( diff --git a/packages/SettingsLib/res/drawable/ic_media_microphone.xml b/packages/SettingsLib/res/drawable/ic_media_microphone.xml new file mode 100644 index 000000000000..209dea515802 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_media_microphone.xml @@ -0,0 +1,25 @@ +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,560Q430,560 395,525Q360,490 360,440L360,200Q360,150 395,115Q430,80 480,80Q530,80 565,115Q600,150 600,200L600,440Q600,490 565,525Q530,560 480,560ZM480,320Q480,320 480,320Q480,320 480,320L480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320Q480,320 480,320L480,320Q480,320 480,320Q480,320 480,320ZM440,840L440,717Q336,703 268,624Q200,545 200,440L280,440Q280,523 338.5,581.5Q397,640 480,640Q563,640 621.5,581.5Q680,523 680,440L760,440Q760,545 692,624Q624,703 520,717L520,840L440,840ZM480,480Q497,480 508.5,468.5Q520,457 520,440L520,200Q520,183 508.5,171.5Q497,160 480,160Q463,160 451.5,171.5Q440,183 440,200L440,440Q440,457 451.5,468.5Q463,480 480,480Z" /> +</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java index e41126f03c60..2475c8e9dfd1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java @@ -31,6 +31,8 @@ import androidx.preference.PreferenceViewHolder; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.core.instrumentation.SettingsJankMonitor; +import com.android.settingslib.widget.SettingsThemeHelper; +import com.android.settingslib.widget.theme.R; /** * A custom preference that provides inline switch toggle. It has a mandatory field for title, and @@ -62,7 +64,9 @@ public class PrimarySwitchPreference extends RestrictedPreference { @Override protected int getSecondTargetResId() { - return androidx.preference.R.layout.preference_widget_switch_compat; + return SettingsThemeHelper.isExpressiveTheme(getContext()) + ? R.layout.settingslib_expressive_preference_switch + : androidx.preference.R.layout.preference_widget_switch_compat; } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java index 9dd2dbb41295..dae69e64934c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java @@ -132,8 +132,7 @@ public class InputMediaDevice extends MediaDevice { @VisibleForTesting int getDrawableResId() { - // TODO(b/357122624): check with UX to obtain the icon for desktop devices. - return R.drawable.ic_media_tablet; + return R.drawable.ic_media_microphone; } @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java index bc1ea6c42fa3..088d554326e7 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java @@ -18,9 +18,6 @@ package com.android.settingslib.media; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import android.content.Context; import android.media.AudioDeviceInfo; import android.platform.test.flag.junit.SetFlagsRule; @@ -64,7 +61,7 @@ public class InputMediaDeviceTest { CURRENT_VOLUME, IS_VOLUME_FIXED); assertThat(builtinMediaDevice).isNotNull(); - assertThat(builtinMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_media_tablet); + assertThat(builtinMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_media_microphone); } @Test diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt index 907c39d842ce..f5d01d70e077 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt @@ -944,26 +944,9 @@ private class AnimatedDialog( } override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - // onLaunchAnimationEnd is called by an Animator at the end of the animation, - // on a Choreographer animation tick. The following calls will move the animated - // content from the dialog overlay back to its original position, and this - // change must be reflected in the next frame given that we then sync the next - // frame of both the content and dialog ViewRoots. However, in case that content - // is rendered by Compose, whose compositions are also scheduled on a - // Choreographer frame, any state change made *right now* won't be reflected in - // the next frame given that a Choreographer frame can't schedule another and - // have it happen in the same frame. So we post the forwarded calls to - // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring - // that the move of the content back to its original window will be reflected in - // the next frame right after [onLaunchAnimationEnd] is called. - // - // TODO(b/330672236): Move this to TransitionAnimator. - dialog.context.mainExecutor.execute { - startController.onTransitionAnimationEnd(isExpandingFullyAbove) - endController.onTransitionAnimationEnd(isExpandingFullyAbove) - - onLaunchAnimationEnd() - } + startController.onTransitionAnimationEnd(isExpandingFullyAbove) + endController.onTransitionAnimationEnd(isExpandingFullyAbove) + onLaunchAnimationEnd() } override fun onTransitionAnimationProgress( diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt index fc4cf1d1e21e..859fc4e09bb2 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt @@ -379,13 +379,26 @@ class TransitionAnimator( Log.d(TAG, "Animation ended") } - // TODO(b/330672236): Post this to the main thread instead so that it does not - // flicker with Flexiglass enabled. - controller.onTransitionAnimationEnd(isExpandingFullyAbove) - transitionContainerOverlay.remove(windowBackgroundLayer) - - if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { - openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) + // onAnimationEnd is called at the end of the animation, on a Choreographer + // animation tick. During dialog launches, the following calls will move the + // animated content from the dialog overlay back to its original position, and + // this change must be reflected in the next frame given that we then sync the + // next frame of both the content and dialog ViewRoots. During SysUI activity + // launches, we will instantly collapse the shade at the end of the transition. + // However, if those are rendered by Compose, whose compositions are also + // scheduled on a Choreographer frame, any state change made *right now* won't + // be reflected in the next frame given that a Choreographer frame can't + // schedule another and have it happen in the same frame. So we post the + // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor, + // leaving this Choreographer frame, ensuring that any state change applied by + // onTransitionAnimationEnd() will be reflected in the same frame. + mainExecutor.execute { + controller.onTransitionAnimationEnd(isExpandingFullyAbove) + transitionContainerOverlay.remove(windowBackgroundLayer) + + if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { + openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) + } } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt index f8d0588c9ae6..8e6cb3fe9fb9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt @@ -22,6 +22,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -34,6 +35,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ContentScope +import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.battery.BatteryMeterViewController @@ -41,6 +43,7 @@ import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.qs.composefragment.ui.GridAnchor import com.android.systemui.qs.panels.ui.compose.EditMode import com.android.systemui.qs.panels.ui.compose.TileGrid import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel @@ -79,16 +82,11 @@ constructor( } @Composable - override fun ContentScope.Content( - modifier: Modifier, - ) { + override fun ContentScope.Content(modifier: Modifier) { val viewModel = rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() } - OverlayShade( - modifier = modifier, - onScrimClicked = viewModel::onScrimClicked, - ) { + OverlayShade(modifier = modifier, onScrimClicked = viewModel::onScrimClicked) { Column { ExpandedShadeHeader( viewModelFactory = viewModel.shadeHeaderViewModelFactory, @@ -98,40 +96,36 @@ constructor( modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding), ) - ShadeBody( - viewModel = viewModel.quickSettingsContainerViewModel, - ) + ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel) } } } } @Composable -fun ShadeBody( - viewModel: QuickSettingsContainerViewModel, -) { +fun SceneScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) { val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle() AnimatedContent( targetState = isEditing, - transitionSpec = { fadeIn(tween(500)) togetherWith fadeOut(tween(500)) } + transitionSpec = { fadeIn(tween(500)) togetherWith fadeOut(tween(500)) }, ) { editing -> if (editing) { EditMode( viewModel = viewModel.editModeViewModel, - modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding) + modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding), ) } else { QuickSettingsLayout( viewModel = viewModel, - modifier = Modifier.sysuiResTag("quick_settings_panel") + modifier = Modifier.sysuiResTag("quick_settings_panel"), ) } } } @Composable -private fun QuickSettingsLayout( +private fun SceneScope.QuickSettingsLayout( viewModel: QuickSettingsContainerViewModel, modifier: Modifier = Modifier, ) { @@ -143,15 +137,18 @@ private fun QuickSettingsLayout( BrightnessSliderContainer( viewModel = viewModel.brightnessSliderViewModel, modifier = - Modifier.fillMaxWidth() - .height(QuickSettingsShade.Dimensions.BrightnessSliderHeight), - ) - TileGrid( - viewModel = viewModel.tileGridViewModel, - modifier = - Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), - viewModel.editModeViewModel::startEditing, + Modifier.fillMaxWidth().height(QuickSettingsShade.Dimensions.BrightnessSliderHeight), ) + Box { + GridAnchor() + TileGrid( + viewModel = viewModel.tileGridViewModel, + modifier = + Modifier.fillMaxWidth() + .heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), + viewModel.editModeViewModel::startEditing, + ) + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index 6c4edf49fd83..4162891c0e0b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -152,14 +152,17 @@ private fun Modifier.panelPadding(): Modifier { /** Creates a union of [paddingValues] by using the max padding of each edge. */ @Composable private fun combinePaddings(vararg paddingValues: PaddingValues): PaddingValues { - val layoutDirection = LocalLayoutDirection.current - - return PaddingValues( - start = paddingValues.maxOfOrNull { it.calculateStartPadding(layoutDirection) } ?: 0.dp, - top = paddingValues.maxOfOrNull { it.calculateTopPadding() } ?: 0.dp, - end = paddingValues.maxOfOrNull { it.calculateEndPadding(layoutDirection) } ?: 0.dp, - bottom = paddingValues.maxOfOrNull { it.calculateBottomPadding() } ?: 0.dp, - ) + return if (paddingValues.isEmpty()) { + PaddingValues(0.dp) + } else { + val layoutDirection = LocalLayoutDirection.current + PaddingValues( + start = paddingValues.maxOf { it.calculateStartPadding(layoutDirection) }, + top = paddingValues.maxOf { it.calculateTopPadding() }, + end = paddingValues.maxOf { it.calculateEndPadding(layoutDirection) }, + bottom = paddingValues.maxOf { it.calculateBottomPadding() }, + ) + } } object OverlayShade { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 492543f215b7..af3ddfca14b6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -310,6 +310,41 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { .isEqualTo(displayId) } + @Test + fun afterSuccessfulAuthentication_focusIsNotRequested() = + testScope.runTest { + val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult) + val textInputFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested) + lockDeviceAndOpenPasswordBouncer() + + // remove focus from text field + underTest.onTextFieldFocusChanged(false) + runCurrent() + + // focus should be requested + assertThat(textInputFocusRequested).isTrue() + + // simulate text field getting focus + underTest.onTextFieldFocusChanged(true) + runCurrent() + + // focus should not be requested anymore + assertThat(textInputFocusRequested).isFalse() + + // authenticate successfully. + underTest.onPasswordInputChanged("password") + underTest.onAuthenticateKeyPressed() + runCurrent() + + assertThat(authResult).isTrue() + + // remove focus from text field + underTest.onTextFieldFocusChanged(false) + runCurrent() + // focus should not be requested again + assertThat(textInputFocusRequested).isFalse() + } + private fun TestScope.switchToScene(toScene: SceneKey) { val currentScene by collectLastValue(sceneInteractor.currentScene) val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer @@ -327,10 +362,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { switchToScene(Scenes.Bouncer) } - private suspend fun TestScope.setLockout( - isLockedOut: Boolean, - failedAttemptCount: Int = 5, - ) { + private suspend fun TestScope.setLockout(isLockedOut: Boolean, failedAttemptCount: Int = 5) { if (isLockedOut) { repeat(failedAttemptCount) { kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false) @@ -350,7 +382,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { kosmos.fakeUserRepository.selectedUser.value = SelectedUserModel( userInfo = userInfo, - selectionStatus = SelectionStatus.SELECTION_COMPLETE + selectionStatus = SelectionStatus.SELECTION_COMPLETE, ) advanceTimeBy(PasswordBouncerViewModel.DELAY_TO_FETCH_IMES) } @@ -374,7 +406,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { subtypes = List(auxiliarySubtypes + nonAuxiliarySubtypes) { InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes) - } + }, ) } @@ -383,9 +415,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { private const val WRONG_PASSWORD = "Wrong password" private val USER_INFOS = - listOf( - UserInfo(100, "First user", 0), - UserInfo(101, "Second user", 0), - ) + listOf(UserInfo(100, "First user", 0), UserInfo(101, "Second user", 0)) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt index b3ffc7159f7c..d6734e85ed77 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.communal.domain.interactor import android.app.ActivityManager.RunningTaskInfo import android.app.usage.UsageEvents import android.content.pm.UserInfo +import android.service.dream.dreamManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -28,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.activityStarter import com.android.systemui.settings.fakeUserTracker import com.android.systemui.shared.system.taskStackChangeListeners @@ -48,6 +50,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times @@ -90,6 +93,27 @@ class WidgetTrampolineInteractorTest : SysuiTestCase() { } @Test + fun testNewTaskStartsWhileOnHub_stopsDream() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + moveTaskToFront() + + argumentCaptor<OnDismissAction>().apply { + verify(activityStarter).dismissKeyguardThenExecute(capture(), anyOrNull(), any()) + + firstValue.onDismiss() + runCurrent() + + // Dream is stopped once keyguard is dismissed. + verify(kosmos.dreamManager).stopDream() + } + } + + @Test fun testNewTaskStartsAfterExitingHub_doesNotTriggerUnlock() = testScope.runTest { transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) @@ -209,7 +233,7 @@ class WidgetTrampolineInteractorTest : SysuiTestCase() { ownerName = "test", ), ), - testScope + testScope, ) runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt index 41cc953cd1c2..ba689179c33d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt @@ -81,6 +81,7 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { powerInteractor = kosmos.powerInteractor, alternateBouncerInteractor = kosmos.alternateBouncerInteractor, shadeInteractor = { kosmos.shadeInteractor }, + keyguardInteractor = { kosmos.keyguardInteractor }, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt index 7203b61ecc9f..6f20e70f84a8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt @@ -78,8 +78,6 @@ class QSFragmentComposeViewModelTest : SysuiTestCase() { Dispatchers.resetMain() } - // For now the state changes at 0.5f expansion. This will change once we implement animation - // (and this test will fail) @Test fun qsExpansionValueChanges_correctExpansionState() = with(kosmos) { @@ -87,18 +85,27 @@ class QSFragmentComposeViewModelTest : SysuiTestCase() { val expansionState by collectLastValue(underTest.expansionState) underTest.qsExpansionValue = 0f - assertThat(expansionState) - .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) + assertThat(expansionState!!.progress).isEqualTo(0f) underTest.qsExpansionValue = 0.3f - assertThat(expansionState) - .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS) - - underTest.qsExpansionValue = 0.7f - assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) + assertThat(expansionState!!.progress).isEqualTo(0.3f) underTest.qsExpansionValue = 1f - assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS) + assertThat(expansionState!!.progress).isEqualTo(1f) + } + } + + @Test + fun qsExpansionValueChanges_clamped() = + with(kosmos) { + testScope.testWithinLifecycle { + val expansionState by collectLastValue(underTest.expansionState) + + underTest.qsExpansionValue = -1f + assertThat(expansionState!!.progress).isEqualTo(0f) + + underTest.qsExpansionValue = 2f + assertThat(expansionState!!.progress).isEqualTo(1f) } } @@ -110,7 +117,7 @@ class QSFragmentComposeViewModelTest : SysuiTestCase() { testableContext.orCreateTestableResources.addOverride( R.bool.config_use_large_screen_shade_header, - true + true, ) fakeConfigurationRepository.onConfigurationChange() @@ -126,7 +133,7 @@ class QSFragmentComposeViewModelTest : SysuiTestCase() { testableContext.orCreateTestableResources.addOverride( R.bool.config_use_large_screen_shade_header, - false + false, ) fakeConfigurationRepository.onConfigurationChange() diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 32bcca1cb23d..1f4dea91db01 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -63,10 +63,12 @@ <!-- Container that is wrapped around the views on the start half of the status bar. Its width will change with the number of visible children and sub-children. It is useful when we want to know the visible bounds of the content. --> + <!-- IMPORTANT: The height of this view *must* be match_parent so that the activity + chips don't get cropped when they appear. See b/302160300 and b/366988057. --> <FrameLayout android:id="@+id/status_bar_start_side_content" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:layout_gravity="center_vertical|start" android:clipChildren="false"> @@ -75,6 +77,8 @@ <!-- The alpha of the start side is controlled by PhoneStatusBarTransitions, and the individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK and DISABLE_NOTIFICATION_ICONS, respectively --> + <!-- IMPORTANT: The height of this view *must* be match_parent so that the activity + chips don't get cropped when they appear. See b/302160300 and b/366988057. --> <LinearLayout android:id="@+id/status_bar_start_side_except_heads_up" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml index 2f2b10f8dc0c..66c54a389c8e 100644 --- a/packages/SystemUI/res/xml/media_session_collapsed.xml +++ b/packages/SystemUI/res/xml/media_session_collapsed.xml @@ -65,7 +65,7 @@ <Constraint android:id="@+id/header_title" - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginStart="@dimen/qs_media_padding" @@ -87,13 +87,11 @@ app:layout_constraintEnd_toStartOf="@id/header_artist" app:layout_constraintTop_toTopOf="@id/header_artist" app:layout_constraintBottom_toBottomOf="@id/header_artist" - app:layout_constraintVertical_bias="0" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintHorizontal_chainStyle="packed" /> + app:layout_constraintVertical_bias="0" /> <Constraint android:id="@+id/header_artist" - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/qs_media_padding" android:layout_marginBottom="@dimen/qs_media_padding" @@ -102,6 +100,8 @@ app:layout_constrainedWidth="true" app:layout_constraintStart_toEndOf="@id/media_explicit_indicator" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintHorizontal_weight="1" + app:layout_constraintHorizontal_chainStyle="spread_inside" app:layout_constraintVertical_bias="0" /> <Constraint diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml index 0140d52bd175..19cc3bc507b2 100644 --- a/packages/SystemUI/res/xml/media_session_expanded.xml +++ b/packages/SystemUI/res/xml/media_session_expanded.xml @@ -58,7 +58,7 @@ <Constraint android:id="@+id/header_title" - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/qs_media_padding" android:layout_marginEnd="@dimen/qs_media_padding" @@ -80,13 +80,11 @@ app:layout_constraintStart_toStartOf="@id/header_title" app:layout_constraintEnd_toStartOf="@id/header_artist" app:layout_constraintTop_toTopOf="@id/header_artist" - app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintHorizontal_chainStyle="packed"/> + app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top" /> <Constraint android:id="@+id/header_artist" - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/qs_media_padding" android:layout_marginBottom="@dimen/qs_media_padding" @@ -95,6 +93,8 @@ app:layout_constraintEnd_toStartOf="@id/actionPlayPause" app:layout_constraintStart_toEndOf="@id/media_explicit_indicator" app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top" + app:layout_constraintHorizontal_weight="1" + app:layout_constraintHorizontal_chainStyle="spread_inside" app:layout_constraintVertical_bias="0" /> <Constraint diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt index bb450c0b6d90..18a7739f12ab 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt @@ -83,7 +83,7 @@ constructor( /** Sets whether Udfps overlay should handle touches */ fun setHandleTouches(shouldHandle: Boolean = true) { - if (authController.isUltrasonicUdfpsSupported + if (authController.isUdfpsSupported && shouldHandle != _shouldHandleTouches.value) { fingerprintManager?.setIgnoreDisplayTouches( requestId.value, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index 873d1b3cc03d..4185aed3095c 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -86,6 +86,9 @@ sealed class AuthMethodBouncerViewModel( _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED clearInput() + if (authenticationResult == AuthenticationResult.SUCCEEDED) { + onSuccessfulAuthentication() + } } awaitCancellation() } @@ -116,6 +119,9 @@ sealed class AuthMethodBouncerViewModel( /** Returns the input entered so far. */ protected abstract fun getInput(): List<Any> + /** Invoked after a successful authentication. */ + protected open fun onSuccessfulAuthentication() = Unit + /** Perform authentication result haptics */ private fun performAuthenticationHapticFeedback(result: AuthenticationResult) { if (result == AuthenticationResult.SKIPPED) return diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index 2493cf1a101b..1427d787ea86 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -81,50 +81,59 @@ constructor( val selectedUserId: StateFlow<Int> = _selectedUserId.asStateFlow() private val requests = Channel<Request>(Channel.BUFFERED) + private var wasSuccessfullyAuthenticated = false override suspend fun onActivated(): Nothing { - coroutineScope { - launch { super.onActivated() } - launch { - requests.receiveAsFlow().collect { request -> - when (request) { - is OnImeSwitcherButtonClicked -> { - inputMethodInteractor.showInputMethodPicker( - displayId = request.displayId, - showAuxiliarySubtypes = false, - ) - } - is OnImeDismissed -> { - interactor.onImeHiddenByUser() + try { + coroutineScope { + launch { super.onActivated() } + launch { + requests.receiveAsFlow().collect { request -> + when (request) { + is OnImeSwitcherButtonClicked -> { + inputMethodInteractor.showInputMethodPicker( + displayId = request.displayId, + showAuxiliarySubtypes = false, + ) + } + is OnImeDismissed -> { + interactor.onImeHiddenByUser() + } } } } + launch { + combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> + hasInput && !hasFocus && !wasSuccessfullyAuthenticated + } + .collect { _isTextFieldFocusRequested.value = it } + } + launch { + selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it } + } + launch { + // Re-fetch the currently-enabled IMEs whenever the selected user changes, and + // whenever + // the UI subscribes to the `isImeSwitcherButtonVisible` flow. + combine( + // InputMethodManagerService sometimes takes + // some time to update its internal state when the + // selected user changes. + // As a workaround, delay fetching the IME info. + selectedUserInteractor.selectedUser.onEach { + delay(DELAY_TO_FETCH_IMES) + }, + _isImeSwitcherButtonVisible.onSubscriberAdded(), + ) { selectedUserId, _ -> + inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId) + } + .collect { _isImeSwitcherButtonVisible.value = it } + } + awaitCancellation() } - launch { - combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> - hasInput && !hasFocus - } - .collect { _isTextFieldFocusRequested.value = it } - } - launch { selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it } } - launch { - // Re-fetch the currently-enabled IMEs whenever the selected user changes, and - // whenever - // the UI subscribes to the `isImeSwitcherButtonVisible` flow. - combine( - // InputMethodManagerService sometimes takes some time to update its - // internal - // state when the selected user changes. As a workaround, delay fetching the - // IME - // info. - selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) }, - _isImeSwitcherButtonVisible.onSubscriberAdded() - ) { selectedUserId, _ -> - inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId) - } - .collect { _isImeSwitcherButtonVisible.value = it } - } - awaitCancellation() + } finally { + // reset whenever the view model is "deactivated" + wasSuccessfullyAuthenticated = false } } @@ -141,6 +150,10 @@ constructor( return _password.value.toCharArray().toList() } + override fun onSuccessfulAuthentication() { + wasSuccessfullyAuthenticated = true + } + /** Notifies that the user has changed the password input. */ fun onPasswordInputChanged(newPassword: String) { if (newPassword.isNotEmpty()) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt index f77dd587dca3..f0f7ca522c70 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt @@ -82,19 +82,26 @@ constructor( } _timers.value = - timerTargets.map { (stableId, target) -> - CommunalSmartspaceTimer( - // The view layer should have the instance based smartspaceTargetId instead of - // stable id, so that when a new instance of the timer is created, for example, - // when it is paused, the view should re-render its remote views. - smartspaceTargetId = - if (communalTimerFlickerFix()) stableId else target.smartspaceTargetId, - createdTimestampMillis = targetCreationTimes[stableId]!!, - remoteViews = target.remoteViews!!, - ) - } - - logger.d({ "Smartspace timers updated: $str1" }) { str1 = _timers.value.toString() } + timerTargets + .map { (stableId, target) -> + CommunalSmartspaceTimer( + // The view layer should have the instance based smartspaceTargetId instead + // of stable id, so that when a new instance of the timer is created, for + // example, when it is paused, the view should re-render its remote views. + smartspaceTargetId = + if (communalTimerFlickerFix()) stableId else target.smartspaceTargetId, + createdTimestampMillis = targetCreationTimes[stableId]!!, + remoteViews = target.remoteViews!!, + ) + } + .also { newVal -> + // Only log when value changes to avoid filling up the buffer. + if (newVal != _timers.value) { + logger.d({ "Smartspace timers updated: $str1" }) { + str1 = newVal.toString() + } + } + } } override fun startListening() { diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt index 7453368d0ee7..f7cd2ab89140 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt @@ -16,10 +16,13 @@ package com.android.systemui.communal.domain.interactor +import android.annotation.SuppressLint import android.app.ActivityManager +import android.app.DreamManager import com.android.systemui.common.usagestats.domain.UsageStatsInteractor import com.android.systemui.common.usagestats.shared.model.ActivityEventModel import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.LogBuffer @@ -34,10 +37,12 @@ import com.android.systemui.util.time.SystemClock 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.TimeoutCancellationException import kotlinx.coroutines.delay import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout @@ -56,6 +61,8 @@ constructor( private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val taskStackChangeListeners: TaskStackChangeListeners, private val usageStatsInteractor: UsageStatsInteractor, + private val dreamManager: DreamManager, + @Background private val bgScope: CoroutineScope, @CommunalLog logBuffer: LogBuffer, ) { private companion object { @@ -127,13 +134,21 @@ constructor( * Checks if an activity starts while on the glanceable hub and dismisses the keyguard if it * does. This can detect activities started due to broadcast trampolines from widgets. */ + @SuppressLint("MissingPermission") suspend fun waitForActivityStartAndDismissKeyguard() { if (waitForActivityStartWhileOnHub()) { logger.d("Detected trampoline, requesting unlock") activityStarter.dismissKeyguardThenExecute( - /* action= */ { false }, + /* action= */ { + // Kill the dream when launching the trampoline activity. Right now the exit + // animation stalls when tapping the battery widget, and the dream remains + // visible until the transition hits some timeouts and gets cancelled. + // TODO(b/362841648): remove once exit animation is fixed. + bgScope.launch { dreamManager.stopDream() } + false + }, /* cancel= */ null, - /* afterKeyguardGone= */ false + /* afterKeyguardGone= */ false, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt index 28db3b861278..f90f02aad892 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt @@ -70,7 +70,14 @@ constructor( ) { private val keyguardOccludedByApp: Flow<Boolean> = if (KeyguardWmStateRefactor.isEnabled) { - keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED } + combine( + keyguardTransitionInteractor.currentKeyguardState, + communalSceneInteractor.isIdleOnCommunal, + ::Pair, + ) + .map { (currentState, isIdleOnCommunal) -> + currentState == KeyguardState.OCCLUDED && !isIdleOnCommunal + } } else { combine( keyguardInteractor.isKeyguardOccluded, @@ -120,7 +127,7 @@ constructor( // On fingerprint success when the screen is on and not dreaming, go to the home screen fingerprintUnlockSuccessEvents .sample( - combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair), + combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair) ) .collect { (interactive, dreaming) -> if (interactive && !dreaming) { @@ -148,7 +155,7 @@ constructor( } }, /* cancel= */ null, - /* afterKeyguardGone */ false + /* afterKeyguardGone */ false, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt index 77c54ec1eac3..3992c3fb70b0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import com.android.app.tracing.coroutines.createCoroutineTracingContext class HomeControlsDreamService @Inject @@ -53,7 +54,7 @@ constructor( ) : DreamService() { private val serviceJob = SupervisorJob() - private val serviceScope = CoroutineScope(bgDispatcher + serviceJob) + private val serviceScope = CoroutineScope(bgDispatcher + serviceJob + createCoroutineTracingContext("HomeControlsDreamService")) private val logger = DreamLogger(logBuffer, TAG) private lateinit var taskFragmentComponent: TaskFragmentComponent private val wakeLock: WakeLock by lazy { diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt index 7e2c9f89fa67..4caf95b707b1 100644 --- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt +++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.education.dagger +import com.android.app.tracing.coroutines.createCoroutineTracingContext import com.android.systemui.CoreStartable import com.android.systemui.Flags import com.android.systemui.contextualeducation.GestureType @@ -56,7 +57,7 @@ interface ContextualEducationModule { fun provideEduDataStoreScope( @Background bgDispatcher: CoroutineDispatcher ): CoroutineScope { - return CoroutineScope(bgDispatcher + SupervisorJob()) + return CoroutineScope(bgDispatcher + SupervisorJob() + createCoroutineTracingContext("EduDataStoreScope")) } @EduClock diff --git a/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt new file mode 100644 index 000000000000..62ab18bbb738 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt @@ -0,0 +1,321 @@ +/* + * 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.grid.ui.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Placeable +import androidx.compose.ui.semantics.CollectionInfo +import androidx.compose.ui.semantics.CollectionItemInfo +import androidx.compose.ui.semantics.collectionInfo +import androidx.compose.ui.semantics.collectionItemInfo +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import kotlin.math.max + +/** + * Horizontal (non lazy) grid that supports [spans] for its elements. + * + * The elements will be laid down vertically first, and then by columns. So assuming LTR layout, it + * will be (for a span list `[2, 1, 2, 1, 1, 1, 1, 1]` and 4 rows): + * ``` + * 0 2 5 + * 0 2 6 + * 1 3 7 + * 4 + * ``` + * + * where repeated numbers show larger span. If an element doesn't fit in a column due to its span, + * it will start a new column. + * + * Elements in [spans] must be in the interval `[1, rows]` ([rows] > 0), and the composables are + * associated with the corresponding span based on their index. + * + * Due to the fact that elements are seen as a linear list that's laid out in a grid, the semantics + * represent the collection as a list of elements. + */ +@Composable +fun HorizontalSpannedGrid( + rows: Int, + columnSpacing: Dp, + rowSpacing: Dp, + spans: List<Int>, + modifier: Modifier = Modifier, + composables: @Composable BoxScope.(spanIndex: Int) -> Unit, +) { + SpannedGrid( + primarySpaces = rows, + crossAxisSpacing = rowSpacing, + mainAxisSpacing = columnSpacing, + spans = spans, + isVertical = false, + modifier = modifier, + composables = composables, + ) +} + +/** + * Horizontal (non lazy) grid that supports [spans] for its elements. + * + * The elements will be laid down horizontally first, and then by rows. So assuming LTR layout, it + * will be (for a span list `[2, 1, 2, 1, 1, 1, 1, 1]` and 4 columns): + * ``` + * 0 0 1 + * 2 2 3 4 + * 5 6 7 + * ``` + * + * where repeated numbers show larger span. If an element doesn't fit in a row due to its span, it + * will start a new row. + * + * Elements in [spans] must be in the interval `[1, columns]` ([columns] > 0), and the composables + * are associated with the corresponding span based on their index. + * + * Due to the fact that elements are seen as a linear list that's laid out in a grid, the semantics + * represent the collection as a list of elements. + */ +@Composable +fun VerticalSpannedGrid( + columns: Int, + columnSpacing: Dp, + rowSpacing: Dp, + spans: List<Int>, + modifier: Modifier = Modifier, + composables: @Composable BoxScope.(spanIndex: Int) -> Unit, +) { + SpannedGrid( + primarySpaces = columns, + crossAxisSpacing = columnSpacing, + mainAxisSpacing = rowSpacing, + spans = spans, + isVertical = true, + modifier = modifier, + composables = composables, + ) +} + +@Composable +private fun SpannedGrid( + primarySpaces: Int, + crossAxisSpacing: Dp, + mainAxisSpacing: Dp, + spans: List<Int>, + isVertical: Boolean, + modifier: Modifier = Modifier, + composables: @Composable BoxScope.(spanIndex: Int) -> Unit, +) { + val crossAxisArrangement = Arrangement.spacedBy(crossAxisSpacing) + spans.forEachIndexed { index, span -> + check(span in 1..primarySpaces) { + "Span out of bounds. Span at index $index has value of $span which is outside of the " + + "expected rance of [1, $primarySpaces]" + } + } + + if (isVertical) { + check(crossAxisSpacing >= 0.dp) { "Negative columnSpacing $crossAxisSpacing" } + check(mainAxisSpacing >= 0.dp) { "Negative rowSpacing $mainAxisSpacing" } + } else { + check(mainAxisSpacing >= 0.dp) { "Negative columnSpacing $mainAxisSpacing" } + check(crossAxisSpacing >= 0.dp) { "Negative rowSpacing $crossAxisSpacing" } + } + + val totalMainAxisGroups: Int = + remember(primarySpaces, spans) { + var currentAccumulated = 0 + var groups = 1 + spans.forEach { span -> + if (currentAccumulated + span <= primarySpaces) { + currentAccumulated += span + } else { + groups += 1 + currentAccumulated = span + } + } + groups + } + + val slotPositionsAndSizesCache = remember { + object { + var sizes = IntArray(0) + var positions = IntArray(0) + } + } + + Layout( + { + (0 until spans.size).map { spanIndex -> + Box( + Modifier.semantics { + collectionItemInfo = + if (isVertical) { + CollectionItemInfo(spanIndex, 1, 0, 1) + } else { + CollectionItemInfo(0, 1, spanIndex, 1) + } + } + ) { + composables(spanIndex) + } + } + }, + modifier.semantics { collectionInfo = CollectionInfo(spans.size, 1) }, + ) { measurables, constraints -> + check(measurables.size == spans.size) + val crossAxisSize = if (isVertical) constraints.maxWidth else constraints.maxHeight + check(crossAxisSize != Constraints.Infinity) { "Width must be constrained" } + if (slotPositionsAndSizesCache.sizes.size != primarySpaces) { + slotPositionsAndSizesCache.sizes = IntArray(primarySpaces) + slotPositionsAndSizesCache.positions = IntArray(primarySpaces) + } + calculateCellsCrossAxisSize( + crossAxisSize, + primarySpaces, + crossAxisSpacing.roundToPx(), + slotPositionsAndSizesCache.sizes, + ) + val cellSizesInCrossAxis = slotPositionsAndSizesCache.sizes + + // with is needed because of the double receiver (Density, Arrangement). + with(crossAxisArrangement) { + arrange( + crossAxisSize, + slotPositionsAndSizesCache.sizes, + LayoutDirection.Ltr, + slotPositionsAndSizesCache.positions, + ) + } + val startPositions = slotPositionsAndSizesCache.positions + + val mainAxisSpacingPx = mainAxisSpacing.roundToPx() + val mainAxisTotalGaps = (totalMainAxisGroups - 1) * mainAxisSpacingPx + val mainAxisSize = if (isVertical) constraints.maxHeight else constraints.maxWidth + val mainAxisElementConstraint = + if (mainAxisSize == Constraints.Infinity) { + Constraints.Infinity + } else { + max(0, (mainAxisSize - mainAxisTotalGaps) / totalMainAxisGroups) + } + + val mainAxisSizes = IntArray(totalMainAxisGroups) { 0 } + + var currentSlot = 0 + var mainAxisGroup = 0 + val placeables = + measurables.mapIndexed { index, measurable -> + val span = spans[index] + if (currentSlot + span > primarySpaces) { + currentSlot = 0 + mainAxisGroup += 1 + } + val crossAxisConstraint = + calculateWidth(cellSizesInCrossAxis, startPositions, currentSlot, span) + PlaceResult( + measurable.measure( + makeConstraint( + isVertical, + mainAxisElementConstraint, + crossAxisConstraint, + ) + ), + currentSlot, + mainAxisGroup, + ) + .also { + currentSlot += span + mainAxisSizes[mainAxisGroup] = + max( + mainAxisSizes[mainAxisGroup], + if (isVertical) it.placeable.height else it.placeable.width, + ) + } + } + + val mainAxisTotalSize = mainAxisTotalGaps + mainAxisSizes.sum() + val mainAxisStartingPoints = + mainAxisSizes.runningFold(0) { acc, value -> acc + value + mainAxisSpacingPx } + val height = if (isVertical) mainAxisTotalSize else crossAxisSize + val width = if (isVertical) crossAxisSize else mainAxisTotalSize + + layout(width, height) { + placeables.forEach { (placeable, slot, mainAxisGroup) -> + val x = + if (isVertical) { + startPositions[slot] + } else { + mainAxisStartingPoints[mainAxisGroup] + } + val y = + if (isVertical) { + mainAxisStartingPoints[mainAxisGroup] + } else { + startPositions[slot] + } + placeable.placeRelative(x, y) + } + } + } +} + +fun makeConstraint(isVertical: Boolean, mainAxisSize: Int, crossAxisSize: Int): Constraints { + return if (isVertical) { + Constraints(maxHeight = mainAxisSize, minWidth = crossAxisSize, maxWidth = crossAxisSize) + } else { + Constraints(maxWidth = mainAxisSize, minHeight = crossAxisSize, maxHeight = crossAxisSize) + } +} + +private fun calculateWidth(sizes: IntArray, positions: IntArray, startSlot: Int, span: Int): Int { + val crossAxisSize = + if (span == 1) { + sizes[startSlot] + } else { + val endSlot = startSlot + span - 1 + positions[endSlot] + sizes[endSlot] - positions[startSlot] + } + .coerceAtLeast(0) + return crossAxisSize +} + +private fun calculateCellsCrossAxisSize( + gridSize: Int, + slotCount: Int, + spacingPx: Int, + outArray: IntArray, +) { + check(outArray.size == slotCount) + val gridSizeWithoutSpacing = gridSize - spacingPx * (slotCount - 1) + val slotSize = gridSizeWithoutSpacing / slotCount + val remainingPixels = gridSizeWithoutSpacing % slotCount + outArray.indices.forEach { index -> + outArray[index] = slotSize + if (index < remainingPixels) 1 else 0 + } +} + +private data class PlaceResult( + val placeable: Placeable, + val slotIndex: Int, + val mainAxisGroup: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt index b27135674fb1..f32c94b2bc01 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt @@ -22,6 +22,7 @@ import android.graphics.PorterDuffColorFilter import androidx.annotation.RawRes import androidx.annotation.StringRes import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.core.LinearEasing @@ -40,6 +41,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -67,21 +69,22 @@ import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionSta enum class TutorialActionState { NOT_STARTED, IN_PROGRESS, - FINISHED + FINISHED, } @Composable fun ActionTutorialContent( actionState: TutorialActionState, onDoneButtonClicked: () -> Unit, - config: TutorialScreenConfig + config: TutorialScreenConfig, ) { Column( verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize() .background(config.colors.background) - .padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp) + .safeDrawingPadding() + .padding(start = 48.dp, top = 100.dp, end = 48.dp, bottom = 8.dp), ) { Row(modifier = Modifier.fillMaxWidth().weight(1f)) { TutorialDescription( @@ -92,16 +95,18 @@ fun ActionTutorialContent( bodyTextId = if (actionState == FINISHED) config.strings.bodySuccessResId else config.strings.bodyResId, - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f), ) Spacer(modifier = Modifier.width(76.dp)) TutorialAnimation( actionState, config, - modifier = Modifier.weight(1f).padding(top = 8.dp) + modifier = Modifier.weight(1f).padding(top = 8.dp), ) } - DoneButton(onDoneButtonClicked = onDoneButtonClicked) + AnimatedVisibility(visible = actionState == FINISHED, enter = fadeIn()) { + DoneButton(onDoneButtonClicked = onDoneButtonClicked) + } } } @@ -110,19 +115,19 @@ fun TutorialDescription( @StringRes titleTextId: Int, titleColor: Color, @StringRes bodyTextId: Int, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Column(verticalArrangement = Arrangement.Top, modifier = modifier) { Text( text = stringResource(id = titleTextId), style = MaterialTheme.typography.displayLarge, - color = titleColor + color = titleColor, ) Spacer(modifier = Modifier.height(16.dp)) Text( text = stringResource(id = bodyTextId), style = MaterialTheme.typography.bodyLarge, - color = Color.White + color = Color.White, ) } } @@ -131,7 +136,7 @@ fun TutorialDescription( fun TutorialAnimation( actionState: TutorialActionState, config: TutorialScreenConfig, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Box(modifier = modifier.fillMaxWidth()) { AnimatedContent( @@ -152,18 +157,18 @@ fun TutorialAnimation( // state which shares initial animation frame with both FINISHED and NOT_STARTED EnterTransition.None togetherWith ExitTransition.None } - } + }, ) { state -> when (state) { NOT_STARTED -> EducationAnimation( config.animations.educationResId, - config.colors.animationColors + config.colors.animationColors, ) IN_PROGRESS -> FrozenSuccessAnimation( config.animations.successResId, - config.colors.animationColors + config.colors.animationColors, ) FINISHED -> SuccessAnimation(config.animations.successResId, config.colors.animationColors) @@ -175,7 +180,7 @@ fun TutorialAnimation( @Composable private fun FrozenSuccessAnimation( @RawRes successAnimationId: Int, - animationProperties: LottieDynamicProperties + animationProperties: LottieDynamicProperties, ) { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId)) LottieAnimation( @@ -188,7 +193,7 @@ private fun FrozenSuccessAnimation( @Composable private fun EducationAnimation( @RawRes educationAnimationId: Int, - animationProperties: LottieDynamicProperties + animationProperties: LottieDynamicProperties, ) { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(educationAnimationId)) val progress by @@ -203,7 +208,7 @@ private fun EducationAnimation( @Composable private fun SuccessAnimation( @RawRes successAnimationId: Int, - animationProperties: LottieDynamicProperties + animationProperties: LottieDynamicProperties, ) { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId)) val progress by animateLottieCompositionAsState(composition, iterations = 1) @@ -217,13 +222,13 @@ private fun SuccessAnimation( @Composable fun rememberColorFilterProperty( layerName: String, - color: Color + color: Color, ): LottieDynamicProperty<ColorFilter> { return rememberLottieDynamicProperty( LottieProperty.COLOR_FILTER, value = PorterDuffColorFilter(color.toArgb(), PorterDuff.Mode.SRC_ATOP), // "**" below means match zero or more layers, so ** layerName ** means find layer with that // name at any depth - keyPath = arrayOf("**", layerName, "**") + keyPath = arrayOf("**", layerName, "**"), ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt index eb9b07add12d..ea80911335fa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt @@ -65,6 +65,7 @@ constructor( powerInteractor: PowerInteractor, alternateBouncerInteractor: AlternateBouncerInteractor, shadeInteractor: Lazy<ShadeInteractor>, + keyguardInteractor: Lazy<KeyguardInteractor>, ) { val dismissAction: Flow<DismissAction> = repository.dismissAction @@ -111,9 +112,9 @@ constructor( } else if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) { combine( shadeInteractor.get().isAnyExpanded, - deviceUnlockedInteractor.get().deviceUnlockStatus, - ) { isAnyExpanded, deviceUnlockStatus -> - isAnyExpanded && deviceUnlockStatus.isUnlocked + keyguardInteractor.get().isKeyguardDismissible, + ) { isAnyExpanded, keyguardDismissible -> + isAnyExpanded && keyguardDismissible } } else { flow { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 0b8f7417a49d..cef9a4eaf2bd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.preview +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.app.WallpaperColors import android.content.BroadcastReceiver import android.content.Context @@ -187,7 +188,7 @@ constructor( private var themeStyle: Style? = null init { - coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job()) + coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job() + createCoroutineTracingContext("KeyguardPreviewRenderer")) disposables += DisposableHandle { coroutineScope.cancel() } clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData()) diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt index c2b5d98699b4..555969859a1f 100644 --- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt +++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt @@ -26,7 +26,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.lifecycleScope import com.android.app.tracing.coroutines.createCoroutineTracingContext -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.traceCoroutine import com.android.systemui.Flags.coroutineTracing import com.android.systemui.util.Assert import com.android.systemui.util.Compile @@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.launch /** * Runs the given [block] every time the [View] becomes attached (or immediately after calling this @@ -137,7 +138,7 @@ private fun createLifecycleOwnerAndRun( ): ViewLifecycleOwner { return ViewLifecycleOwner(view).apply { onCreate() - lifecycleScope.launch(nameForTrace, coroutineContext) { block(view) } + lifecycleScope.launch(coroutineContext) { traceCoroutine(nameForTrace) { block(view) } } } } @@ -367,7 +368,8 @@ private val ViewTreeObserver.isWindowVisible * an extension function, and plumbing dagger-injected instances for static usage has little * benefit. */ -private val MAIN_DISPATCHER_SINGLETON = Dispatchers.Main + createCoroutineTracingContext() +private val MAIN_DISPATCHER_SINGLETON = + Dispatchers.Main + createCoroutineTracingContext("RepeatWhenAttached") private const val DEFAULT_TRACE_NAME = "repeatWhenAttached" private const val CURRENT_CLASS_NAME = "com.android.systemui.lifecycle.RepeatWhenAttachedKt" private const val JAVA_ADAPTER_CLASS_NAME = "com.android.systemui.util.kotlin.JavaAdapterKt" diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index 8bec46abd504..70ca82492775 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -545,6 +545,7 @@ public class MediaControlPanel { /** Bind this player view based on the data given. */ public void bindPlayer(@NonNull MediaData data, String key) { + SceneContainerFlag.assertInLegacyMode(); if (mMediaViewHolder == null) { return; } @@ -638,10 +639,7 @@ public class MediaControlPanel { // to something which might impact the measurement // State refresh interferes with the translation animation, only run it if it's not running. if (!mMetadataAnimationHandler.isRunning()) { - // Don't refresh in scene framework, because it will calculate with invalid layout sizes - if (!SceneContainerFlag.isEnabled()) { - mMediaViewController.refreshState(); - } + mMediaViewController.refreshState(); } if (shouldPlayTurbulenceNoise()) { @@ -907,11 +905,6 @@ public class MediaControlPanel { // Capture width & height from views in foreground for artwork scaling in background int width = mMediaViewHolder.getAlbumView().getMeasuredWidth(); int height = mMediaViewHolder.getAlbumView().getMeasuredHeight(); - if (SceneContainerFlag.isEnabled() && (width <= 0 || height <= 0)) { - // TODO(b/312714128): ensure we have a valid size before setting background - width = mMediaViewController.getWidthInSceneContainerPx(); - height = mMediaViewController.getHeightInSceneContainerPx(); - } final int finalWidth = width; final int finalHeight = height; diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index 64402052c984..544dbddeb3f0 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -16,6 +16,7 @@ package com.android.systemui.mediaprojection.appselector +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.app.Activity import android.content.ComponentName import android.content.Context @@ -133,7 +134,7 @@ interface MediaProjectionAppSelectorModule { @MediaProjectionAppSelector @MediaProjectionAppSelectorScope fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope = - CoroutineScope(applicationScope.coroutineContext + SupervisorJob()) + CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("MediaProjectionAppSelectorScope")) } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index b3c697e06a92..1216a8879751 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -551,6 +551,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } @Override + public void appTransitionStarting(int displayId, long startTime, long duration, + boolean forced) { + appTransitionPending(false); + } + + @Override public void appTransitionCancelled(int displayId) { appTransitionPending(false); } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index 44460ed0716d..eff5fc0db761 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -75,7 +75,9 @@ constructor( * [NoteTaskController], ensure custom actions can be triggered (i.e., keyboard shortcut). */ private fun initializeHandleSystemKey() { - commandQueue.addCallback(callbacks) + if (!useKeyGestureEventHandler()) { + commandQueue.addCallback(callbacks) + } } /** @@ -130,6 +132,11 @@ constructor( InputManager.KeyGestureEventHandler { override fun handleSystemKey(key: KeyEvent) { + if (useKeyGestureEventHandler()) { + throw IllegalStateException( + "handleSystemKey must not be used when KeyGestureEventHandler is used" + ) + } key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask) } @@ -151,13 +158,13 @@ constructor( override fun handleKeyGestureEvent( event: KeyGestureEvent, - focusedToken: IBinder? + focusedToken: IBinder?, ): Boolean { return this@NoteTaskInitializer.handleKeyGestureEvent(event) } override fun isKeyGestureSupported(gestureType: Int): Boolean { - return this@NoteTaskInitializer.isKeyGestureSupported(gestureType); + return this@NoteTaskInitializer.isKeyGestureSupported(gestureType) } } @@ -209,8 +216,20 @@ constructor( "handleKeyGestureEvent: Received OPEN_NOTES gesture event from keycodes: " + event.keycodes.contentToString() } - backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) } - return true + if ( + event.keycodes.contains(KEYCODE_N) && + event.hasModifiers(KeyEvent.META_CTRL_ON or KeyEvent.META_META_ON) + ) { + debugLog { "Note task triggered by keyboard shortcut" } + backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) } + return true + } + if (event.keycodes.size == 1 && event.keycodes[0] == KEYCODE_STYLUS_BUTTON_TAIL) { + debugLog { "Note task triggered by stylus tail button" } + backgroundExecutor.execute { controller.showNoteTask(TAIL_BUTTON) } + return true + } + return false } private fun isKeyGestureSupported(gestureType: Int): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index af167d4f6918..c174038aafe4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -26,7 +26,6 @@ import android.view.ViewGroup import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner -import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -38,10 +37,14 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot @@ -51,11 +54,18 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.CustomAccessibilityAction import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import com.android.compose.animation.scene.MutableSceneTransitionLayoutState +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.height import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf @@ -70,11 +80,17 @@ import com.android.systemui.media.dagger.MediaModule.QS_PANEL import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL import com.android.systemui.plugins.qs.QS import com.android.systemui.plugins.qs.QSContainerController +import com.android.systemui.qs.composefragment.SceneKeys.QuickQuickSettings +import com.android.systemui.qs.composefragment.SceneKeys.QuickSettings +import com.android.systemui.qs.composefragment.SceneKeys.toIdleSceneKey import com.android.systemui.qs.composefragment.ui.notificationScrimClip +import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.footer.ui.compose.FooterActions import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings +import com.android.systemui.qs.shared.ui.ElementKeys +import com.android.systemui.qs.ui.composable.QuickSettingsShade import com.android.systemui.qs.ui.composable.QuickSettingsTheme import com.android.systemui.qs.ui.composable.ShadeBody import com.android.systemui.res.R @@ -86,11 +102,13 @@ import java.io.PrintWriter import java.util.function.Consumer import javax.inject.Inject import javax.inject.Named +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @SuppressLint("ValidFragment") @@ -166,33 +184,48 @@ constructor( setContent { PlatformTheme { val visible by viewModel.qsVisible.collectAsStateWithLifecycle() - val qsState by viewModel.expansionState.collectAsStateWithLifecycle() AnimatedVisibility( visible = visible, modifier = - Modifier.windowInsetsPadding(WindowInsets.navigationBars).thenIf( - notificationScrimClippingParams.isEnabled - ) { - Modifier.notificationScrimClip( - notificationScrimClippingParams.leftInset, - notificationScrimClippingParams.top, - notificationScrimClippingParams.rightInset, - notificationScrimClippingParams.bottom, - notificationScrimClippingParams.radius, - ) - }, - ) { - AnimatedContent(targetState = qsState) { - when (it) { - QSFragmentComposeViewModel.QSExpansionState.QQS -> { - QuickQuickSettingsElement() - } - QSFragmentComposeViewModel.QSExpansionState.QS -> { - QuickSettingsElement() + Modifier.windowInsetsPadding(WindowInsets.navigationBars) + .thenIf(notificationScrimClippingParams.isEnabled) { + Modifier.notificationScrimClip( + notificationScrimClippingParams.leftInset, + notificationScrimClippingParams.top, + notificationScrimClippingParams.rightInset, + notificationScrimClippingParams.bottom, + notificationScrimClippingParams.radius, + ) } - else -> {} - } + .graphicsLayer { elevation = 4.dp.toPx() }, + ) { + val sceneState = remember { + MutableSceneTransitionLayoutState( + viewModel.expansionState.value.toIdleSceneKey(), + transitions = + transitions { + from(QuickQuickSettings, QuickSettings) { + quickQuickSettingsToQuickSettings() + } + }, + ) + } + + LaunchedEffect(Unit) { + synchronizeQsState( + sceneState, + viewModel.expansionState.map { it.progress }, + ) + } + + SceneTransitionLayout( + state = sceneState, + modifier = Modifier.fillMaxSize(), + ) { + scene(QuickSettings) { QuickSettingsElement() } + + scene(QuickQuickSettings) { QuickQuickSettingsElement() } } } } @@ -420,7 +453,7 @@ constructor( } @Composable - private fun QuickQuickSettingsElement() { + private fun SceneScope.QuickQuickSettingsElement() { val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle() val bottomPadding = dimensionResource(id = R.dimen.qqs_layout_padding_bottom) DisposableEffect(Unit) { @@ -450,8 +483,15 @@ constructor( viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel, modifier = Modifier.collapseExpandSemanticAction( - stringResource(id = R.string.accessibility_quick_settings_expand) - ), + stringResource( + id = R.string.accessibility_quick_settings_expand + ) + ) + .padding( + horizontal = { + QuickSettingsShade.Dimensions.Padding.roundToPx() + } + ), ) } } @@ -460,7 +500,7 @@ constructor( } @Composable - private fun QuickSettingsElement() { + private fun SceneScope.QuickSettingsElement() { val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle() val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top) Column( @@ -471,7 +511,10 @@ constructor( ) { val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle() if (qsEnabled) { - Box(modifier = Modifier.fillMaxSize().weight(1f)) { + Box( + modifier = + Modifier.element(ElementKeys.QuickSettingsContent).fillMaxSize().weight(1f) + ) { Column { Spacer( modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() } @@ -483,7 +526,9 @@ constructor( FooterActions( viewModel = viewModel.footerActionsViewModel, qsVisibilityLifecycleOwner = this@QSFragmentCompose, - modifier = Modifier.sysuiResTag("qs_footer_actions"), + modifier = + Modifier.sysuiResTag("qs_footer_actions") + .element(ElementKeys.FooterActions), ) } } @@ -590,3 +635,85 @@ private val instanceProvider = return currentId++ } } + +object SceneKeys { + val QuickQuickSettings = SceneKey("QuickQuickSettingsScene") + val QuickSettings = SceneKey("QuickSettingsScene") + + fun QSFragmentComposeViewModel.QSExpansionState.toIdleSceneKey(): SceneKey { + return when { + progress < 0.5f -> QuickQuickSettings + else -> QuickSettings + } + } +} + +suspend fun synchronizeQsState(state: MutableSceneTransitionLayoutState, expansion: Flow<Float>) { + coroutineScope { + val animationScope = this + + var currentTransition: ExpansionTransition? = null + + fun snapTo(scene: SceneKey) { + state.snapToScene(scene) + currentTransition = null + } + + expansion.collectLatest { progress -> + when (progress) { + 0f -> snapTo(QuickQuickSettings) + 1f -> snapTo(QuickSettings) + else -> { + val transition = currentTransition + if (transition != null) { + transition.progress = progress + return@collectLatest + } + + val newTransition = + ExpansionTransition(progress).also { currentTransition = it } + state.startTransitionImmediately( + animationScope = animationScope, + transition = newTransition, + ) + } + } + } + } +} + +private class ExpansionTransition(currentProgress: Float) : + TransitionState.Transition.ChangeScene( + fromScene = QuickQuickSettings, + toScene = QuickSettings, + ) { + override val currentScene: SceneKey + get() { + // This should return the logical scene. If the QS STLState is only driven by + // synchronizeQSState() then it probably does not matter which one we return, this is + // only used to compute the current user actions of a STL. + return QuickQuickSettings + } + + override var progress: Float by mutableFloatStateOf(currentProgress) + + override val progressVelocity: Float + get() = 0f + + override val isInitiatedByUserInput: Boolean + get() = true + + override val isUserInputOngoing: Boolean + get() = true + + private val finishCompletable = CompletableDeferred<Unit>() + + override suspend fun run() { + // This transition runs until it is interrupted by another one. + finishCompletable.await() + } + + override fun freezeAndAnimateToCurrentState() { + finishCompletable.complete(Unit) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt new file mode 100644 index 000000000000..1514986d16d9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/FromQuickQuickSettingsToQuickSettings.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.composefragment.ui + +import com.android.compose.animation.scene.TransitionBuilder +import com.android.systemui.qs.shared.ui.ElementKeys + +fun TransitionBuilder.quickQuickSettingsToQuickSettings() { + + fractionRange(start = 0.5f) { fade(ElementKeys.QuickSettingsContent) } + + fractionRange(start = 0.9f) { fade(ElementKeys.FooterActions) } + + anchoredTranslate(ElementKeys.QuickSettingsContent, ElementKeys.GridAnchor) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/GridAnchor.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/GridAnchor.kt new file mode 100644 index 000000000000..f0f46d33b83d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/GridAnchor.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.composefragment.ui + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.SceneScope +import com.android.systemui.qs.shared.ui.ElementKeys + +/** + * This composable is used at the start of the tiles in QQS and QS to anchor the expansion and be + * able to have relative anchor translation of elements that appear in QS. + */ +@Composable +fun SceneScope.GridAnchor(modifier: Modifier = Modifier) { + // The size of this anchor does not matter, as the tiles don't change size on expansion. + Spacer(modifier.element(ElementKeys.GridAnchor)) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt index 7ab11d22ee49..7300ee1053ff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt @@ -147,7 +147,7 @@ constructor( .stateIn( lifecycleScope, SharingStarted.WhileSubscribed(), - disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled() + disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled(), ) private val _showCollapsedOnKeyguard = MutableStateFlow(false) @@ -213,19 +213,11 @@ constructor( } val expansionState: StateFlow<QSExpansionState> = - combine( - _stackScrollerOverscrolling, - _qsExpanded, - _qsExpansion, - ) { args: Array<Any> -> + combine(_stackScrollerOverscrolling, _qsExpanded, _qsExpansion) { args: Array<Any> -> val expansion = args[2] as Float - if (expansion > 0.5f) { - QSExpansionState.QS - } else { - QSExpansionState.QQS - } + QSExpansionState(expansion.coerceIn(0f, 1f)) } - .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState.QQS) + .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState(0f)) /** * Accessibility action for collapsing/expanding QS. The provided runnable is responsible for @@ -262,13 +254,6 @@ constructor( fun create(lifecycleScope: LifecycleCoroutineScope): QSFragmentComposeViewModel } - sealed interface QSExpansionState { - data object QQS : QSExpansionState - - data object QS : QSExpansionState - - @JvmInline value class Expanding(val progress: Float) : QSExpansionState - - @JvmInline value class Collapsing(val progress: Float) : QSExpansionState - } + // In the future, this will have other relevant elements like squishiness. + data class QSExpansionState(@FloatRange(0.0, 1.0) val progress: Float) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt index fd276c2dd220..0c02b400646c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.android.compose.animation.scene.SceneScope import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.TileRow import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel @@ -27,7 +28,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec /** A layout of tiles, indicating how they should be composed when showing in QS or in edit mode. */ interface GridLayout { @Composable - fun TileGrid( + fun SceneScope.TileGrid( tiles: List<TileViewModel>, modifier: Modifier, editModeStart: () -> Unit, @@ -66,7 +67,7 @@ interface PaginatableGridLayout : GridLayout { */ fun splitInRows( tiles: List<SizedTile<TileViewModel>>, - columns: Int + columns: Int, ): List<List<SizedTile<TileViewModel>>> { val row = TileRow<TileViewModel>(columns) diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index 08a56bf29f66..083f529a21da 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.SceneScope import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight @@ -55,7 +56,7 @@ constructor( @PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout, ) : GridLayout by delegateGridLayout { @Composable - override fun TileGrid( + override fun SceneScope.TileGrid( tiles: List<TileViewModel>, modifier: Modifier, editModeStart: () -> Unit, @@ -85,16 +86,16 @@ constructor( ) { val page = pages[it] - delegateGridLayout.TileGrid(tiles = page, modifier = Modifier, editModeStart = {}) + with(delegateGridLayout) { + TileGrid(tiles = page, modifier = Modifier, editModeStart = {}) + } } - Box( - modifier = Modifier.height(FooterHeight).fillMaxWidth(), - ) { + Box(modifier = Modifier.height(FooterHeight).fillMaxWidth()) { PagerDots( pagerState = pagerState, activeColor = MaterialTheme.colorScheme.primary, nonActiveColor = MaterialTheme.colorScheme.surfaceVariant, - modifier = Modifier.align(Alignment.Center) + modifier = Modifier.align(Alignment.Center), ) CompositionLocalProvider(value = LocalContentColor provides Color.White) { IconButton( @@ -103,7 +104,7 @@ constructor( ) { Icon( imageVector = Icons.Default.Edit, - contentDescription = stringResource(id = R.string.qs_edit) + contentDescription = stringResource(id = R.string.qs_edit), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index f4acbec1063c..8998a7f5d815 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -16,21 +16,28 @@ package com.android.systemui.qs.panels.ui.compose -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.SceneScope import com.android.systemui.compose.modifiers.sysuiResTag +import com.android.systemui.grid.ui.compose.VerticalSpannedGrid +import com.android.systemui.qs.composefragment.ui.GridAnchor import com.android.systemui.qs.panels.ui.compose.infinitegrid.Tile -import com.android.systemui.qs.panels.ui.compose.infinitegrid.TileLazyGrid import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel +import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey +import com.android.systemui.res.R @Composable -fun QuickQuickSettings(viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier) { +fun SceneScope.QuickQuickSettings( + viewModel: QuickQuickSettingsViewModel, + modifier: Modifier = Modifier, +) { val sizedTiles by viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList()) val tiles = sizedTiles.fastMap { it.tile } @@ -41,20 +48,20 @@ fun QuickQuickSettings(viewModel: QuickQuickSettingsViewModel, modifier: Modifie onDispose { tiles.forEach { it.stopListening(token) } } } val columns by viewModel.columns.collectAsStateWithLifecycle() - - TileLazyGrid( - modifier = modifier.sysuiResTag("qqs_tile_layout"), - columns = GridCells.Fixed(columns), - ) { - items( - sizedTiles.size, - key = { index -> sizedTiles[index].tile.spec.spec }, - span = { index -> GridItemSpan(sizedTiles[index].width) }, - ) { index -> + Box(modifier = modifier) { + GridAnchor() + VerticalSpannedGrid( + columns = columns, + columnSpacing = dimensionResource(R.dimen.qs_tile_margin_horizontal), + rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical), + spans = sizedTiles.fastMap { it.width }, + modifier = Modifier.sysuiResTag("qqs_tile_layout"), + ) { spanIndex -> + val it = sizedTiles[spanIndex] Tile( - tile = sizedTiles[index].tile, - iconOnly = sizedTiles[index].isIcon, - modifier = Modifier, + tile = it.tile, + iconOnly = it.isIcon, + modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt index 8c57d41b2123..1a5297b10e37 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt @@ -20,16 +20,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.SceneScope import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel @Composable -fun TileGrid( +fun SceneScope.TileGrid( viewModel: TileGridViewModel, modifier: Modifier = Modifier, - editModeStart: () -> Unit + editModeStart: () -> Unit, ) { val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList()) - gridLayout.TileGrid(tiles, modifier, editModeStart) + with(gridLayout) { TileGrid(tiles, modifier, editModeStart) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 4946c0194d34..8a9606545fc3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -16,15 +16,17 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout import com.android.systemui.qs.panels.ui.compose.rememberEditListState @@ -33,6 +35,8 @@ import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey +import com.android.systemui.res.R import javax.inject.Inject @SysUISingleton @@ -44,7 +48,7 @@ constructor( ) : PaginatableGridLayout { @Composable - override fun TileGrid( + override fun SceneScope.TileGrid( tiles: List<TileViewModel>, modifier: Modifier, editModeStart: () -> Unit, @@ -57,15 +61,18 @@ constructor( val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) } - TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { - items(sizedTiles.size, span = { index -> GridItemSpan(sizedTiles[index].width) }) { - index -> - Tile( - tile = sizedTiles[index].tile, - iconOnly = iconTilesViewModel.isIconTile(sizedTiles[index].tile.spec), - modifier = Modifier, - ) - } + VerticalSpannedGrid( + columns = columns, + columnSpacing = dimensionResource(R.dimen.qs_tile_margin_horizontal), + rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical), + spans = sizedTiles.fastMap { it.width }, + ) { spanIndex -> + val it = sizedTiles[spanIndex] + Tile( + tile = it.tile, + iconOnly = iconTilesViewModel.isIconTile(it.tile.spec), + modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt b/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt new file mode 100644 index 000000000000..625459d1c6fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/shared/ui/ElementKeys.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.shared.ui + +import com.android.compose.animation.scene.ElementKey +import com.android.systemui.qs.pipeline.shared.TileSpec + +/** Element keys to be used by the compose implementation of QS for animations. */ +object ElementKeys { + val QuickSettingsContent = ElementKey("QuickSettingsContent") + val GridAnchor = ElementKey("QuickSettingsGridAnchor") + val FooterActions = ElementKey("FooterActions") + + class TileElementKey(spec: TileSpec, val position: Int) : ElementKey(spec.spec, spec.spec) + + fun TileSpec.toElementKey(positionInGrid: Int) = TileElementKey(this, positionInGrid) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt index d0437a7210f1..b8f4ab40bb1d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.base.viewmodel +import com.android.app.tracing.coroutines.createCoroutineTracingContext import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -27,5 +28,5 @@ class QSTileCoroutineScopeFactory constructor(@Application private val applicationScope: CoroutineScope) { fun create(): CoroutineScope = - CoroutineScope(applicationScope.coroutineContext + SupervisorJob()) + CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("QSTileScope")) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt index 246fe3883e19..ae56c2aad4e9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.qs.tiles.dialog +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.util.Log import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.DialogCuj @@ -62,7 +63,7 @@ constructor( } return } else { - coroutineScope = CoroutineScope(bgDispatcher) + coroutineScope = CoroutineScope(bgDispatcher + createCoroutineTracingContext("InternetDialogScope")) dialog = dialogFactory .create(aboveStatusBar, canConfigMobileData, canConfigWifi, coroutineScope) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt index cca947ff7e77..ac75932b8fee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.impl.location.domain.interactor +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.content.Intent import android.provider.Settings import com.android.systemui.dagger.qualifiers.Application @@ -52,7 +53,7 @@ constructor( val wasEnabled: Boolean = input.data.isEnabled if (keyguardController.isMethodSecure() && keyguardController.isShowing()) { activityStarter.postQSRunnableDismissingKeyguard { - CoroutineScope(applicationScope.coroutineContext).launch { + CoroutineScope(applicationScope.coroutineContext + createCoroutineTracingContext("LocationTileScope")).launch { locationController.setLocationEnabled(!wasEnabled) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt index b25c61cba2b7..468e180a6e41 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.impl.saver.domain +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.content.Context import android.content.DialogInterface import android.content.SharedPreferences @@ -44,7 +45,7 @@ class DataSaverDialogDelegate( setTitle(R.string.data_saver_enable_title) setMessage(R.string.data_saver_description) setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ -> - CoroutineScope(backgroundContext).launch { + CoroutineScope(backgroundContext + createCoroutineTracingContext("DataSaverDialogScope")).launch { dataSaverController.setDataSaverEnabled(true) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index b7e2cf23e3a8..286cac10fa05 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -193,22 +193,16 @@ constructor( // We are in a session if either Shade or QuickSettings is on the back stack .map { backStack -> backStack.asIterable().any { + // TODO(b/356596436): Include overlays in the back stack as well. it == Scenes.Shade || it == Scenes.QuickSettings } } .distinctUntilChanged(), - sceneInteractor.transitionState - .mapNotNull { state -> - // We are also in a session if either Shade or QuickSettings is the - // current scene - when (state) { - is ObservableTransitionState.Idle -> state.currentScene - is ObservableTransitionState.Transition -> state.fromContent - }.let { it == Scenes.Shade || it == Scenes.QuickSettings } - } - .distinctUntilChanged(), - ) { inBackStack, isCurrentScene -> - inBackStack || isCurrentScene + // We are also in a session if either Notifications Shade or QuickSettings Shade + // is currently shown (whether idle or animating). + shadeInteractor.isAnyExpanded, + ) { inBackStack, isShadeShown -> + inBackStack || isShadeShown } // Once a session has ended, clear the session storage. .filter { inSession -> !inSession } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java index f69b0cb630d3..7724abd4aaac 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java @@ -505,8 +505,8 @@ public class LegacyScreenshotController implements InteractiveScreenshotHandler return; } // delay starting scroll capture to make sure scrim is up before the app moves - mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, - mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner)); + mViewProxy.prepareScrollingTransition(response, newScreenshot, mScreenshotTakenInPortrait, + () -> executeBatchScrollCapture(response, owner)); } private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java deleted file mode 100644 index fe58bc9f34a9..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ /dev/null @@ -1,663 +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 com.android.systemui.screenshot; - -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; - -import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM; -import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK; -import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT; -import static com.android.systemui.screenshot.LogConfig.DEBUG_UI; -import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW; -import static com.android.systemui.screenshot.LogConfig.logTag; -import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER; -import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.Insets; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Process; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.Display; -import android.view.ScrollCaptureResponse; -import android.view.ViewRootImpl; -import android.view.WindowManager; -import android.widget.Toast; -import android.window.WindowContext; - -import com.android.internal.logging.UiEventLogger; -import com.android.settingslib.applications.InterestingConfigChanges; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.broadcast.BroadcastSender; -import com.android.systemui.clipboardoverlay.ClipboardOverlayController; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.res.R; -import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; -import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor; -import com.android.systemui.util.Assert; - -import com.google.common.util.concurrent.ListenableFuture; - -import dagger.assisted.Assisted; -import dagger.assisted.AssistedFactory; -import dagger.assisted.AssistedInject; - -import kotlin.Unit; - -import java.util.UUID; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Consumer; - -import javax.inject.Provider; - -/** - * Controls the state and flow for screenshots. - */ -public class ScreenshotController implements InteractiveScreenshotHandler { - private static final String TAG = logTag(ScreenshotController.class); - - // From WizardManagerHelper.java - private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete"; - - static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000; - - private final WindowContext mContext; - private final FeatureFlags mFlags; - private final ScreenshotShelfViewProxy mViewProxy; - private final ScreenshotNotificationsController mNotificationsController; - private final ScreenshotSmartActions mScreenshotSmartActions; - private final UiEventLogger mUiEventLogger; - private final ImageExporter mImageExporter; - private final ImageCapture mImageCapture; - private final Executor mMainExecutor; - private final ExecutorService mBgExecutor; - private final BroadcastSender mBroadcastSender; - private final BroadcastDispatcher mBroadcastDispatcher; - private final ScreenshotActionsController mActionsController; - - @Nullable - private final ScreenshotSoundController mScreenshotSoundController; - private final ScreenshotWindow mWindow; - private final Display mDisplay; - private final ScrollCaptureExecutor mScrollCaptureExecutor; - private final ScreenshotNotificationSmartActionsProvider - mScreenshotNotificationSmartActionsProvider; - private final TimeoutHandler mScreenshotHandler; - private final UserManager mUserManager; - private final AssistContentRequester mAssistContentRequester; - private final ActionExecutor mActionExecutor; - - - private final MessageContainerController mMessageContainerController; - private final AnnouncementResolver mAnnouncementResolver; - private Bitmap mScreenBitmap; - private boolean mScreenshotTakenInPortrait; - private Animator mScreenshotAnimation; - private RequestCallback mCurrentRequestCallback; - private String mPackageName = ""; - private final BroadcastReceiver mCopyBroadcastReceiver; - - /** Tracks config changes that require re-creating UI */ - private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( - ActivityInfo.CONFIG_ORIENTATION - | ActivityInfo.CONFIG_LAYOUT_DIRECTION - | ActivityInfo.CONFIG_LOCALE - | ActivityInfo.CONFIG_UI_MODE - | ActivityInfo.CONFIG_SCREEN_LAYOUT - | ActivityInfo.CONFIG_ASSETS_PATHS); - - - @AssistedInject - ScreenshotController( - Context context, - ScreenshotWindow.Factory screenshotWindowFactory, - FeatureFlags flags, - ScreenshotShelfViewProxy.Factory viewProxyFactory, - ScreenshotSmartActions screenshotSmartActions, - ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory, - UiEventLogger uiEventLogger, - ImageExporter imageExporter, - ImageCapture imageCapture, - @Main Executor mainExecutor, - ScrollCaptureExecutor scrollCaptureExecutor, - TimeoutHandler timeoutHandler, - BroadcastSender broadcastSender, - BroadcastDispatcher broadcastDispatcher, - ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider, - ScreenshotActionsController.Factory screenshotActionsControllerFactory, - ActionExecutor.Factory actionExecutorFactory, - UserManager userManager, - AssistContentRequester assistContentRequester, - MessageContainerController messageContainerController, - Provider<ScreenshotSoundController> screenshotSoundController, - AnnouncementResolver announcementResolver, - @Assisted Display display - ) { - mScreenshotSmartActions = screenshotSmartActions; - mNotificationsController = screenshotNotificationsControllerFactory.create( - display.getDisplayId()); - mUiEventLogger = uiEventLogger; - mImageExporter = imageExporter; - mImageCapture = imageCapture; - mMainExecutor = mainExecutor; - mScrollCaptureExecutor = scrollCaptureExecutor; - mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider; - mBgExecutor = Executors.newSingleThreadExecutor(); - mBroadcastSender = broadcastSender; - mBroadcastDispatcher = broadcastDispatcher; - - mScreenshotHandler = timeoutHandler; - mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS); - - mDisplay = display; - mWindow = screenshotWindowFactory.create(mDisplay); - mContext = mWindow.getContext(); - mFlags = flags; - mUserManager = userManager; - mMessageContainerController = messageContainerController; - mAssistContentRequester = assistContentRequester; - mAnnouncementResolver = announcementResolver; - - mViewProxy = viewProxyFactory.getProxy(mContext, mDisplay.getDisplayId()); - - mScreenshotHandler.setOnTimeoutRunnable(() -> { - if (DEBUG_UI) { - Log.d(TAG, "Corner timeout hit"); - } - mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT); - }); - - mConfigChanges.applyNewConfig(context.getResources()); - reloadAssets(); - - mActionExecutor = actionExecutorFactory.create(mWindow.getWindow(), mViewProxy, - () -> { - finishDismiss(); - return Unit.INSTANCE; - }); - mActionsController = screenshotActionsControllerFactory.getController(mActionExecutor); - - - // Sound is only reproduced from the controller of the default display. - if (mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY) { - mScreenshotSoundController = screenshotSoundController.get(); - } else { - mScreenshotSoundController = null; - } - - mCopyBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) { - mViewProxy.requestDismissal(SCREENSHOT_DISMISSED_OTHER); - } - } - }; - mBroadcastDispatcher.registerReceiver(mCopyBroadcastReceiver, new IntentFilter( - ClipboardOverlayController.COPY_OVERLAY_ACTION), null, null, - Context.RECEIVER_NOT_EXPORTED, ClipboardOverlayController.SELF_PERMISSION); - } - - @Override - public void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, - RequestCallback requestCallback) { - Assert.isMainThread(); - - mCurrentRequestCallback = requestCallback; - if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN - && screenshot.getBitmap() == null) { - Rect bounds = getFullScreenRect(); - screenshot.setBitmap(mImageCapture.captureDisplay(mDisplay.getDisplayId(), bounds)); - screenshot.setScreenBounds(bounds); - } - - if (screenshot.getBitmap() == null) { - Log.e(TAG, "handleScreenshot: Screenshot bitmap was null"); - mNotificationsController.notifyScreenshotError( - R.string.screenshot_failed_to_capture_text); - if (mCurrentRequestCallback != null) { - mCurrentRequestCallback.reportError(); - } - return; - } - - mScreenBitmap = screenshot.getBitmap(); - String oldPackageName = mPackageName; - mPackageName = screenshot.getPackageNameString(); - - if (!isUserSetupComplete(Process.myUserHandle())) { - Log.w(TAG, "User setup not complete, displaying toast only"); - // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing - // and sharing shouldn't be exposed to the user. - saveScreenshotAndToast(screenshot, finisher); - return; - } - - mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION), - ClipboardOverlayController.SELF_PERMISSION); - - mScreenshotTakenInPortrait = - mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; - - // Optimizations - mScreenBitmap.setHasAlpha(false); - mScreenBitmap.prepareToDraw(); - - prepareViewForNewScreenshot(screenshot, oldPackageName); - - final UUID requestId; - requestId = mActionsController.setCurrentScreenshot(screenshot); - saveScreenshotInBackground(screenshot, requestId, finisher, result -> { - if (result.uri != null) { - ScreenshotSavedResult savedScreenshot = new ScreenshotSavedResult( - result.uri, screenshot.getUserOrDefault(), result.timestamp); - mActionsController.setCompletedScreenshot(requestId, savedScreenshot); - } - }); - - if (screenshot.getTaskId() >= 0) { - mAssistContentRequester.requestAssistContent( - screenshot.getTaskId(), - assistContent -> - mActionsController.onAssistContent(requestId, assistContent)); - } else { - mActionsController.onAssistContent(requestId, null); - } - - // The window is focusable by default - mWindow.setFocusable(true); - mViewProxy.requestFocus(); - - enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle()); - - mWindow.attachWindow(); - - boolean showFlash; - if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { - if (screenshot.getScreenBounds() != null - && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(), - screenshot.getScreenBounds())) { - showFlash = false; - } else { - showFlash = true; - screenshot.setInsets(Insets.NONE); - screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(), - screenshot.getBitmap().getHeight())); - } - } else { - showFlash = true; - } - - mViewProxy.prepareEntranceAnimation( - () -> startAnimation(screenshot.getScreenBounds(), showFlash, - () -> mMessageContainerController.onScreenshotTaken(screenshot))); - - mViewProxy.setScreenshot(screenshot); - - } - - void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) { - mWindow.whenWindowAttached(() -> { - mAnnouncementResolver.getScreenshotAnnouncement( - screenshot.getUserHandle().getIdentifier(), - announcement -> { - mViewProxy.announceForAccessibility(announcement); - }); - }); - - mViewProxy.reset(); - - if (mViewProxy.isAttachedToWindow()) { - // if we didn't already dismiss for another reason - if (!mViewProxy.isDismissing()) { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, - oldPackageName); - } - if (DEBUG_WINDOW) { - Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. " - + "(dismissing=" + mViewProxy.isDismissing() + ")"); - } - } - - mViewProxy.setPackageName(mPackageName); - } - - /** - * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already - * being dismissed) - */ - @Override - public void requestDismissal(ScreenshotEvent event) { - mViewProxy.requestDismissal(event); - } - - @Override - public boolean isPendingSharedTransition() { - return mActionExecutor.isPendingSharedTransition(); - } - - // Any cleanup needed when the service is being destroyed. - @Override - public void onDestroy() { - removeWindow(); - releaseMediaPlayer(); - releaseContext(); - mBgExecutor.shutdown(); - } - - /** - * Release the constructed window context. - */ - private void releaseContext() { - mBroadcastDispatcher.unregisterReceiver(mCopyBroadcastReceiver); - mContext.release(); - } - - private void releaseMediaPlayer() { - if (mScreenshotSoundController == null) return; - mScreenshotSoundController.releaseScreenshotSoundAsync(); - } - - /** - * Update resources on configuration change. Reinflate for theme/color changes. - */ - private void reloadAssets() { - if (DEBUG_UI) { - Log.d(TAG, "reloadAssets()"); - } - - mMessageContainerController.setView(mViewProxy.getView()); - mViewProxy.setCallbacks(new ScreenshotShelfViewProxy.ScreenshotViewCallback() { - @Override - public void onUserInteraction() { - if (DEBUG_INPUT) { - Log.d(TAG, "onUserInteraction"); - } - mScreenshotHandler.resetTimeout(); - } - - @Override - public void onDismiss() { - finishDismiss(); - } - - @Override - public void onTouchOutside() { - // TODO(159460485): Remove this when focus is handled properly in the system - mWindow.setFocusable(false); - } - }); - - if (DEBUG_WINDOW) { - Log.d(TAG, "setContentView: " + mViewProxy.getView()); - } - mWindow.setContentView(mViewProxy.getView()); - } - - private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) { - // Wait until this window is attached to request because it is - // the reference used to locate the target window (below). - mWindow.whenWindowAttached(() -> { - requestScrollCapture(requestId, owner); - mWindow.setActivityConfigCallback( - new ViewRootImpl.ActivityConfigCallback() { - @Override - public void onConfigurationChanged(Configuration overrideConfig, - int newDisplayId) { - if (mConfigChanges.applyNewConfig(mContext.getResources())) { - // Hide the scroll chip until we know it's available in this - // orientation - mActionsController.onScrollChipInvalidated(); - // Delay scroll capture eval a bit to allow the underlying activity - // to set up in the new orientation. - mScreenshotHandler.postDelayed( - () -> requestScrollCapture(requestId, owner), 150); - mViewProxy.updateInsets(mWindow.getWindowInsets()); - // Screenshot animation calculations won't be valid anymore, - // so just end - if (mScreenshotAnimation != null - && mScreenshotAnimation.isRunning()) { - mScreenshotAnimation.end(); - } - } - } - }); - }); - } - - private void requestScrollCapture(UUID requestId, UserHandle owner) { - mScrollCaptureExecutor.requestScrollCapture( - mDisplay.getDisplayId(), - mWindow.getWindowToken(), - (response) -> { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, - 0, response.getPackageName()); - mActionsController.onScrollChipReady(requestId, - () -> onScrollButtonClicked(owner, response)); - return Unit.INSTANCE; - } - ); - } - - private void onScrollButtonClicked(UserHandle owner, ScrollCaptureResponse response) { - if (DEBUG_INPUT) { - Log.d(TAG, "scroll chip tapped"); - } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0, - response.getPackageName()); - Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplay.getDisplayId(), - getFullScreenRect()); - if (newScreenshot == null) { - Log.e(TAG, "Failed to capture current screenshot for scroll transition!"); - return; - } - // delay starting scroll capture to make sure scrim is up before the app moves - mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot, - mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner)); - } - - private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) { - mScrollCaptureExecutor.executeBatchScrollCapture(response, - () -> { - final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent( - owner, mContext); - mContext.startActivity(intent); - }, - mViewProxy::restoreNonScrollingUi, - mViewProxy::startLongScreenshotTransition); - } - - @Override - public void removeWindow() { - mWindow.removeWindow(); - mViewProxy.stopInputListening(); - } - - private void playCameraSoundIfNeeded() { - if (mScreenshotSoundController == null) return; - // the controller is not-null only on the default display controller - mScreenshotSoundController.playScreenshotSoundAsync(); - } - - /** - * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on - * failure). - */ - private void saveScreenshotAndToast(ScreenshotData screenshot, Consumer<Uri> finisher) { - // Play the shutter sound to notify that we've taken a screenshot - playCameraSoundIfNeeded(); - - saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher, result -> { - if (result.uri != null) { - mScreenshotHandler.post(() -> Toast.makeText(mContext, - R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show()); - } - }); - } - - /** - * Starts the animation after taking the screenshot - */ - private void startAnimation(Rect screenRect, boolean showFlash, Runnable onAnimationComplete) { - if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { - mScreenshotAnimation.cancel(); - } - - mScreenshotAnimation = - mViewProxy.createScreenshotDropInAnimation(screenRect, showFlash); - if (onAnimationComplete != null) { - mScreenshotAnimation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - onAnimationComplete.run(); - } - }); - } - - // Play the shutter sound to notify that we've taken a screenshot - playCameraSoundIfNeeded(); - - if (DEBUG_ANIM) { - Log.d(TAG, "starting post-screenshot animation"); - } - mScreenshotAnimation.start(); - } - - /** Reset screenshot view and then call onCompleteRunnable */ - private void finishDismiss() { - Log.d(TAG, "finishDismiss"); - mActionsController.endScreenshotSession(); - mScrollCaptureExecutor.close(); - if (mCurrentRequestCallback != null) { - mCurrentRequestCallback.onFinish(); - mCurrentRequestCallback = null; - } - mViewProxy.reset(); - removeWindow(); - mScreenshotHandler.cancelTimeout(); - } - - private void saveScreenshotInBackground(ScreenshotData screenshot, UUID requestId, - Consumer<Uri> finisher, Consumer<ImageExporter.Result> onResult) { - ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor, - requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), - mDisplay.getDisplayId()); - future.addListener(() -> { - try { - ImageExporter.Result result = future.get(); - Log.d(TAG, "Saved screenshot: " + result); - logScreenshotResultStatus(result.uri, screenshot.getUserHandle()); - onResult.accept(result); - if (DEBUG_CALLBACK) { - Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) " - + "finisher.accept(\"" + result.uri + "\""); - } - finisher.accept(result.uri); - } catch (Exception e) { - Log.d(TAG, "Failed to store screenshot", e); - if (DEBUG_CALLBACK) { - Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)"); - } - finisher.accept(null); - } - }, mMainExecutor); - } - - /** - * Logs success/failure of the screenshot saving task, and shows an error if it failed. - */ - private void logScreenshotResultStatus(Uri uri, UserHandle owner) { - if (uri == null) { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName); - mNotificationsController.notifyScreenshotError( - R.string.screenshot_failed_to_save_text); - } else { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName); - if (mUserManager.isManagedProfile(owner.getIdentifier())) { - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0, - mPackageName); - } - } - } - - private boolean isUserSetupComplete(UserHandle owner) { - return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0) - .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; - } - - private Rect getFullScreenRect() { - DisplayMetrics displayMetrics = new DisplayMetrics(); - mDisplay.getRealMetrics(displayMetrics); - return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels); - } - - /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ - private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, - Rect screenBounds) { - int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right; - int insettedHeight = bitmap.getHeight() - bitmapInsets.top - bitmapInsets.bottom; - - if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0 - || bitmap.getHeight() == 0) { - if (DEBUG_UI) { - Log.e(TAG, "Provided bitmap and insets create degenerate region: " - + bitmap.getWidth() + "x" + bitmap.getHeight() + " " + bitmapInsets); - } - return false; - } - - float insettedBitmapAspect = ((float) insettedWidth) / insettedHeight; - float boundsAspect = ((float) screenBounds.width()) / screenBounds.height(); - - boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f; - if (DEBUG_UI) { - Log.d(TAG, "aspectRatiosMatch: don't match bitmap: " + insettedBitmapAspect - + ", bounds: " + boundsAspect); - } - return matchWithinTolerance; - } - - /** Injectable factory to create screenshot controller instances for a specific display. */ - @AssistedFactory - public interface Factory extends InteractiveScreenshotHandler.Factory { - /** - * Creates an instance of the controller for that specific display. - * - * @param display display to capture - */ - ScreenshotController create(Display display); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt new file mode 100644 index 000000000000..29208f89c4e1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt @@ -0,0 +1,632 @@ +/* + * 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.screenshot + +import android.animation.Animator +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Insets +import android.graphics.Rect +import android.net.Uri +import android.os.Process +import android.os.UserHandle +import android.os.UserManager +import android.provider.Settings +import android.util.DisplayMetrics +import android.util.Log +import android.view.Display +import android.view.ScrollCaptureResponse +import android.view.ViewRootImpl.ActivityConfigCallback +import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN +import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE +import android.widget.Toast +import android.window.WindowContext +import androidx.core.animation.doOnEnd +import com.android.internal.logging.UiEventLogger +import com.android.settingslib.applications.InterestingConfigChanges +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.clipboardoverlay.ClipboardOverlayController +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import com.android.systemui.screenshot.ActionIntentCreator.createLongScreenshotIntent +import com.android.systemui.screenshot.ScreenshotShelfViewProxy.ScreenshotViewCallback +import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot +import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor +import com.android.systemui.util.Assert +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import java.util.UUID +import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.function.Consumer +import javax.inject.Provider +import kotlin.math.abs + +/** Controls the state and flow for screenshots. */ +class ScreenshotController +@AssistedInject +internal constructor( + appContext: Context, + screenshotWindowFactory: ScreenshotWindow.Factory, + viewProxyFactory: ScreenshotShelfViewProxy.Factory, + screenshotNotificationsControllerFactory: ScreenshotNotificationsController.Factory, + screenshotActionsControllerFactory: ScreenshotActionsController.Factory, + actionExecutorFactory: ActionExecutor.Factory, + screenshotSoundControllerProvider: Provider<ScreenshotSoundController?>, + private val uiEventLogger: UiEventLogger, + private val imageExporter: ImageExporter, + private val imageCapture: ImageCapture, + private val scrollCaptureExecutor: ScrollCaptureExecutor, + private val screenshotHandler: TimeoutHandler, + private val broadcastSender: BroadcastSender, + private val broadcastDispatcher: BroadcastDispatcher, + private val userManager: UserManager, + private val assistContentRequester: AssistContentRequester, + private val messageContainerController: MessageContainerController, + private val announcementResolver: AnnouncementResolver, + @Main private val mainExecutor: Executor, + @Assisted private val display: Display, +) : InteractiveScreenshotHandler { + private val context: WindowContext + private val viewProxy: ScreenshotShelfViewProxy + private val notificationController = + screenshotNotificationsControllerFactory.create(display.displayId) + private val bgExecutor: ExecutorService = Executors.newSingleThreadExecutor() + private val actionsController: ScreenshotActionsController + private val window: ScreenshotWindow + private val actionExecutor: ActionExecutor + private val copyBroadcastReceiver: BroadcastReceiver + + private var screenshotSoundController: ScreenshotSoundController? = null + private var screenBitmap: Bitmap? = null + private var screenshotTakenInPortrait = false + private var screenshotAnimation: Animator? = null + private var currentRequestCallback: TakeScreenshotService.RequestCallback? = null + private var packageName = "" + + /** Tracks config changes that require re-creating UI */ + private val configChanges = + InterestingConfigChanges( + ActivityInfo.CONFIG_ORIENTATION or + ActivityInfo.CONFIG_LAYOUT_DIRECTION or + ActivityInfo.CONFIG_LOCALE or + ActivityInfo.CONFIG_UI_MODE or + ActivityInfo.CONFIG_SCREEN_LAYOUT or + ActivityInfo.CONFIG_ASSETS_PATHS + ) + + init { + screenshotHandler.defaultTimeoutMillis = SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS + + window = screenshotWindowFactory.create(display) + context = window.getContext() + + viewProxy = viewProxyFactory.getProxy(context, display.displayId) + + screenshotHandler.setOnTimeoutRunnable { + if (LogConfig.DEBUG_UI) { + Log.d(TAG, "Corner timeout hit") + } + viewProxy.requestDismissal(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT) + } + + configChanges.applyNewConfig(appContext.resources) + reloadAssets() + + actionExecutor = actionExecutorFactory.create(window.window, viewProxy) { finishDismiss() } + actionsController = screenshotActionsControllerFactory.getController(actionExecutor) + + // Sound is only reproduced from the controller of the default display. + screenshotSoundController = + if (display.displayId == Display.DEFAULT_DISPLAY) { + screenshotSoundControllerProvider.get() + } else { + null + } + + copyBroadcastReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (ClipboardOverlayController.COPY_OVERLAY_ACTION == intent.action) { + viewProxy.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER) + } + } + } + broadcastDispatcher.registerReceiver( + copyBroadcastReceiver, + IntentFilter(ClipboardOverlayController.COPY_OVERLAY_ACTION), + null, + null, + Context.RECEIVER_NOT_EXPORTED, + ClipboardOverlayController.SELF_PERMISSION, + ) + } + + override fun handleScreenshot( + screenshot: ScreenshotData, + finisher: Consumer<Uri?>, + requestCallback: TakeScreenshotService.RequestCallback, + ) { + Assert.isMainThread() + + currentRequestCallback = requestCallback + if (screenshot.type == TAKE_SCREENSHOT_FULLSCREEN && screenshot.bitmap == null) { + val bounds = fullScreenRect + screenshot.bitmap = imageCapture.captureDisplay(display.displayId, bounds) + screenshot.screenBounds = bounds + } + + val currentBitmap = screenshot.bitmap + if (currentBitmap == null) { + Log.e(TAG, "handleScreenshot: Screenshot bitmap was null") + notificationController.notifyScreenshotError(R.string.screenshot_failed_to_capture_text) + currentRequestCallback?.reportError() + return + } + + screenBitmap = currentBitmap + val oldPackageName = packageName + packageName = screenshot.packageNameString + + if (!isUserSetupComplete(Process.myUserHandle())) { + Log.w(TAG, "User setup not complete, displaying toast only") + // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing + // and sharing shouldn't be exposed to the user. + saveScreenshotAndToast(screenshot, finisher) + return + } + + broadcastSender.sendBroadcast( + Intent(ClipboardOverlayController.SCREENSHOT_ACTION), + ClipboardOverlayController.SELF_PERMISSION, + ) + + screenshotTakenInPortrait = + context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT + + // Optimizations + currentBitmap.setHasAlpha(false) + currentBitmap.prepareToDraw() + + prepareViewForNewScreenshot(screenshot, oldPackageName) + val requestId = actionsController.setCurrentScreenshot(screenshot) + saveScreenshotInBackground(screenshot, requestId, finisher) { result -> + if (result.uri != null) { + val savedScreenshot = + ScreenshotSavedResult( + result.uri, + screenshot.getUserOrDefault(), + result.timestamp, + ) + actionsController.setCompletedScreenshot(requestId, savedScreenshot) + } + } + + if (screenshot.taskId >= 0) { + assistContentRequester.requestAssistContent(screenshot.taskId) { assistContent -> + actionsController.onAssistContent(requestId, assistContent) + } + } else { + actionsController.onAssistContent(requestId, null) + } + + // The window is focusable by default + window.setFocusable(true) + viewProxy.requestFocus() + + enqueueScrollCaptureRequest(requestId, screenshot.userHandle!!) + + window.attachWindow() + + val showFlash: Boolean + if (screenshot.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) { + if (aspectRatiosMatch(currentBitmap, screenshot.insets, screenshot.screenBounds)) { + showFlash = false + } else { + showFlash = true + screenshot.insets = Insets.NONE + screenshot.screenBounds = Rect(0, 0, currentBitmap.width, currentBitmap.height) + } + } else { + showFlash = true + } + + // screenshot.screenBounds is expected to be non-null in all cases at this point + val bounds = + screenshot.screenBounds ?: Rect(0, 0, currentBitmap.width, currentBitmap.height) + + viewProxy.prepareEntranceAnimation { + startAnimation(bounds, showFlash) { + messageContainerController.onScreenshotTaken(screenshot) + } + } + + viewProxy.screenshot = screenshot + } + + private fun prepareViewForNewScreenshot(screenshot: ScreenshotData, oldPackageName: String?) { + window.whenWindowAttached { + announcementResolver.getScreenshotAnnouncement(screenshot.userHandle!!.identifier) { + viewProxy.announceForAccessibility(it) + } + } + + viewProxy.reset() + + if (viewProxy.isAttachedToWindow) { + // if we didn't already dismiss for another reason + if (!viewProxy.isDismissing) { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0, oldPackageName) + } + if (LogConfig.DEBUG_WINDOW) { + Log.d( + TAG, + "saveScreenshot: screenshotView is already attached, resetting. " + + "(dismissing=${viewProxy.isDismissing})", + ) + } + } + + viewProxy.packageName = packageName + } + + /** + * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already + * being dismissed) + */ + override fun requestDismissal(event: ScreenshotEvent) { + viewProxy.requestDismissal(event) + } + + override fun isPendingSharedTransition(): Boolean { + return actionExecutor.isPendingSharedTransition + } + + // Any cleanup needed when the service is being destroyed. + override fun onDestroy() { + removeWindow() + releaseMediaPlayer() + releaseContext() + bgExecutor.shutdown() + } + + /** Release the constructed window context. */ + private fun releaseContext() { + broadcastDispatcher.unregisterReceiver(copyBroadcastReceiver) + context.release() + } + + private fun releaseMediaPlayer() { + screenshotSoundController?.releaseScreenshotSoundAsync() + } + + /** Update resources on configuration change. Reinflate for theme/color changes. */ + private fun reloadAssets() { + if (LogConfig.DEBUG_UI) { + Log.d(TAG, "reloadAssets()") + } + + messageContainerController.setView(viewProxy.view) + viewProxy.callbacks = + object : ScreenshotViewCallback { + override fun onUserInteraction() { + if (LogConfig.DEBUG_INPUT) { + Log.d(TAG, "onUserInteraction") + } + screenshotHandler.resetTimeout() + } + + override fun onDismiss() { + finishDismiss() + } + + override fun onTouchOutside() { + // TODO(159460485): Remove this when focus is handled properly in the system + window.setFocusable(false) + } + } + + if (LogConfig.DEBUG_WINDOW) { + Log.d(TAG, "setContentView: " + viewProxy.view) + } + window.setContentView(viewProxy.view) + } + + private fun enqueueScrollCaptureRequest(requestId: UUID, owner: UserHandle) { + // Wait until this window is attached to request because it is + // the reference used to locate the target window (below). + window.whenWindowAttached { + requestScrollCapture(requestId, owner) + window.setActivityConfigCallback( + object : ActivityConfigCallback { + override fun onConfigurationChanged( + overrideConfig: Configuration, + newDisplayId: Int, + ) { + if (configChanges.applyNewConfig(context.resources)) { + // Hide the scroll chip until we know it's available in this + // orientation + actionsController.onScrollChipInvalidated() + // Delay scroll capture eval a bit to allow the underlying activity + // to set up in the new orientation. + screenshotHandler.postDelayed( + { requestScrollCapture(requestId, owner) }, + 150, + ) + viewProxy.updateInsets(window.getWindowInsets()) + // Screenshot animation calculations won't be valid anymore, so just end + screenshotAnimation?.let { currentAnimation -> + if (currentAnimation.isRunning) { + currentAnimation.end() + } + } + } + } + } + ) + } + } + + private fun requestScrollCapture(requestId: UUID, owner: UserHandle) { + scrollCaptureExecutor.requestScrollCapture(display.displayId, window.getWindowToken()) { + response: ScrollCaptureResponse -> + uiEventLogger.log( + ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, + 0, + response.packageName, + ) + actionsController.onScrollChipReady(requestId) { + onScrollButtonClicked(owner, response) + } + } + } + + private fun onScrollButtonClicked(owner: UserHandle, response: ScrollCaptureResponse) { + if (LogConfig.DEBUG_INPUT) { + Log.d(TAG, "scroll chip tapped") + } + uiEventLogger.log( + ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, + 0, + response.packageName, + ) + val newScreenshot = imageCapture.captureDisplay(display.displayId, null) + if (newScreenshot == null) { + Log.e(TAG, "Failed to capture current screenshot for scroll transition!") + return + } + // delay starting scroll capture to make sure scrim is up before the app moves + viewProxy.prepareScrollingTransition(response, newScreenshot, screenshotTakenInPortrait) { + executeBatchScrollCapture(response, owner) + } + } + + private fun executeBatchScrollCapture(response: ScrollCaptureResponse, owner: UserHandle) { + scrollCaptureExecutor.executeBatchScrollCapture( + response, + { + val intent = createLongScreenshotIntent(owner, context) + context.startActivity(intent) + }, + { viewProxy.restoreNonScrollingUi() }, + { transitionDestination: Rect, onTransitionEnd: Runnable, longScreenshot: LongScreenshot + -> + viewProxy.startLongScreenshotTransition( + transitionDestination, + onTransitionEnd, + longScreenshot, + ) + }, + ) + } + + override fun removeWindow() { + window.removeWindow() + viewProxy.stopInputListening() + } + + private fun playCameraSoundIfNeeded() { + // the controller is not-null only on the default display controller + screenshotSoundController?.playScreenshotSoundAsync() + } + + /** + * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on + * failure). + */ + private fun saveScreenshotAndToast(screenshot: ScreenshotData, finisher: Consumer<Uri?>) { + // Play the shutter sound to notify that we've taken a screenshot + playCameraSoundIfNeeded() + + saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher) { + result: ImageExporter.Result -> + if (result.uri != null) { + screenshotHandler.post { + Toast.makeText(context, R.string.screenshot_saved_title, Toast.LENGTH_SHORT) + .show() + } + } + } + } + + /** Starts the animation after taking the screenshot */ + private fun startAnimation( + screenRect: Rect, + showFlash: Boolean, + onAnimationComplete: Runnable?, + ) { + screenshotAnimation?.let { currentAnimation -> + if (currentAnimation.isRunning) { + currentAnimation.cancel() + } + } + + screenshotAnimation = + viewProxy.createScreenshotDropInAnimation(screenRect, showFlash).apply { + doOnEnd { onAnimationComplete?.run() } + // Play the shutter sound to notify that we've taken a screenshot + playCameraSoundIfNeeded() + if (LogConfig.DEBUG_ANIM) { + Log.d(TAG, "starting post-screenshot animation") + } + start() + } + } + + /** Reset screenshot view and then call onCompleteRunnable */ + private fun finishDismiss() { + Log.d(TAG, "finishDismiss") + actionsController.endScreenshotSession() + scrollCaptureExecutor.close() + currentRequestCallback?.onFinish() + currentRequestCallback = null + viewProxy.reset() + removeWindow() + screenshotHandler.cancelTimeout() + } + + private fun saveScreenshotInBackground( + screenshot: ScreenshotData, + requestId: UUID, + finisher: Consumer<Uri?>, + onResult: Consumer<ImageExporter.Result>, + ) { + val future = + imageExporter.export( + bgExecutor, + requestId, + screenshot.bitmap, + screenshot.getUserOrDefault(), + display.displayId, + ) + future.addListener( + { + try { + val result = future.get() + Log.d(TAG, "Saved screenshot: $result") + logScreenshotResultStatus(result.uri, screenshot.userHandle!!) + onResult.accept(result) + if (LogConfig.DEBUG_CALLBACK) { + Log.d(TAG, "finished bg processing, calling back with uri: ${result.uri}") + } + finisher.accept(result.uri) + } catch (e: Exception) { + Log.d(TAG, "Failed to store screenshot", e) + if (LogConfig.DEBUG_CALLBACK) { + Log.d(TAG, "calling back with uri: null") + } + finisher.accept(null) + } + }, + mainExecutor, + ) + } + + /** Logs success/failure of the screenshot saving task, and shows an error if it failed. */ + private fun logScreenshotResultStatus(uri: Uri?, owner: UserHandle) { + if (uri == null) { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, packageName) + notificationController.notifyScreenshotError(R.string.screenshot_failed_to_save_text) + } else { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, packageName) + if (userManager.isManagedProfile(owner.identifier)) { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0, packageName) + } + } + } + + private fun isUserSetupComplete(owner: UserHandle): Boolean { + return Settings.Secure.getInt( + context.createContextAsUser(owner, 0).contentResolver, + SETTINGS_SECURE_USER_SETUP_COMPLETE, + 0, + ) == 1 + } + + private val fullScreenRect: Rect + get() { + val displayMetrics = DisplayMetrics() + display.getRealMetrics(displayMetrics) + return Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels) + } + + /** Injectable factory to create screenshot controller instances for a specific display. */ + @AssistedFactory + interface Factory : InteractiveScreenshotHandler.Factory { + /** + * Creates an instance of the controller for that specific display. + * + * @param display display to capture + */ + override fun create(display: Display): ScreenshotController + } + + companion object { + private val TAG: String = LogConfig.logTag(ScreenshotController::class.java) + + // From WizardManagerHelper.java + private const val SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete" + + const val SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS: Int = 6000 + + /** Does the aspect ratio of the bitmap with insets removed match the bounds. */ + private fun aspectRatiosMatch( + bitmap: Bitmap, + bitmapInsets: Insets, + screenBounds: Rect?, + ): Boolean { + if (screenBounds == null) { + return false + } + val insettedWidth = bitmap.width - bitmapInsets.left - bitmapInsets.right + val insettedHeight = bitmap.height - bitmapInsets.top - bitmapInsets.bottom + + if ( + insettedHeight == 0 || insettedWidth == 0 || bitmap.width == 0 || bitmap.height == 0 + ) { + if (LogConfig.DEBUG_UI) { + Log.e( + TAG, + "Provided bitmap and insets create degenerate region: " + + "${bitmap.width} x ${bitmap.height} $bitmapInsets", + ) + } + return false + } + + val insettedBitmapAspect = insettedWidth.toFloat() / insettedHeight + val boundsAspect = screenBounds.width().toFloat() / screenBounds.height() + + val matchWithinTolerance = abs((insettedBitmapAspect - boundsAspect).toDouble()) < 0.1f + if (LogConfig.DEBUG_UI) { + Log.d( + TAG, + "aspectRatiosMatch: don't match bitmap: " + + "$insettedBitmapAspect, bounds: $boundsAspect", + ) + } + return matchWithinTolerance + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt index 50215af30ad4..c1ea3adc38d5 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt @@ -67,7 +67,7 @@ constructor( shelfViewBinder: ScreenshotShelfViewBinder, private val thumbnailObserver: ThumbnailObserver, @Assisted private val context: Context, - @Assisted private val displayId: Int + @Assisted private val displayId: Int, ) { interface ScreenshotViewCallback { @@ -117,7 +117,7 @@ constructor( animationController, LayoutInflater.from(context), onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) }, - onUserInteraction = { callbacks?.onUserInteraction() } + onUserInteraction = { callbacks?.onUserInteraction() }, ) view.updateInsets(windowManager.currentWindowMetrics.windowInsets) addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) } @@ -130,7 +130,7 @@ constructor( screenshotPreview = view.screenshotPreview thumbnailObserver.setViews( view.blurredScreenshotPreview, - view.requireViewById(R.id.screenshot_preview_border) + view.requireViewById(R.id.screenshot_preview_border), ) view.addOnAttachStateChangeListener( object : View.OnAttachStateChangeListener { @@ -204,7 +204,6 @@ constructor( fun prepareScrollingTransition( response: ScrollCaptureResponse, - screenBitmap: Bitmap, // unused newScreenshot: Bitmap, screenshotTakenInPortrait: Boolean, onTransitionPrepared: Runnable, @@ -224,7 +223,7 @@ constructor( 0, 0, context.resources.displayMetrics.widthPixels, - context.resources.displayMetrics.heightPixels + context.resources.displayMetrics.heightPixels, ) ) return r @@ -239,7 +238,7 @@ constructor( animationController.runLongScreenshotTransition( transitionDestination, longScreenshot, - onTransitionEnd + onTransitionEnd, ) transitionAnimation.doOnEnd { callbacks?.onDismiss() } transitionAnimation.start() @@ -295,7 +294,7 @@ constructor( .findOnBackInvokedDispatcher() ?.registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, - onBackInvokedCallback + onBackInvokedCallback, ) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index f76c5fd4ca83..0c05dbde6117 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2220,6 +2220,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @VisibleForTesting void onFlingEnd(boolean cancelled) { mIsFlinging = false; + mExpectingSynthesizedDown = false; // No overshoot when the animation ends setOverExpansionInternal(0, false /* isFromGesture */); setAnimator(null); @@ -2352,7 +2353,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return; } if (mExpectingSynthesizedDown) { - mExpectingSynthesizedDown = false; // Window never will receive touch events that typically trigger haptic on open. maybeVibrateOnOpening(false /* openingWithTouch */); fling(velocity > 1f ? 1000f * velocity : 0 /* expand */); @@ -3994,6 +3994,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } mExpandedFraction = Math.min(1f, maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); + if (mExpandedFraction > 0f && mExpectingSynthesizedDown) { + mExpectingSynthesizedDown = false; + } mShadeRepository.setLegacyShadeExpansion(mExpandedFraction); mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction); mExpansionDragDownAmountPx = h; diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index bf888073e002..5473af3b3fb5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -59,7 +59,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; -import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener; import com.android.systemui.statusbar.DragDownHelper; @@ -265,20 +264,21 @@ public class NotificationShadeWindowViewController implements Dumpable { } private void bindBouncer(BouncerViewBinder bouncerViewBinder) { + mBouncerParentView = mView.findViewById(R.id.keyguard_bouncer_container); + bouncerViewBinder.bind(mBouncerParentView); if (ComposeBouncerFlags.INSTANCE.isOnlyComposeBouncerEnabled()) { - collectFlow(mView, mKeyguardTransitionInteractor.isFinishedIn(Scenes.Gone, - KeyguardState.GONE), this::removeBouncerParentView); collectFlow(mView, mKeyguardTransitionInteractor.transition( - new Edge.StateToState(KeyguardState.GONE, null)), - this::handleGoneToAnyOtherStateTransition); + new Edge.StateToState(KeyguardState.PRIMARY_BOUNCER, null)), + this::onTransitionAwayFromBouncer); + collectFlow(mView, mKeyguardTransitionInteractor.transition( + new Edge.StateToState(null, KeyguardState.PRIMARY_BOUNCER)), + this::onTransitionToBouncer); collectFlow(mView, mPrimaryBouncerInteractor.isShowing(), (showing) -> ViewKt.setVisible(mBouncerParentView, showing)); } - mBouncerParentView = mView.findViewById(R.id.keyguard_bouncer_container); - bouncerViewBinder.bind(mBouncerParentView); } - private void handleGoneToAnyOtherStateTransition(TransitionStep transitionStep) { + private void onTransitionToBouncer(TransitionStep transitionStep) { if (transitionStep.getTransitionState() == TransitionState.STARTED) { if (mView.indexOfChild(mBouncerParentView) != -1) { mView.removeView(mBouncerParentView); @@ -287,8 +287,8 @@ public class NotificationShadeWindowViewController implements Dumpable { } } - private void removeBouncerParentView(boolean isFinishedInGoneState) { - if (isFinishedInGoneState) { + private void onTransitionAwayFromBouncer(TransitionStep transitionStep) { + if (transitionStep.getTransitionState() == TransitionState.FINISHED) { mView.removeView(mBouncerParentView); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt index 2bb476523cb8..ce25cf5895c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt @@ -17,8 +17,12 @@ package com.android.systemui.statusbar.disableflags.data.model import android.app.StatusBarManager.DISABLE2_NONE import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS +import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS +import android.app.StatusBarManager.DISABLE_CLOCK import android.app.StatusBarManager.DISABLE_NONE import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS +import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS +import android.app.StatusBarManager.DISABLE_SYSTEM_INFO import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.disableflags.DisableFlagsLogger @@ -27,12 +31,14 @@ import com.android.systemui.statusbar.disableflags.DisableFlagsLogger * Model for the disable flags that come from [IStatusBar]. * * For clients of the disable flags: do *not* refer to the disable integers directly. Instead, - * re-use or define a helper method that internally processes the flags. (We want to hide the - * bitwise logic here so no one else has to worry about it.) + * re-use or define a helper method or property that internally processes the flags. (We want to + * hide the bitwise logic here so no one else has to worry about it.) */ data class DisableFlagsModel( private val disable1: Int = DISABLE_NONE, private val disable2: Int = DISABLE2_NONE, + /** True if we should animate any view visibility changes and false otherwise. */ + val animate: Boolean = false, ) { /** Returns true if notification alerts are allowed based on the flags. */ fun areNotificationAlertsEnabled(): Boolean { @@ -49,6 +55,13 @@ data class DisableFlagsModel( return (disable2 and DISABLE2_QUICK_SETTINGS) == 0 } + val isClockEnabled = (disable1 and DISABLE_CLOCK) == 0 + + val areNotificationIconsEnabled = (disable1 and DISABLE_NOTIFICATION_ICONS) == 0 + + val isSystemInfoEnabled = + (disable1 and DISABLE_SYSTEM_INFO) == 0 && (disable2 and DISABLE2_SYSTEM_ICONS) == 0 + /** Logs the change to the provided buffer. */ fun logChange(buffer: LogBuffer, disableFlagsLogger: DisableFlagsLogger) { buffer.log( @@ -60,9 +73,9 @@ data class DisableFlagsModel( }, { disableFlagsLogger.getDisableFlagsString( - new = DisableFlagsLogger.DisableState(int1, int2), + new = DisableFlagsLogger.DisableState(int1, int2) ) - } + }, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt index 13b74b493905..9004e5d12663 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepository.kt @@ -72,6 +72,7 @@ constructor( // [QuickSettingsInteractor]-type class. However, that's out of // scope for the CentralSurfaces removal project. remoteInputQuickSettingsDisabler.adjustDisableFlags(state2), + animate, ) ) } @@ -82,5 +83,5 @@ constructor( .distinctUntilChanged() .onEach { it.logChange(logBuffer, disableFlagsLogger) } // Use Eagerly because we always need to know about disable flags - .stateIn(scope, SharingStarted.Eagerly, DisableFlagsModel()) + .stateIn(scope, SharingStarted.Eagerly, DisableFlagsModel(animate = false)) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 658455688865..a8b4728bc982 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -56,7 +56,8 @@ import com.android.systemui.statusbar.OperatorNameView; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips; -import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState; +import com.android.systemui.statusbar.core.StatusBarSimpleFragment; +import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder; @@ -366,8 +367,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mPrimaryOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip_primary); mSecondaryOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip_secondary); - showEndSideContent(false); - showClock(false); + if (!StatusBarSimpleFragment.isEnabled()) { + showEndSideContent(false); + showClock(false); + } initOperatorName(); initNotificationIconArea(); mSystemEventAnimator = getSystemEventAnimator(); @@ -455,7 +458,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue super.onPause(); mCommandQueue.removeCallback(this); mStatusBarStateController.removeCallback(this); - mOngoingCallController.removeCallback(mOngoingCallListener); + if (!StatusBarSimpleFragment.isEnabled()) { + mOngoingCallController.removeCallback(mOngoingCallListener); + } mAnimationScheduler.removeCallback(this); mSecureSettings.unregisterContentObserverSync(mVolumeSettingObserver); } @@ -490,7 +495,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mNotificationIconAreaInner = notificationIcons; mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons); - updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false); + if (!StatusBarSimpleFragment.isEnabled()) { + updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false); + } Trace.endSection(); } @@ -509,11 +516,17 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue new StatusBarVisibilityChangeListener() { @Override public void onStatusBarVisibilityMaybeChanged() { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } updateStatusBarVisibilities(/* animate= */ true); } @Override public void onTransitionFromLockscreenToDreamStarted() { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } mTransitionFromLockscreenToDreamStarted = true; } @@ -522,6 +535,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue boolean hasPrimaryOngoingActivity, boolean hasSecondaryOngoingActivity, boolean shouldAnimate) { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } mHasPrimaryOngoingActivity = hasPrimaryOngoingActivity; mHasSecondaryOngoingActivity = hasSecondaryOngoingActivity; updateStatusBarVisibilities(shouldAnimate); @@ -530,6 +546,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onIsHomeStatusBarAllowedBySceneChanged( boolean isHomeStatusBarAllowedByScene) { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } mHomeStatusBarAllowedByScene = isHomeStatusBarAllowedByScene; updateStatusBarVisibilities(/* animate= */ true); } @@ -537,17 +556,22 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void disable(int displayId, int state1, int state2, boolean animate) { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } if (displayId != getContext().getDisplayId()) { return; } mCollapsedStatusBarFragmentLogger - .logDisableFlagChange(new DisableState(state1, state2)); + .logDisableFlagChange(new DisableFlagsLogger.DisableState(state1, state2)); mLastSystemVisibility = StatusBarVisibilityModel.createModelFromFlags(state1, state2); updateStatusBarVisibilities(animate); } private void updateStatusBarVisibilities(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarVisibilityModel previousModel = mLastModifiedVisibility; StatusBarVisibilityModel newModel = calculateInternalModel(mLastSystemVisibility); mCollapsedStatusBarFragmentLogger.logVisibilityModel(newModel); @@ -587,6 +611,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private StatusBarVisibilityModel calculateInternalModel( StatusBarVisibilityModel externalModel) { + StatusBarSimpleFragment.assertInLegacyMode(); + // TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead. boolean headsUpVisible = mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible(); @@ -639,6 +665,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * mLastModifiedVisibility. */ private void updateNotificationIconAreaAndOngoingActivityChip(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); + StatusBarVisibilityModel visibilityModel = mLastModifiedVisibility; boolean disableNotifications = !visibilityModel.getShowNotificationIcons(); boolean hasOngoingActivity = visibilityModel.getShowPrimaryOngoingActivityChip(); @@ -674,6 +702,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private boolean shouldHideStatusBar() { + StatusBarSimpleFragment.assertInLegacyMode(); + if (!mShadeExpansionStateManager.isClosed() && mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()) { return true; @@ -728,6 +758,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void hideEndSideContent(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); if (!animate || !mAnimationsEnabled) { mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER); } else { @@ -737,6 +768,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void showEndSideContent(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); if (!animate || !mAnimationsEnabled) { mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER); return; @@ -753,15 +785,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void hideClock(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateHiddenState(mClockView, clockHiddenMode(), animate); } private void showClock(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateShow(mClockView, animate); } /** Hides the primary ongoing activity chip. */ private void hidePrimaryOngoingActivityChip(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateHiddenState(mPrimaryOngoingActivityChip, View.GONE, animate); } @@ -773,15 +808,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * activities. See b/332662551. */ private void showPrimaryOngoingActivityChip(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateShow(mPrimaryOngoingActivityChip, animate); } private void hideSecondaryOngoingActivityChip(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateHiddenState(mSecondaryOngoingActivityChip, View.GONE, animate); } private void showSecondaryOngoingActivityChip(boolean animate) { StatusBarRonChips.assertInNewMode(); + StatusBarSimpleFragment.assertInLegacyMode(); animateShow(mSecondaryOngoingActivityChip, animate); } @@ -790,6 +828,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * don't set the clock GONE otherwise it'll mess up the animation. */ private int clockHiddenMode() { + StatusBarSimpleFragment.assertInLegacyMode(); if (!mShadeExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing() && !mStatusBarStateController.isDozing()) { return View.INVISIBLE; @@ -798,20 +837,24 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } public void hideNotificationIconArea(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateHide(mNotificationIconAreaInner, animate); } public void showNotificationIconArea(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateShow(mNotificationIconAreaInner, animate); } public void hideOperatorName(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); if (mOperatorNameViewController != null) { animateHide(mOperatorNameViewController.getView(), animate); } } public void showOperatorName(boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); if (mOperatorNameViewController != null) { animateShow(mOperatorNameViewController.getView(), animate); } @@ -821,6 +864,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * Animate a view to INVISIBLE or GONE */ private void animateHiddenState(final View v, int state, boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); v.animate().cancel(); if (!animate || !mAnimationsEnabled) { v.setAlpha(0f); @@ -840,6 +884,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * Hides a view. */ private void animateHide(final View v, boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); animateHiddenState(v, View.INVISIBLE, animate); } @@ -847,6 +892,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable. */ private void animateShow(View v, boolean animate) { + StatusBarSimpleFragment.assertInLegacyMode(); v.animate().cancel(); v.setVisibility(View.VISIBLE); if (!animate || !mAnimationsEnabled) { @@ -883,13 +929,17 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mOperatorNameViewController.init(); // This view should not be visible on lock-screen if (mKeyguardStateController.isShowing()) { - hideOperatorName(false); + if (!StatusBarSimpleFragment.isEnabled()) { + hideOperatorName(false); + } } } } private void initOngoingCallChip() { - mOngoingCallController.addCallback(mOngoingCallListener); + if (!StatusBarSimpleFragment.isEnabled()) { + mOngoingCallController.addCallback(mOngoingCallListener); + } // TODO(b/364653005): Do we also need to set the secondary activity chip? mOngoingCallController.setChipView(mPrimaryOngoingActivityChip); } @@ -899,6 +949,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onDozingChanged(boolean isDozing) { + if (StatusBarSimpleFragment.isEnabled()) { + return; + } updateStatusBarVisibilities(/* animate= */ false); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt index c8836e4235dc..eaf15a8cbe17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt @@ -20,6 +20,7 @@ import android.view.View import androidx.core.animation.Interpolator import androidx.core.animation.ValueAnimator import com.android.app.animation.InterpolatorsAndroidX +import com.android.systemui.statusbar.core.StatusBarSimpleFragment /** * A controller that keeps track of multiple sources applying alpha value changes to a view. It will @@ -48,7 +49,7 @@ constructor(private val view: View, private val initialAlpha: Float = 1f) { sourceId: Int, duration: Long, interpolator: Interpolator = InterpolatorsAndroidX.ALPHA_IN, - startDelay: Long = 0 + startDelay: Long = 0, ) { animators[sourceId]?.cancel() val animator = ValueAnimator.ofFloat(getMinAlpha(), alpha) @@ -74,8 +75,10 @@ constructor(private val view: View, private val initialAlpha: Float = 1f) { private fun applyAlphaToView() { val minAlpha = getMinAlpha() - view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE - view.alpha = minAlpha + if (!StatusBarSimpleFragment.isEnabled) { + view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE + view.alpha = minAlpha + } } private fun getMinAlpha() = alphas.minOfOrNull { it.value } ?: initialAlpha diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index deae576662e3..bad6f80c3735 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel +import com.android.app.tracing.coroutines.createCoroutineTracingContext import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -29,6 +30,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMob import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.cancel @@ -114,7 +116,7 @@ constructor( private fun createViewModel(subId: Int): Pair<MobileIconViewModel, CoroutineScope> { // Create a child scope so we can cancel it - val vmScope = scope.createChildScope() + val vmScope = scope.createChildScope(createCoroutineTracingContext("MobileIconViewModel")) val vm = MobileIconViewModel( subId, @@ -128,8 +130,8 @@ constructor( return Pair(vm, vmScope) } - private fun CoroutineScope.createChildScope() = - CoroutineScope(coroutineContext + Job(coroutineContext[Job])) + private fun CoroutineScope.createChildScope(extraContext: CoroutineContext) = + CoroutineScope(coroutineContext + Job(coroutineContext[Job]) + extraContext) private fun invalidateCaches(subIds: List<Int>) { reuseCache.keys diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt new file mode 100644 index 000000000000..9164da721e3a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractor.kt @@ -0,0 +1,47 @@ +/* + * 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.pipeline.shared.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository +import com.android.systemui.statusbar.pipeline.shared.domain.model.StatusBarDisableFlagsVisibilityModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** + * Interactor for the home screen status bar (aka + * [com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment]). + */ +@SysUISingleton +class CollapsedStatusBarInteractor +@Inject +constructor(disableFlagsRepository: DisableFlagsRepository) { + /** + * The visibilities of various status bar child views, based only on the information we received + * from disable flags. + */ + val visibilityViaDisableFlags: Flow<StatusBarDisableFlagsVisibilityModel> = + disableFlagsRepository.disableFlags.map { + StatusBarDisableFlagsVisibilityModel( + isClockAllowed = it.isClockEnabled, + areNotificationIconsAllowed = it.areNotificationIconsEnabled, + isSystemInfoAllowed = it.isSystemInfoEnabled, + animate = it.animate, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/model/StatusBarDisableFlagsVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/model/StatusBarDisableFlagsVisibilityModel.kt new file mode 100644 index 000000000000..69e9746ee24f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/domain/model/StatusBarDisableFlagsVisibilityModel.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.shared.domain.model + +/** + * Represents the visibilities of various status bar child views, based only on the information we + * received from disable flags. + */ +data class StatusBarDisableFlagsVisibilityModel( + /** True if the clock is allowed to be shown. */ + val isClockAllowed: Boolean, + /** True if the notification icons are allowed to be shown. */ + val areNotificationIconsAllowed: Boolean, + /** True if the system information (wifi, mobile, etc.) is allowed to be shown. */ + val isSystemInfoAllowed: Boolean, + /** True if we should animate any view visibility changes and false otherwise. */ + val animate: Boolean, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt index 49eabba5c2b0..4cb66c19a0bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle +import com.android.app.animation.Interpolators import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached @@ -28,7 +29,9 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.core.StatusBarSimpleFragment import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor +import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel import javax.inject.Inject import kotlinx.coroutines.launch @@ -134,6 +137,29 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa } } } + + if (StatusBarSimpleFragment.isEnabled) { + val clockView = view.requireViewById<View>(R.id.clock) + launch { viewModel.isClockVisible.collect { clockView.adjustVisibility(it) } } + + val notificationIconsArea = view.requireViewById<View>(R.id.notificationIcons) + launch { + viewModel.isNotificationIconContainerVisible.collect { + notificationIconsArea.adjustVisibility(it) + } + } + + val systemInfoView = + view.requireViewById<View>(R.id.status_bar_end_side_content) + // TODO(b/364360986): Also handle operator name view. + launch { + viewModel.isSystemInfoVisible.collect { + systemInfoView.adjustVisibility(it) + // TODO(b/364360986): The system info view has a custom alpha controller + // in CollapsedStatusBarFragment. + } + } + } } } } @@ -167,6 +193,54 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa ) .start() } + + private fun View.adjustVisibility(model: CollapsedStatusBarViewModel.VisibilityModel) { + if (model.visibility == View.VISIBLE) { + this.show(model.shouldAnimateChange) + } else { + this.hide(model.visibility, model.shouldAnimateChange) + } + } + + // See CollapsedStatusBarFragment#hide. + private fun View.hide(state: Int = View.INVISIBLE, shouldAnimateChange: Boolean) { + val v = this + v.animate().cancel() + if (!shouldAnimateChange) { + v.alpha = 0f + v.visibility = state + return + } + + v.animate() + .alpha(0f) + .setDuration(CollapsedStatusBarFragment.FADE_OUT_DURATION.toLong()) + .setStartDelay(0) + .setInterpolator(Interpolators.ALPHA_OUT) + .withEndAction { v.visibility = state } + } + + // See CollapsedStatusBarFragment#show. + private fun View.show(shouldAnimateChange: Boolean) { + val v = this + v.animate().cancel() + v.visibility = View.VISIBLE + if (!shouldAnimateChange) { + v.alpha = 1f + return + } + v.animate() + .alpha(1f) + .setDuration(CollapsedStatusBarFragment.FADE_IN_DURATION.toLong()) + .setInterpolator(Interpolators.ALPHA_IN) + .setStartDelay(CollapsedStatusBarFragment.FADE_IN_DELAY.toLong()) + // We need to clean up any pending end action from animateHide if we call both hide and + // show in the same frame before the animation actually gets started. + // cancel() doesn't really remove the end action. + .withEndAction(null) + + // TODO(b/364360986): Synchronize the motion with the Keyguard fading if necessary. + } } /** Listener for various events that may affect the status bar's visibility. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt index 9cce2b8fb72b..692e0e4f55f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt @@ -16,23 +16,29 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel +import android.view.View import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor +import com.android.systemui.statusbar.pipeline.shared.domain.interactor.CollapsedStatusBarInteractor +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel.VisibilityModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -80,9 +86,18 @@ interface CollapsedStatusBarViewModel { /** * True if the current scene can show the home status bar (aka this status bar), and false if * the current scene should never show the home status bar. + * + * TODO(b/364360986): Once the is<SomeChildView>Visible flows are fully enabled, we shouldn't + * need this flow anymore. */ val isHomeStatusBarAllowedByScene: StateFlow<Boolean> + val isClockVisible: Flow<VisibilityModel> + val isNotificationIconContainerVisible: Flow<VisibilityModel> + val isSystemInfoVisible: Flow<VisibilityModel> + + // TODO(b/364360986): Add isOngoingActivityChipVisible: Flow<VisibilityModel> + /** * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where * status bar and navigation icons dim. In this mode, a notification dot appears where the @@ -93,17 +108,26 @@ interface CollapsedStatusBarViewModel { * [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE]. */ fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> + + /** Models the current visibility for a specific child view of status bar. */ + data class VisibilityModel( + @View.Visibility val visibility: Int, + /** True if a visibility change should be animated. */ + val shouldAnimateChange: Boolean, + ) } @SysUISingleton class CollapsedStatusBarViewModelImpl @Inject constructor( + collapsedStatusBarInteractor: CollapsedStatusBarInteractor, private val lightsOutInteractor: LightsOutInteractor, private val notificationsInteractor: ActiveNotificationsInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, sceneInteractor: SceneInteractor, sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor, + shadeInteractor: ShadeInteractor, ongoingActivityChipsViewModel: OngoingActivityChipsViewModel, @Application coroutineScope: CoroutineScope, ) : CollapsedStatusBarViewModel { @@ -148,4 +172,59 @@ constructor( } .distinctUntilChanged() } + + /** + * True if the current SysUI state can show the home status bar (aka this status bar), and false + * if we shouldn't be showing any part of the home status bar. + */ + private val isHomeScreenStatusBarAllowedLegacy: Flow<Boolean> = + combine( + keyguardTransitionInteractor.currentKeyguardState, + shadeInteractor.isAnyFullyExpanded, + ) { currentKeyguardState, isShadeExpanded -> + (currentKeyguardState == GONE || currentKeyguardState == OCCLUDED) && !isShadeExpanded + // TODO(b/364360986): Add edge cases, like secure camera launch. + } + + private val isHomeScreenStatusBarAllowed: Flow<Boolean> = + if (SceneContainerFlag.isEnabled) { + isHomeStatusBarAllowedByScene + } else { + isHomeScreenStatusBarAllowedLegacy + } + + override val isClockVisible: Flow<VisibilityModel> = + combine( + isHomeScreenStatusBarAllowed, + collapsedStatusBarInteractor.visibilityViaDisableFlags, + ) { isStatusBarAllowed, visibilityViaDisableFlags -> + val showClock = isStatusBarAllowed && visibilityViaDisableFlags.isClockAllowed + // TODO(b/364360986): Take CollapsedStatusBarFragment.clockHiddenMode into account. + VisibilityModel(showClock.toVisibilityInt(), visibilityViaDisableFlags.animate) + } + override val isNotificationIconContainerVisible: Flow<VisibilityModel> = + combine( + isHomeScreenStatusBarAllowed, + collapsedStatusBarInteractor.visibilityViaDisableFlags, + ) { isStatusBarAllowed, visibilityViaDisableFlags -> + val showNotificationIconContainer = + isStatusBarAllowed && visibilityViaDisableFlags.areNotificationIconsAllowed + VisibilityModel( + showNotificationIconContainer.toVisibilityInt(), + visibilityViaDisableFlags.animate + ) + } + override val isSystemInfoVisible: Flow<VisibilityModel> = + combine( + isHomeScreenStatusBarAllowed, + collapsedStatusBarInteractor.visibilityViaDisableFlags, + ) { isStatusBarAllowed, visibilityViaDisableFlags -> + val showSystemInfo = isStatusBarAllowed && visibilityViaDisableFlags.isSystemInfoAllowed + VisibilityModel(showSystemInfo.toVisibilityInt(), visibilityViaDisableFlags.animate) + } + + @View.Visibility + private fun Boolean.toVisibilityInt(): Int { + return if (this) View.VISIBLE else View.GONE + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt index 1127f6fe3614..caf09a3b638e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -216,7 +216,7 @@ constructor( ) return } - var stateAfter: String + val stateAfter: String if (entry in nextMap) { if (entry in nextMap) nextMap.remove(entry) if (entry in nextList) nextList.remove(entry) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt index 8f424b2e251e..fa9c6b2c8151 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt @@ -17,9 +17,9 @@ package com.android.systemui.statusbar.window import android.app.StatusBarManager -import android.app.StatusBarManager.WindowVisibleState import android.app.StatusBarManager.WINDOW_STATE_SHOWING import android.app.StatusBarManager.WINDOW_STATUS_BAR +import android.app.StatusBarManager.WindowVisibleState import android.app.StatusBarManager.windowStateToString import android.util.Log import com.android.systemui.dagger.SysUISingleton @@ -31,23 +31,27 @@ import javax.inject.Inject /** * A centralized class maintaining the state of the status bar window. * + * @deprecated use + * [com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepository] instead. + * * Classes that want to get updates about the status bar window state should subscribe to this class * via [addListener] and should NOT add their own callback on [CommandQueue]. */ @SysUISingleton -class StatusBarWindowStateController @Inject constructor( - @DisplayId private val thisDisplayId: Int, - commandQueue: CommandQueue -) { - private val commandQueueCallback = object : CommandQueue.Callbacks { - override fun setWindowState( - displayId: Int, - @StatusBarManager.WindowType window: Int, - @WindowVisibleState state: Int - ) { - this@StatusBarWindowStateController.setWindowState(displayId, window, state) +@Deprecated("Use StatusBarWindowRepository instead") +class StatusBarWindowStateController +@Inject +constructor(@DisplayId private val thisDisplayId: Int, commandQueue: CommandQueue) { + private val commandQueueCallback = + object : CommandQueue.Callbacks { + override fun setWindowState( + displayId: Int, + @StatusBarManager.WindowType window: Int, + @WindowVisibleState state: Int, + ) { + this@StatusBarWindowStateController.setWindowState(displayId, window, state) + } } - } private val listeners: MutableSet<StatusBarWindowStateListener> = HashSet() @WindowVisibleState private var windowState: Int = WINDOW_STATE_SHOWING @@ -71,7 +75,7 @@ class StatusBarWindowStateController @Inject constructor( private fun setWindowState( displayId: Int, @StatusBarManager.WindowType window: Int, - @WindowVisibleState state: Int + @WindowVisibleState state: Int, ) { if (displayId != thisDisplayId) { return diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepository.kt new file mode 100644 index 000000000000..678576d1b450 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepository.kt @@ -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.systemui.statusbar.window.data.repository + +import android.app.StatusBarManager +import android.app.StatusBarManager.WINDOW_STATE_HIDDEN +import android.app.StatusBarManager.WINDOW_STATE_HIDING +import android.app.StatusBarManager.WINDOW_STATE_SHOWING +import android.app.StatusBarManager.WINDOW_STATUS_BAR +import android.app.StatusBarManager.WindowVisibleState +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.DisplayId +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.window.data.model.StatusBarWindowState +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +/** + * A centralized class maintaining the state of the status bar window. + * + * Classes that want to get updates about the status bar window state should subscribe to + * [windowState] and should NOT add their own callback on [CommandQueue]. + */ +@SysUISingleton +class StatusBarWindowStateRepository +@Inject +constructor( + private val commandQueue: CommandQueue, + @DisplayId private val thisDisplayId: Int, + @Application private val scope: CoroutineScope, +) { + val windowState: StateFlow<StatusBarWindowState> = + conflatedCallbackFlow { + val callback = + object : CommandQueue.Callbacks { + override fun setWindowState( + displayId: Int, + @StatusBarManager.WindowType window: Int, + @WindowVisibleState state: Int, + ) { + // TODO(b/364360986): Log the window state changes. + if (displayId != thisDisplayId) { + return + } + if (window != WINDOW_STATUS_BAR) { + return + } + trySend(state.toWindowState()) + } + } + commandQueue.addCallback(callback) + awaitClose { commandQueue.removeCallback(callback) } + } + // Use Eagerly because we always need to know about the status bar window state + .stateIn(scope, SharingStarted.Eagerly, StatusBarWindowState.Hidden) + + @WindowVisibleState + private fun Int.toWindowState(): StatusBarWindowState { + return when (this) { + WINDOW_STATE_SHOWING -> StatusBarWindowState.Showing + WINDOW_STATE_HIDING -> StatusBarWindowState.Hiding + WINDOW_STATE_HIDDEN -> StatusBarWindowState.Hidden + else -> StatusBarWindowState.Hidden + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/shared/model/StatusBarWindowState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/shared/model/StatusBarWindowState.kt new file mode 100644 index 000000000000..a99046ee05e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/shared/model/StatusBarWindowState.kt @@ -0,0 +1,27 @@ +/* + * 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.window.data.model + +/** + * Represents the state of the status bar *window* as a whole (as opposed to individual views within + * the status bar). + */ +enum class StatusBarWindowState { + Showing, + Hiding, + Hidden, +} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt index 8ecf250e2bbd..2af84c7e46f0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt @@ -37,7 +37,7 @@ class GlobalCoroutinesModule { @Application fun applicationScope( @Main dispatcherContext: CoroutineContext, - ): CoroutineScope = CoroutineScope(dispatcherContext) + ): CoroutineScope = CoroutineScope(dispatcherContext + createCoroutineTracingContext("ApplicationScope")) @Provides @Singleton @@ -51,15 +51,7 @@ class GlobalCoroutinesModule { @Provides @Singleton @Main - fun mainCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext { - return Dispatchers.Main.immediate + tracingCoroutineContext - } - - @OptIn(ExperimentalCoroutinesApi::class) - @Provides - @Tracing - @Singleton - fun tracingCoroutineContext(): CoroutineContext { - return createCoroutineTracingContext() + fun mainCoroutineContext(): CoroutineContext { + return Dispatchers.Main.immediate } } 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 a03221e03467..3c0682822564 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt @@ -91,10 +91,9 @@ class SysUICoroutinesModule { @Background @SysUISingleton fun bgCoroutineContext( - @Tracing tracingCoroutineContext: CoroutineContext, @Background bgCoroutineDispatcher: CoroutineDispatcher, ): CoroutineContext { - return bgCoroutineDispatcher + tracingCoroutineContext + return bgCoroutineDispatcher } /** Coroutine dispatcher for background operations on for UI. */ @@ -112,9 +111,8 @@ class SysUICoroutinesModule { @UiBackground @SysUISingleton fun uiBgCoroutineContext( - @Tracing tracingCoroutineContext: CoroutineContext, @UiBackground uiBgCoroutineDispatcher: CoroutineDispatcher, ): CoroutineContext { - return uiBgCoroutineDispatcher + tracingCoroutineContext + return uiBgCoroutineDispatcher } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt index 82f41a7fd154..4d9aaa6dc6b0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.util.settings +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.annotation.UserIdInt import android.content.ContentResolver import android.database.ContentObserver @@ -93,7 +94,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-A")).launch { registerContentObserverSync(getUriFor(name), settingsObserver) } @@ -110,7 +111,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-B")).launch { registerContentObserverSync(getUriFor(name), settingsObserver) registered.run() } @@ -143,7 +144,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-C")).launch { registerContentObserverSync(uri, settingsObserver) } @@ -160,7 +161,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-D")).launch { registerContentObserverSync(uri, settingsObserver) registered.run() } @@ -205,7 +206,7 @@ interface SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-E")).launch { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) } @@ -223,7 +224,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-F")).launch { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) registered.run() } @@ -274,7 +275,7 @@ interface SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-G")).launch { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) } @@ -292,7 +293,7 @@ interface SettingsProxy { settingsObserver: ContentObserver, @WorkerThread registered: Runnable ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-H")).launch { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) registered.run() } @@ -329,7 +330,7 @@ interface SettingsProxy { */ @AnyThread fun unregisterContentObserverAsync(settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher).launch { unregisterContentObserver(settingsObserver) } + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-I")).launch { unregisterContentObserver(settingsObserver) } /** * Look up a name in the database. diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt index 8e3b813a2a82..c820c07b61b1 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.util.settings +import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.annotation.UserIdInt import android.annotation.WorkerThread import android.content.ContentResolver @@ -78,7 +79,7 @@ interface UserSettingsProxy : SettingsProxy { } override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-A")).launch { registerContentObserverForUserSync(uri, settingsObserver, userId) } @@ -112,7 +113,7 @@ interface UserSettingsProxy : SettingsProxy { notifyForDescendants: Boolean, settingsObserver: ContentObserver ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-B")).launch { registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } @@ -157,7 +158,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-C")).launch { registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) } @@ -198,7 +199,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-D")).launch { registerContentObserverForUserSync(uri, settingsObserver, userHandle) } @@ -215,7 +216,7 @@ interface UserSettingsProxy : SettingsProxy { userHandle: Int, @WorkerThread registered: Runnable ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-E")).launch { registerContentObserverForUserSync(uri, settingsObserver, userHandle) registered.run() } @@ -274,7 +275,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int ) { - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-F")).launch { registerContentObserverForUserSync( getUriFor(name), notifyForDescendants, @@ -338,7 +339,7 @@ interface UserSettingsProxy : SettingsProxy { settingsObserver: ContentObserver, userHandle: Int ) = - CoroutineScope(backgroundDispatcher).launch { + CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-G")).launch { registerContentObserverForUserSync( uri, notifyForDescendants, diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index b86d57114f85..ab846f143caf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -21,6 +21,7 @@ import android.hardware.input.InputManager import android.hardware.input.KeyGestureEvent import android.os.UserHandle import android.os.UserManager +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.view.KeyEvent @@ -32,6 +33,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase +import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT +import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON import com.android.systemui.settings.FakeUserTracker import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.concurrency.FakeExecutor @@ -62,8 +65,7 @@ import org.mockito.MockitoAnnotations.initMocks @RunWith(AndroidJUnit4::class) internal class NoteTaskInitializerTest : SysuiTestCase() { - @get:Rule - val setFlagsRule = SetFlagsRule() + @get:Rule val setFlagsRule = SetFlagsRule() @Mock lateinit var commandQueue: CommandQueue @Mock lateinit var inputManager: InputManager @@ -83,10 +85,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { whenever(keyguardMonitor.isUserUnlocked(userTracker.userId)).thenReturn(true) } - private fun createUnderTest( - isEnabled: Boolean, - bubbles: Bubbles?, - ): NoteTaskInitializer = + private fun createUnderTest(isEnabled: Boolean, bubbles: Bubbles?): NoteTaskInitializer = NoteTaskInitializer( controller = controller, commandQueue = commandQueue, @@ -104,7 +103,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { code: Int, downTime: Long = 0L, eventTime: Long = 0L, - metaState: Int = 0 + metaState: Int = 0, ): KeyEvent = KeyEvent(downTime, eventTime, action, code, 0 /*repeat*/, metaState) @Test @@ -113,7 +112,6 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { createUnderTest(isEnabled = true, bubbles = bubbles).initialize() - verify(commandQueue).addCallback(any()) verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any()) verify(controller).updateNoteTaskForCurrentUserAndManagedProfiles() verify(keyguardMonitor).registerCallback(any()) @@ -125,7 +123,6 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { createUnderTest(isEnabled = true, bubbles = bubbles).initialize() - verify(commandQueue).addCallback(any()) verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any()) verify(controller, never()).setNoteTaskShortcutEnabled(any(), any()) verify(keyguardMonitor).registerCallback(any()) @@ -165,12 +162,13 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun initialize_handleSystemKey() { val expectedKeyEvent = createKeyEvent( ACTION_DOWN, KEYCODE_N, - metaState = KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON + metaState = KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON, ) val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() @@ -183,22 +181,66 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { @Test @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) - fun initialize_handleKeyGestureEvent() { - val gestureEvent = KeyGestureEvent.Builder() - .setKeycodes(intArrayOf(KeyEvent.KEYCODE_N)) - .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON) - .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES) - .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - .build() + fun handlesShortcut_metaCtrlN() { + val gestureEvent = + KeyGestureEvent.Builder() + .setKeycodes(intArrayOf(KeyEvent.KEYCODE_N)) + .setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES) + .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + .build() val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() - val callback = - withArgCaptor { verify(inputManager).registerKeyGestureEventHandler(capture()) } + val callback = withArgCaptor { + verify(inputManager).registerKeyGestureEventHandler(capture()) + } assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isTrue() executor.runAllReady() - verify(controller).showNoteTask(any()) + verify(controller).showNoteTask(eq(KEYBOARD_SHORTCUT)) + } + + @Test + @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + fun handlesShortcut_stylusTailButton() { + val gestureEvent = + KeyGestureEvent.Builder() + .setKeycodes(intArrayOf(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL)) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES) + .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + .build() + val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) + underTest.initialize() + val callback = withArgCaptor { + verify(inputManager).registerKeyGestureEventHandler(capture()) + } + + assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isTrue() + + executor.runAllReady() + verify(controller).showNoteTask(eq(TAIL_BUTTON)) + } + + @Test + @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + fun ignoresUnrelatedShortcuts() { + val gestureEvent = + KeyGestureEvent.Builder() + .setKeycodes(intArrayOf(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL)) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + .build() + val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) + underTest.initialize() + val callback = withArgCaptor { + verify(inputManager).registerKeyGestureEventHandler(capture()) + } + + assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isFalse() + + executor.runAllReady() + verify(controller, never()).showNoteTask(any()) } @Test @@ -249,6 +291,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun tailButtonGestureDetection_singlePress_shouldShowNoteTaskOnUp() { val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() @@ -267,6 +310,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun tailButtonGestureDetection_doublePress_shouldNotShowNoteTaskTwice() { val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() @@ -289,6 +333,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) fun tailButtonGestureDetection_longPress_shouldNotShowNoteTask() { val underTest = createUnderTest(isEnabled = true, bubbles = bubbles) underTest.initialize() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt index d2dfc9257e7e..907c68440b55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/data/repository/DisableFlagsRepositoryTest.kt @@ -17,16 +17,19 @@ package com.android.systemui.statusbar.disableflags.data.repository import android.app.StatusBarManager.DISABLE2_NONE import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS +import android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS import android.app.StatusBarManager.DISABLE_CLOCK import android.app.StatusBarManager.DISABLE_NONE import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS +import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS +import android.app.StatusBarManager.DISABLE_SYSTEM_INFO import android.content.res.Configuration import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.log.LogBufferFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.disableflags.DisableFlagsLogger import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel @@ -82,7 +85,7 @@ class DisableFlagsRepositoryTest : SysuiTestCase() { @Test fun disableFlags_initialValue_none() { assertThat(underTest.disableFlags.value) - .isEqualTo(DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)) + .isEqualTo(DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = false)) } @Test @@ -182,12 +185,7 @@ class DisableFlagsRepositoryTest : SysuiTestCase() { fun disableFlags_quickSettingsDisabled_quickSettingsEnabledFalse() = testScope.runTest { getCommandQueueCallback() - .disable( - DISPLAY_ID, - DISABLE_NONE, - DISABLE2_QUICK_SETTINGS, - /* animate= */ false, - ) + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_QUICK_SETTINGS, /* animate= */ false) assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse() } @@ -217,21 +215,84 @@ class DisableFlagsRepositoryTest : SysuiTestCase() { configuration.orientation = Configuration.ORIENTATION_LANDSCAPE mContext.orCreateTestableResources.addOverride( R.bool.config_use_split_notification_shade, - /* value= */ false + /* value= */ false, ) remoteInputQuickSettingsDisabler.setRemoteInputActive(true) remoteInputQuickSettingsDisabler.onConfigChanged(configuration) getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false) + + // THEN quick settings is disabled (even if the disable flags don't say so) + assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse() + } + + @Test + fun disableFlags_clockDisabled() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_CLOCK, DISABLE2_NONE, /* animate= */ false) + + assertThat(underTest.disableFlags.value.isClockEnabled).isFalse() + } + + @Test + fun disableFlags_clockEnabled() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false) + + assertThat(underTest.disableFlags.value.isClockEnabled).isTrue() + } + + @Test + fun disableFlags_notificationIconsDisabled() = + testScope.runTest { + getCommandQueueCallback() .disable( DISPLAY_ID, - DISABLE_NONE, + DISABLE_NOTIFICATION_ICONS, DISABLE2_NONE, /* animate= */ false, ) - // THEN quick settings is disabled (even if the disable flags don't say so) - assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse() + assertThat(underTest.disableFlags.value.areNotificationIconsEnabled).isFalse() + } + + @Test + fun disableFlags_notificationIconsEnabled() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false) + + assertThat(underTest.disableFlags.value.areNotificationIconsEnabled).isTrue() + } + + @Test + fun disableFlags_systemInfoDisabled_viaDisable1() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_SYSTEM_INFO, DISABLE2_NONE, /* animate= */ false) + + assertThat(underTest.disableFlags.value.isSystemInfoEnabled).isFalse() + } + + @Test + fun disableFlags_systemInfoDisabled_viaDisable2() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_SYSTEM_ICONS, /* animate= */ false) + + assertThat(underTest.disableFlags.value.isSystemInfoEnabled).isFalse() + } + + @Test + fun disableFlags_systemInfoEnabled() = + testScope.runTest { + getCommandQueueCallback() + .disable(DISPLAY_ID, DISABLE_NONE, DISABLE2_NONE, /* animate= */ false) + + assertThat(underTest.disableFlags.value.isSystemInfoEnabled).isTrue() } @Test @@ -267,6 +328,34 @@ class DisableFlagsRepositoryTest : SysuiTestCase() { assertThat(underTest.disableFlags.value.isQuickSettingsEnabled()).isFalse() } + @Test + fun disableFlags_animateFalse() = + testScope.runTest { + getCommandQueueCallback() + .disable( + DISPLAY_ID, + DISABLE_NOTIFICATION_ALERTS, + DISABLE2_NONE, + /* animate= */ false, + ) + + assertThat(underTest.disableFlags.value.animate).isFalse() + } + + @Test + fun disableFlags_animateTrue() = + testScope.runTest { + getCommandQueueCallback() + .disable( + DISPLAY_ID, + DISABLE_NOTIFICATION_ALERTS, + DISABLE2_NONE, + /* animate= */ true, + ) + + assertThat(underTest.disableFlags.value.animate).isTrue() + } + private fun getCommandQueueCallback(): CommandQueue.Callbacks { val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>() verify(commandQueue).addCallback(callbackCaptor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 135fab877d57..63a560ffd2c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -18,6 +18,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS; import static com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS; +import static com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN; @@ -157,6 +158,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testDisableNone() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -167,6 +169,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testDisableSystemInfo_systemAnimationIdle_doesHide() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -184,6 +187,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -213,6 +217,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -228,8 +233,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } - @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -245,6 +250,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -268,6 +274,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testDisableNotifications() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -285,6 +292,25 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + public void testDisableNotifications_doesNothingWhenFlagEnabled() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); + + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + + fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); + + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + } + + @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testDisableClock() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -302,7 +328,26 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) + public void testDisableClock_doesNothingWhenFlagEnabled() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false); + + assertEquals(View.VISIBLE, getClockView().getVisibility()); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.VISIBLE, getClockView().getVisibility()); + + fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false); + + assertEquals(View.VISIBLE, getClockView().getVisibility()); + } + + @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_shadeOpenAndShouldHide_everythingHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -320,6 +365,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_shadeOpenButNotShouldHide_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -338,6 +384,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { /** Regression test for b/279790651. */ @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -365,6 +412,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_notTransitioningToOccluded_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -380,6 +428,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_isTransitioningToOccluded_everythingHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -395,6 +444,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -425,7 +475,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_noOngoingCall_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -437,7 +487,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -450,7 +500,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -463,7 +513,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_hasOngoingCallButAlsoHun_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -476,7 +526,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_ongoingCallEnded_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -500,7 +550,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating @@ -517,7 +567,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void screenSharingChipsDisabled_ignoresNewCallback() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -551,6 +601,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void noOngoingActivity_chipHidden() { resumeAndGetFragment(); @@ -568,6 +619,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() { resumeAndGetFragment(); @@ -581,8 +633,36 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @EnableFlags({ + FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, + FLAG_STATUS_BAR_RON_CHIPS, + FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void hasPrimaryOngoingActivity_viewsUnchangedWhenSimpleFragmentFlagOn() { + resumeAndGetFragment(); + + assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasPrimaryOngoingActivity= */ true, + /* hasSecondaryOngoingActivity= */ false, + /* shouldAnimate= */ false); + + assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + + mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( + /* hasPrimaryOngoingActivity= */ false, + /* hasSecondaryOngoingActivity= */ false, + /* shouldAnimate= */ false); + + assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + } + + @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void hasSecondaryOngoingActivity_butRonsFlagOff_secondaryChipHidden() { resumeAndGetFragment(); @@ -596,6 +676,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() { resumeAndGetFragment(); @@ -610,7 +691,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_ronsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -627,6 +708,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_ronsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -644,7 +726,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void hasOngoingActivityButAlsoHun_chipHidden_ronsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -661,6 +743,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasOngoingActivitiesButAlsoHun_chipsHidden_ronsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -678,7 +761,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void primaryOngoingActivityEnded_chipHidden_ronsFlagOff() { resumeAndGetFragment(); @@ -701,6 +784,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void primaryOngoingActivityEnded_chipHidden_ronsFlagOn() { resumeAndGetFragment(); @@ -723,6 +807,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void secondaryOngoingActivityEnded_chipHidden() { resumeAndGetFragment(); @@ -745,7 +830,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating @@ -764,6 +849,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating @@ -782,7 +868,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -815,6 +901,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -848,6 +935,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void isHomeStatusBarAllowedByScene_false_everythingHidden() { resumeAndGetFragment(); @@ -861,6 +949,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void isHomeStatusBarAllowedByScene_true_everythingShown() { resumeAndGetFragment(); @@ -874,6 +963,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -891,6 +981,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -908,6 +999,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() { resumeAndGetFragment(); @@ -921,6 +1013,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_isDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); @@ -932,6 +1025,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_NotDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(false); @@ -943,6 +1037,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_headsUpShouldBeVisibleTrue_clockDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); @@ -953,6 +1048,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false); @@ -1006,6 +1102,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testStatusBarIcons_hiddenThroughoutCameraLaunch() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -1028,6 +1125,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @DisableSceneContainer + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -1063,6 +1161,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt index 997c00cf49a4..c435d3d99680 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt @@ -16,10 +16,12 @@ package com.android.systemui.statusbar.phone.fragment +import android.platform.test.annotations.DisableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule import junit.framework.Assert.assertEquals @@ -36,6 +38,7 @@ private const val INITIAL_ALPHA = 1f @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest +@DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) class MultiSourceMinAlphaControllerTest : SysuiTestCase() { private val view = View(context) @@ -60,7 +63,7 @@ class MultiSourceMinAlphaControllerTest : SysuiTestCase() { multiSourceMinAlphaController.animateToAlpha( alpha = 0.5f, sourceId = TEST_SOURCE_1, - duration = TEST_ANIMATION_DURATION + duration = TEST_ANIMATION_DURATION, ) animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION) assertEquals(0.5f, view.alpha) @@ -71,7 +74,7 @@ class MultiSourceMinAlphaControllerTest : SysuiTestCase() { multiSourceMinAlphaController.animateToAlpha( alpha = 0.5f, sourceId = TEST_SOURCE_1, - duration = TEST_ANIMATION_DURATION + duration = TEST_ANIMATION_DURATION, ) multiSourceMinAlphaController.setAlpha(alpha = 0.7f, sourceId = TEST_SOURCE_2) multiSourceMinAlphaController.reset() @@ -94,7 +97,7 @@ class MultiSourceMinAlphaControllerTest : SysuiTestCase() { multiSourceMinAlphaController.animateToAlpha( alpha = 0f, sourceId = TEST_SOURCE_1, - duration = TEST_ANIMATION_DURATION + duration = TEST_ANIMATION_DURATION, ) animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2) multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt new file mode 100644 index 000000000000..5036e775211e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorTest.kt @@ -0,0 +1,94 @@ +/* + * 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.pipeline.shared.domain.interactor + +import android.app.StatusBarManager.DISABLE2_NONE +import android.app.StatusBarManager.DISABLE_CLOCK +import android.app.StatusBarManager.DISABLE_NONE +import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS +import android.app.StatusBarManager.DISABLE_SYSTEM_INFO +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel +import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.test.runTest + +@SmallTest +class CollapsedStatusBarInteractorTest : SysuiTestCase() { + val kosmos = testKosmos() + val testScope = kosmos.testScope + val disableFlagsRepo = kosmos.fakeDisableFlagsRepository + + val underTest = kosmos.collapsedStatusBarInteractor + + @Test + fun visibilityViaDisableFlags_allDisabled() = + testScope.runTest { + val latest by collectLastValue(underTest.visibilityViaDisableFlags) + + disableFlagsRepo.disableFlags.value = + DisableFlagsModel( + DISABLE_CLOCK or DISABLE_NOTIFICATION_ICONS or DISABLE_SYSTEM_INFO, + DISABLE2_NONE, + animate = false, + ) + + assertThat(latest!!.isClockAllowed).isFalse() + assertThat(latest!!.areNotificationIconsAllowed).isFalse() + assertThat(latest!!.isSystemInfoAllowed).isFalse() + } + + @Test + fun visibilityViaDisableFlags_allEnabled() = + testScope.runTest { + val latest by collectLastValue(underTest.visibilityViaDisableFlags) + + disableFlagsRepo.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = false) + + assertThat(latest!!.isClockAllowed).isTrue() + assertThat(latest!!.areNotificationIconsAllowed).isTrue() + assertThat(latest!!.isSystemInfoAllowed).isTrue() + } + + @Test + fun visibilityViaDisableFlags_animateFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.visibilityViaDisableFlags) + + disableFlagsRepo.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = false) + + assertThat(latest!!.animate).isFalse() + } + + @Test + fun visibilityViaDisableFlags_animateTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.visibilityViaDisableFlags) + + disableFlagsRepo.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE, animate = true) + + assertThat(latest!!.animate).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 7ae6ea51b912..bd857807851c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -16,21 +16,27 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel +import android.app.StatusBarManager.DISABLE2_NONE +import android.app.StatusBarManager.DISABLE_CLOCK +import android.app.StatusBarManager.DISABLE_NONE +import android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS +import android.app.StatusBarManager.DISABLE_SYSTEM_INFO import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -38,27 +44,25 @@ import com.android.systemui.log.assertLogsWtf import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository import com.android.systemui.scene.data.repository.sceneContainerRepository -import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor -import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsScreenRecordChip import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip -import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel import com.android.systemui.statusbar.data.model.StatusBarMode import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository +import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel +import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository -import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor -import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow @@ -83,21 +87,15 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository private val activeNotificationListRepository = kosmos.activeNotificationListRepository private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val disableFlagsRepository = kosmos.fakeDisableFlagsRepository - private val underTest = - CollapsedStatusBarViewModelImpl( - kosmos.lightsOutInteractor, - kosmos.activeNotificationsInteractor, - kosmos.keyguardTransitionInteractor, - kosmos.sceneInteractor, - kosmos.sceneContainerOcclusionInteractor, - kosmos.ongoingActivityChipsViewModel, - kosmos.applicationCoroutineScope, - ) + private lateinit var underTest: CollapsedStatusBarViewModel @Before fun setUp() { setUpPackageManagerForMediaProjection(kosmos) + // Initialize here because some flags are checked when this class is constructed + underTest = kosmos.collapsedStatusBarViewModel } @Test @@ -495,14 +493,272 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { assertThat(latest).isTrue() } + @Test + fun isClockVisible_allowedByDisableFlags_visible() = + testScope.runTest { + val latest by collectLastValue(underTest.isClockVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun isClockVisible_notAllowedByDisableFlags_gone() = + testScope.runTest { + val latest by collectLastValue(underTest.isClockVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_CLOCK, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.GONE) + } + + @Test + fun isNotificationIconContainerVisible_allowedByDisableFlags_visible() = + testScope.runTest { + val latest by collectLastValue(underTest.isNotificationIconContainerVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun isNotificationIconContainerVisible_notAllowedByDisableFlags_gone() = + testScope.runTest { + val latest by collectLastValue(underTest.isNotificationIconContainerVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_NOTIFICATION_ICONS, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.GONE) + } + + @Test + fun isSystemInfoVisible_allowedByDisableFlags_visible() = + testScope.runTest { + val latest by collectLastValue(underTest.isSystemInfoVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + fun isSystemInfoVisible_notAllowedByDisableFlags_gone() = + testScope.runTest { + val latest by collectLastValue(underTest.isSystemInfoVisible) + transitionKeyguardToGone() + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(DISABLE_SYSTEM_INFO, DISABLE2_NONE) + + assertThat(latest!!.visibility).isEqualTo(View.GONE) + } + + @Test + @DisableSceneContainer + fun lockscreenVisible_sceneFlagOff_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + testScope = this, + ) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + + @Test + @EnableSceneContainer + fun lockscreenVisible_sceneFlagOn_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + + @Test + @DisableSceneContainer + fun bouncerVisible_sceneFlagOff_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + testScope = this, + ) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + + @Test + @EnableSceneContainer + fun bouncerVisible_sceneFlagOn_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + + @Test + @DisableSceneContainer + fun keyguardIsOccluded_sceneFlagOff_statusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + testScope = this, + ) + + assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @EnableSceneContainer + fun keyguardIsOccluded_sceneFlagOn_statusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen) + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null) + + assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @DisableSceneContainer + fun keyguardNotShown_sceneFlagOff_statusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + transitionKeyguardToGone() + + assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @DisableSceneContainer + fun shadeNotShown_sceneFlagOff_statusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + transitionKeyguardToGone() + + kosmos.shadeTestUtil.setShadeExpansion(0f) + + assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @EnableSceneContainer + fun keyguardNotShownAndShadeNotShown_sceneFlagOn_statusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Gone) + + assertThat(clockVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.VISIBLE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.VISIBLE) + } + + @Test + @DisableSceneContainer + fun shadeShown_sceneFlagOff_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + transitionKeyguardToGone() + + kosmos.shadeTestUtil.setShadeExpansion(1f) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + + @Test + @EnableSceneContainer + fun shadeShown_sceneFlagOn_noStatusBarViewsShown() = + testScope.runTest { + val clockVisible by collectLastValue(underTest.isClockVisible) + val notifIconsVisible by collectLastValue(underTest.isNotificationIconContainerVisible) + val systemInfoVisible by collectLastValue(underTest.isSystemInfoVisible) + transitionKeyguardToGone() + + kosmos.sceneContainerRepository.snapToScene(Scenes.Shade) + + assertThat(clockVisible!!.visibility).isEqualTo(View.GONE) + assertThat(notifIconsVisible!!.visibility).isEqualTo(View.GONE) + assertThat(systemInfoVisible!!.visibility).isEqualTo(View.GONE) + } + private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) = ActiveNotificationsStore.Builder() .apply { notifications.forEach(::addIndividualNotif) } .build() private val testNotifications = - listOf( - activeNotificationModel(key = "notif1"), - activeNotificationModel(key = "notif2"), + listOf(activeNotificationModel(key = "notif1"), activeNotificationModel(key = "notif2")) + + private suspend fun transitionKeyguardToGone() { + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope = testScope, ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt index 4834d367d4be..cc90c1167ef1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel +import android.view.View import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import kotlinx.coroutines.flow.Flow @@ -36,9 +37,29 @@ class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel { override val isHomeStatusBarAllowedByScene = MutableStateFlow(false) - override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut + override val isClockVisible = + MutableStateFlow( + CollapsedStatusBarViewModel.VisibilityModel( + visibility = View.GONE, + shouldAnimateChange = false, + ) + ) + + override val isNotificationIconContainerVisible = + MutableStateFlow( + CollapsedStatusBarViewModel.VisibilityModel( + visibility = View.GONE, + shouldAnimateChange = false, + ) + ) - fun setNotificationLightsOut(lightsOut: Boolean) { - areNotificationLightsOut.value = lightsOut - } + override val isSystemInfoVisible = + MutableStateFlow( + CollapsedStatusBarViewModel.VisibilityModel( + visibility = View.GONE, + shouldAnimateChange = false, + ) + ) + + override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryTest.kt new file mode 100644 index 000000000000..38e04bb1d00f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2021 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.window.data.repository + +import android.app.StatusBarManager.WINDOW_NAVIGATION_BAR +import android.app.StatusBarManager.WINDOW_STATE_HIDDEN +import android.app.StatusBarManager.WINDOW_STATE_HIDING +import android.app.StatusBarManager.WINDOW_STATE_SHOWING +import android.app.StatusBarManager.WINDOW_STATUS_BAR +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.commandQueue +import com.android.systemui.statusbar.window.data.model.StatusBarWindowState +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.mockito.Mockito.verify +import org.mockito.kotlin.argumentCaptor + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class StatusBarWindowStateRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val commandQueue = kosmos.commandQueue + private val underTest = + StatusBarWindowStateRepository(commandQueue, DISPLAY_ID, testScope.backgroundScope) + + private val callback: CommandQueue.Callbacks + get() { + testScope.runCurrent() + val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>() + verify(commandQueue).addCallback(callbackCaptor.capture()) + return callbackCaptor.firstValue + } + + @Test + fun windowState_notSameDisplayId_notUpdated() = + testScope.runTest { + val latest by collectLastValue(underTest.windowState) + assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) + + callback.setWindowState(DISPLAY_ID + 1, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) + + assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) + } + + @Test + fun windowState_notStatusBarWindow_notUpdated() = + testScope.runTest { + val latest by collectLastValue(underTest.windowState) + assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) + + callback.setWindowState(DISPLAY_ID, WINDOW_NAVIGATION_BAR, WINDOW_STATE_SHOWING) + + assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) + } + + @Test + fun windowState_showing_updated() = + testScope.runTest { + val latest by collectLastValue(underTest.windowState) + + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) + + assertThat(latest).isEqualTo(StatusBarWindowState.Showing) + } + + @Test + fun windowState_hiding_updated() = + testScope.runTest { + val latest by collectLastValue(underTest.windowState) + + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_HIDING) + + assertThat(latest).isEqualTo(StatusBarWindowState.Hiding) + } + + @Test + fun windowState_hidden_updated() = + testScope.runTest { + val latest by collectLastValue(underTest.windowState) + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING) + assertThat(latest).isEqualTo(StatusBarWindowState.Showing) + + callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_HIDDEN) + + assertThat(latest).isEqualTo(StatusBarWindowState.Hidden) + } +} + +private const val DISPLAY_ID = 10 diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt index 81242244b7a6..3d4136252ca4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt @@ -16,9 +16,11 @@ package com.android.systemui.communal.domain.interactor +import android.service.dream.dreamManager import com.android.systemui.common.usagestats.domain.interactor.usageStatsInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.activityStarter import com.android.systemui.shared.system.taskStackChangeListeners @@ -32,6 +34,8 @@ val Kosmos.widgetTrampolineInteractor: WidgetTrampolineInteractor by keyguardTransitionInteractor = keyguardTransitionInteractor, taskStackChangeListeners = taskStackChangeListeners, usageStatsInteractor = usageStatsInteractor, + dreamManager = dreamManager, + bgScope = applicationCoroutineScope, logBuffer = logcatLogBuffer("WidgetTrampolineInteractor"), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt index 574bbcd6106c..e2b283b06562 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt @@ -39,5 +39,6 @@ val Kosmos.keyguardDismissActionInteractor by powerInteractor = powerInteractor, alternateBouncerInteractor = alternateBouncerInteractor, shadeInteractor = { shadeInteractor }, + keyguardInteractor = { keyguardInteractor }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.kt new file mode 100644 index 000000000000..385a813996ff --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/domain/interactor/CollapsedStatusBarInteractorKosmos.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.statusbar.pipeline.shared.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository + +val Kosmos.collapsedStatusBarInteractor: CollapsedStatusBarInteractor by + Kosmos.Fixture { CollapsedStatusBarInteractor(fakeDisableFlagsRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.kt new file mode 100644 index 000000000000..1c7fd4817498 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelKosmos.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.statusbar.pipeline.shared.ui.viewmodel + +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor +import com.android.systemui.statusbar.phone.domain.interactor.lightsOutInteractor +import com.android.systemui.statusbar.pipeline.shared.domain.interactor.collapsedStatusBarInteractor + +val Kosmos.collapsedStatusBarViewModel: CollapsedStatusBarViewModel by + Kosmos.Fixture { + CollapsedStatusBarViewModelImpl( + collapsedStatusBarInteractor, + lightsOutInteractor, + activeNotificationsInteractor, + keyguardTransitionInteractor, + sceneInteractor, + sceneContainerOcclusionInteractor, + shadeInteractor, + ongoingActivityChipsViewModel, + applicationCoroutineScope, + ) + } diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index 93cdde0d9a20..75d07bb80c05 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -1037,14 +1037,12 @@ public class CameraExtensionsProxyService extends Service { @Override public void onCaptureFailed(int captureSequenceId, int reason) { - if (Flags.concertMode()) { - if (mCaptureCallback != null) { - try { - mCaptureCallback.onCaptureProcessFailed(captureSequenceId, reason); - } catch (RemoteException e) { - Log.e(TAG, "Failed to notify capture failure due to remote " + - "exception!"); - } + if (mCaptureCallback != null) { + try { + mCaptureCallback.onCaptureProcessFailed(captureSequenceId, reason); + } catch (RemoteException e) { + Log.e(TAG, "Failed to notify capture failure due to remote " + + "exception!"); } } } diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index 241726283c52..90bb93de3bc4 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -16,12 +16,11 @@ package android.platform.test.ravenwood; +import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP; -import static org.junit.Assert.fail; - import android.app.ActivityManager; import android.app.Instrumentation; import android.app.ResourcesManager; @@ -211,23 +210,21 @@ public class RavenwoodRuntimeEnvironmentController { var file = new File(RAVENWOOD_RESOURCE_APK); return config.mState.loadResources(file.exists() ? file : null); }; - // Set up test context's resources. + + // Set up test context's (== instrumentation context's) resources. // If the target package name == test package name, then we use the main resources. - // Otherwise, we don't simulate loading resources from the test APK yet. - // (we need to add `test_resource_apk` to `android_ravenwood_test`) - final Supplier<Resources> testResourcesLoader; + final Supplier<Resources> instResourcesLoader; if (isSelfInstrumenting) { - testResourcesLoader = targetResourcesLoader; + instResourcesLoader = targetResourcesLoader; } else { - testResourcesLoader = () -> { - fail("Cannot load resources from the test context (yet)." - + " Use target context's resources instead."); - return null; // unreachable. + instResourcesLoader = () -> { + var file = new File(RAVENWOOD_INST_RESOURCE_APK); + return config.mState.loadResources(file.exists() ? file : null); }; } - var testContext = new RavenwoodContext( - config.mTestPackageName, main, testResourcesLoader); + var instContext = new RavenwoodContext( + config.mTestPackageName, main, instResourcesLoader); var targetContext = new RavenwoodContext( config.mTargetPackageName, main, targetResourcesLoader); @@ -236,18 +233,18 @@ public class RavenwoodRuntimeEnvironmentController { config.mTargetPackageName, main, targetResourcesLoader); appContext.setApplicationContext(appContext); if (isSelfInstrumenting) { - testContext.setApplicationContext(appContext); + instContext.setApplicationContext(appContext); targetContext.setApplicationContext(appContext); } else { // When instrumenting into another APK, the test context doesn't have an app context. targetContext.setApplicationContext(appContext); } - config.mTestContext = testContext; + config.mInstContext = instContext; config.mTargetContext = targetContext; // Prepare other fields. config.mInstrumentation = new Instrumentation(); - config.mInstrumentation.basicInit(config.mTestContext, config.mTargetContext); + config.mInstrumentation.basicInit(config.mInstContext, config.mTargetContext); InstrumentationRegistry.registerInstance(config.mInstrumentation, Bundle.EMPTY); RavenwoodSystemServer.init(config); @@ -284,13 +281,13 @@ public class RavenwoodRuntimeEnvironmentController { InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); config.mInstrumentation = null; - if (config.mTestContext != null) { - ((RavenwoodContext) config.mTestContext).cleanUp(); + if (config.mInstContext != null) { + ((RavenwoodContext) config.mInstContext).cleanUp(); } if (config.mTargetContext != null) { ((RavenwoodContext) config.mTargetContext).cleanUp(); } - config.mTestContext = null; + config.mInstContext = null; config.mTargetContext = null; if (config.mProvideMainThread) { diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java index d4090e26223a..3946dd8471b0 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java @@ -67,7 +67,7 @@ public class RavenwoodSystemServer { sStartedServices = new ArraySet<>(); sTimings = new TimingsTraceAndSlog(); - sServiceManager = new SystemServiceManager(config.mTestContext); + sServiceManager = new SystemServiceManager(config.mInstContext); sServiceManager.setStartInfo(false, SystemClock.elapsedRealtime(), SystemClock.uptimeMillis()); diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java index 5d251bdafd44..5ba972df1193 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -167,6 +167,7 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable return runner; } + private final Class<?> mTestJavaClass; private TestClass mTestClass = null; private Runner mRealRunner = null; private Description mDescription = null; @@ -192,6 +193,7 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable * Constructor. */ public RavenwoodAwareTestRunner(Class<?> testClass) { + mTestJavaClass = testClass; try { performGlobalInitialization(); @@ -320,7 +322,7 @@ public final class RavenwoodAwareTestRunner extends Runner implements Filterable return; } - Log.v(TAG, "Starting " + mTestClass.getJavaClass().getCanonicalName()); + Log.v(TAG, "Starting " + mTestJavaClass.getCanonicalName()); if (RAVENWOOD_VERBOSE_LOGGING) { dumpDescription(getDescription()); } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java index ea33aa690173..446f819ad41b 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java @@ -74,7 +74,7 @@ public final class RavenwoodConfig { final List<Class<?>> mServicesRequired = new ArrayList<>(); - volatile Context mTestContext; + volatile Context mInstContext; volatile Context mTargetContext; volatile Instrumentation mInstrumentation; diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index 984106b21e9a..4196d8e22610 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -216,7 +216,7 @@ public final class RavenwoodRule implements TestRule { */ @Deprecated public Context getContext() { - return Objects.requireNonNull(mConfiguration.mTestContext, + return Objects.requireNonNull(mConfiguration.mInstContext, "Context is only available during @Test execution"); } diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java index 96746c679020..989bb6be1782 100644 --- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java +++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java @@ -63,6 +63,8 @@ public class RavenwoodCommonUtils { public static final String RAVENWOOD_SYSPROP = "ro.is_on_ravenwood"; public static final String RAVENWOOD_RESOURCE_APK = "ravenwood-res-apks/ravenwood-res.apk"; + public static final String RAVENWOOD_INST_RESOURCE_APK = + "ravenwood-res-apks/ravenwood-inst-res.apk"; public static final String RAVENWOOD_EMPTY_RESOURCES_APK = RAVENWOOD_RUNTIME_PATH + "ravenwood-data/ravenwood-empty-res.apk"; diff --git a/ravenwood/tests/bivalentinst/Android.bp b/ravenwood/tests/bivalentinst/Android.bp index 38d1b299b002..41e45e5a6d95 100644 --- a/ravenwood/tests/bivalentinst/Android.bp +++ b/ravenwood/tests/bivalentinst/Android.bp @@ -27,8 +27,7 @@ android_ravenwood_test { "junit", "truth", ], - // TODO(b/366246777) uncomment it and the test. - // resource_apk: "RavenwoodBivalentInstTest_self_inst_device", + resource_apk: "RavenwoodBivalentInstTest_self_inst_device", auto_gen_config: true, } @@ -53,8 +52,8 @@ android_ravenwood_test { "junit", "truth", ], - // TODO(b/366246777) uncomment it and the test. - // resource_apk: "RavenwoodBivalentInstTestTarget", + resource_apk: "RavenwoodBivalentInstTestTarget", + inst_resource_apk: "RavenwoodBivalentInstTest_nonself_inst_device", auto_gen_config: true, } diff --git a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java index 9f3ca6ffcd26..92d43d714e14 100644 --- a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java +++ b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_nonself.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import android.app.Instrumentation; import android.content.Context; -import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.ravenwood.RavenwoodConfig; import android.platform.test.ravenwood.RavenwoodConfig.Config; @@ -97,7 +96,6 @@ public class RavenwoodInstrumentationTest_nonself { } @Test - @DisabledOnRavenwood(reason = "b/366246777") public void testTargetAppResource() { assertThat(sTargetContext.getString( com.android.ravenwood.bivalentinst_target_app.R.string.test_string_in_target)) @@ -105,8 +103,6 @@ public class RavenwoodInstrumentationTest_nonself { } @Test - @DisabledOnRavenwood( - reason = "Loading resources from non-self-instrumenting test APK isn't supported yet") public void testTestAppResource() { assertThat(sTestContext.getString( com.android.ravenwood.bivalentinsttest_nonself_inst.R.string.test_string_in_test)) diff --git a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java index fdff22210c16..2f35923dead2 100644 --- a/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java +++ b/ravenwood/tests/bivalentinst/test/com/android/ravenwoodtest/bivalentinst/RavenwoodInstrumentationTest_self.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import android.app.Instrumentation; import android.content.Context; -import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.ravenwood.RavenwoodConfig; import android.platform.test.ravenwood.RavenwoodConfig.Config; @@ -109,7 +108,6 @@ public class RavenwoodInstrumentationTest_self { } @Test - @DisabledOnRavenwood(reason = "b/366246777") public void testTargetAppResource() { assertThat(sTargetContext.getString( com.android.ravenwood.bivalentinsttest_self_inst.R.string.test_string_in_test)) @@ -117,7 +115,6 @@ public class RavenwoodInstrumentationTest_self { } @Test - @DisabledOnRavenwood(reason = "b/366246777") public void testTestAppResource() { assertThat(sTestContext.getString( com.android.ravenwood.bivalentinsttest_self_inst.R.string.test_string_in_test)) diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java index 09ed12d49cea..bd013133d3a4 100644 --- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java +++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerCallbackTest.java @@ -33,6 +33,7 @@ import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runner.RunWith; import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import java.util.ArrayList; @@ -365,14 +366,14 @@ public class RavenwoodRunnerCallbackTest extends RavenwoodRunnerTestBase { @Expected(""" testRunStarted: classes testSuiteStarted: classes - testSuiteStarted: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest) - testIgnored: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest) - testSuiteFinished: ClassUnloadbleTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleTest) + testSuiteStarted: ClassUnloadbleAndDisabledTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndDisabledTest) + testIgnored: ClassUnloadbleAndDisabledTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndDisabledTest) + testSuiteFinished: ClassUnloadbleAndDisabledTest(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndDisabledTest) testSuiteFinished: classes testRunFinished: 0,0,0,1 """) // CHECKSTYLE:ON - public static class ClassUnloadbleTest { + public static class ClassUnloadbleAndDisabledTest { static { Assert.fail("Class unloadable!"); } @@ -385,4 +386,73 @@ public class RavenwoodRunnerCallbackTest extends RavenwoodRunnerTestBase { public void test2() { } } + + /** + * The test class is unloadable, but has a @DisabledOnRavenwood. + */ + @RunWith(AndroidJUnit4.class) + // CHECKSTYLE:OFF + @Expected(""" + testRunStarted: classes + testSuiteStarted: classes + testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest + testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest + testStarted: test1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest) + testFailure: Class unloadable! + testFinished: test1(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest) + testStarted: test2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest) + testFailure: Class unloadable! + testFinished: test2(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$ClassUnloadbleAndEnabledTest) + testSuiteFinished: classes + testRunFinished: 2,2,0,0 + """) + // CHECKSTYLE:ON + public static class ClassUnloadbleAndEnabledTest { + static { + Assert.fail("Class unloadable!"); + } + + @Test + public void test1() { + } + + @Test + public void test2() { + } + } + + public static class BrokenTestRunner extends BlockJUnit4ClassRunner { + public BrokenTestRunner(Class<?> testClass) throws InitializationError { + super(testClass); + + if (true) { + throw new RuntimeException("This is a broken test runner!"); + } + } + } + + /** + * The test runner throws an exception from the ctor. + */ + @RunWith(BrokenTestRunner.class) + // CHECKSTYLE:OFF + @Expected(""" + testRunStarted: classes + testSuiteStarted: classes + testStarted: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BrokenRunnerTest) + testFailure: Exception detected in constructor + testFinished: Constructor(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerCallbackTest$BrokenRunnerTest) + testSuiteFinished: classes + testRunFinished: 1,1,0,0 + """) + // CHECKSTYLE:ON + public static class BrokenRunnerTest { + @Test + public void test1() { + } + + @Test + public void test2() { + } + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index 165a9452fb0a..1e723b5a1da2 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -204,9 +204,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { serviceIntent, targetUser, safeExecuteAppFunctionCallback, - /* bindFlags= */ Context.BIND_AUTO_CREATE, - /* timeoutInMillis= */ mServiceConfig - .getExecuteAppFunctionTimeoutMillis()); + /* bindFlags= */ Context.BIND_AUTO_CREATE); }) .exceptionally( ex -> { @@ -221,13 +219,11 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @NonNull Intent serviceIntent, @NonNull UserHandle targetUser, @NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback, - int bindFlags, - long timeoutInMillis) { + int bindFlags) { boolean bindServiceResult = mRemoteServiceCaller.runServiceCall( serviceIntent, bindFlags, - timeoutInMillis, targetUser, new RunServiceCallCallback<IAppFunctionService>() { @Override @@ -268,16 +264,6 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { "Failed to connect to AppFunctionService", /* extras= */ null)); } - - @Override - public void onTimedOut() { - Slog.e(TAG, "Timed out"); - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_TIMED_OUT, - "Binding to AppFunctionService timed out.", - /* extras= */ null)); - } }); if (!bindServiceResult) { diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java index 759f02eb138a..d84b20556053 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java @@ -53,12 +53,10 @@ import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; /** * This class implements helper methods for synchronously interacting with AppSearch while @@ -74,8 +72,9 @@ public class MetadataSyncAdapter { private final AppSearchManager mAppSearchManager; private final PackageManager mPackageManager; private final Object mLock = new Object(); + @GuardedBy("mLock") - private Future<AndroidFuture<Boolean>> mCurrentSyncTask; + private Future<?> mCurrentSyncTask; // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility // by permissions. @@ -105,7 +104,7 @@ public class MetadataSyncAdapter { AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB) .build(); AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>(); - Callable<AndroidFuture<Boolean>> callableTask = + Runnable runnable = () -> { try (FutureAppSearchSession staticMetadataSearchSession = new FutureAppSearchSessionImpl( @@ -125,14 +124,13 @@ public class MetadataSyncAdapter { } catch (Exception ex) { settableSyncStatus.completeExceptionally(ex); } - return settableSyncStatus; }; synchronized (mLock) { if (mCurrentSyncTask != null && !mCurrentSyncTask.isDone()) { - boolean cancel = mCurrentSyncTask.cancel(false); + var unused = mCurrentSyncTask.cancel(false); } - mCurrentSyncTask = mExecutor.submit(callableTask); + mCurrentSyncTask = mExecutor.submit(runnable); } return settableSyncStatus; @@ -140,11 +138,7 @@ public class MetadataSyncAdapter { /** This method shuts down the {@link MetadataSyncAdapter} scheduler. */ public void shutDown() { - try { - var unused = mExecutor.awaitTermination(30, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Slog.e(TAG, "Error shutting down MetadataSyncAdapter scheduler", e); - } + mExecutor.shutdown(); } @WorkerThread diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java index 58597c38bb94..cd5c3831bc0d 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java +++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java @@ -43,7 +43,6 @@ public interface RemoteServiceCaller<T> { * @param intent An Intent object that describes the service that should be bound. * @param bindFlags Flags used to control the binding process See {@link * android.content.Context#bindService}. - * @param timeoutInMillis The maximum time in milliseconds to wait for the service connection. * @param userHandle The UserHandle of the user for which the service should be bound. * @param callback A callback to be invoked for various events. See {@link * RunServiceCallCallback}. @@ -51,7 +50,6 @@ public interface RemoteServiceCaller<T> { boolean runServiceCall( @NonNull Intent intent, int bindFlags, - long timeoutInMillis, @NonNull UserHandle userHandle, @NonNull RunServiceCallCallback<T> callback); @@ -75,11 +73,5 @@ public interface RemoteServiceCaller<T> { /** Called when the service connection was failed to establish. */ void onFailedToConnect(); - - /** - * Called when the whole operation(i.e. binding and the service call) takes longer than - * allowed. - */ - void onTimedOut(); } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java index eea17eeca371..070a99d5bb28 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java @@ -62,12 +62,11 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> { public boolean runServiceCall( @NonNull Intent intent, int bindFlags, - long timeoutInMillis, @NonNull UserHandle userHandle, @NonNull RunServiceCallCallback<T> callback) { OneOffServiceConnection serviceConnection = new OneOffServiceConnection( - intent, bindFlags, timeoutInMillis, userHandle, callback); + intent, bindFlags, userHandle, callback); return serviceConnection.bindAndRun(); } @@ -76,28 +75,17 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> { implements ServiceConnection, ServiceUsageCompleteListener { private final Intent mIntent; private final int mFlags; - private final long mTimeoutMillis; private final UserHandle mUserHandle; private final RunServiceCallCallback<T> mCallback; - private final Runnable mTimeoutCallback; OneOffServiceConnection( @NonNull Intent intent, int flags, - long timeoutMillis, @NonNull UserHandle userHandle, @NonNull RunServiceCallCallback<T> callback) { mIntent = intent; mFlags = flags; - mTimeoutMillis = timeoutMillis; mCallback = callback; - mTimeoutCallback = - () -> - mExecutor.execute( - () -> { - safeUnbind(); - mCallback.onTimedOut(); - }); mUserHandle = userHandle; } @@ -105,9 +93,7 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> { boolean bindServiceResult = mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle); - if (bindServiceResult) { - mHandler.postDelayed(mTimeoutCallback, mTimeoutMillis); - } else { + if(!bindServiceResult) { safeUnbind(); } @@ -141,7 +127,6 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> { private void safeUnbind() { try { - mHandler.removeCallbacks(mTimeoutCallback); mContext.unbindService(this); } catch (Exception ex) { Log.w(TAG, "Failed to unbind", ex); diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java index 46d60f9c8504..0c54720c53e4 100644 --- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java +++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java @@ -454,10 +454,10 @@ public final class AssociationDiskStore { @NonNull Associations associations) throws IOException { final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS); + writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId()); for (AssociationInfo association : associations.getAssociations()) { writeAssociation(serializer, association); } - writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId()); serializer.endTag(null, XML_TAG_ASSOCIATIONS); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 871c32086a7f..414a4e66591b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -15304,10 +15304,8 @@ public class ActivityManagerService extends IActivityManager.Stub } psr.setReportedForegroundServiceTypes(fgServiceTypes); - ProcessChangeItem item = mProcessList.enqueueProcessChangeItemLocked( - proc.getPid(), proc.info.uid); - item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES; - item.foregroundServiceTypes = fgServiceTypes; + mProcessList.enqueueProcessChangeItemLocked(proc.getPid(), proc.info.uid, + ProcessChangeItem.CHANGE_FOREGROUND_SERVICES, fgServiceTypes); } if (oomAdj) { updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY); diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS index d6f04db5af55..1bcf8259afe8 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -1,16 +1,24 @@ # Applications & Processes -yamasani@google.com -jsharkey@google.com -hackbod@google.com -omakoto@google.com -ctate@google.com -huiyu@google.com -mwachens@google.com -sudheersai@google.com -suprabh@google.com -varunshah@google.com -bookatz@google.com -jji@google.com +per-file ActivityManager* = file:/ACTIVITY_MANAGER_OWNERS +per-file ActiveServices.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessList.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ActivityThread.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file SystemServer.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ServiceRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file AppProfiler.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessStateRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessServiceRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ForegroundServiceTypeLoggerModule.java = file:/ACTIVITY_MANAGER_OWNERS +per-file AppRestrictionController.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessErrorStateRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ProcessProfileRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file ConnectionRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file UidRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file IntentBindRecord.java = file:/ACTIVITY_MANAGER_OWNERS +per-file AppFGSTracker.java = file:/ACTIVITY_MANAGER_OWNERS +per-file FgsTempAllowList.java = file:/ACTIVITY_MANAGER_OWNERS +per-file HostingRecord.java = file:/ACTIVITY_MANAGER_OWNERS # Windows & Activities ogunwale@google.com @@ -20,24 +28,19 @@ patb@google.com per-file AccessCheckDelegateHelper.java = file:/core/java/android/permission/OWNERS # Battery Stats -joeo@google.com +per-file AppBatteryTracker.java = file:/BATTERY_STATS_OWNERS per-file BatteryStats* = file:/BATTERY_STATS_OWNERS per-file BatteryExternalStats* = file:/BATTERY_STATS_OWNERS -# Londoners -michaelwr@google.com -narayan@google.com - # Voice Interaction per-file *Assist* = file:/core/java/android/service/voice/OWNERS per-file *Voice* = file:/core/java/android/service/voice/OWNERS -per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com, tedbauer@google.com - -per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNERS - -per-file ContentProviderHelper.java = varunshah@google.com, omakoto@google.com, jsharkey@google.com, yamasani@google.com +# Content Provider +per-file ContentProvider* = varunshah@google.com, yamasani@google.com +# Cached App Freezer +per-file ProcessCachedOptimizerRecord.java = file:/PERFORMANCE_OWNERS per-file CachedAppOptimizer.java = file:/PERFORMANCE_OWNERS per-file Freezer.java = file:/PERFORMANCE_OWNERS @@ -46,3 +49,23 @@ per-file User* = file:/MULTIUSER_OWNERS # Broadcasts per-file Broadcast* = file:/BROADCASTS_OWNERS + +# Permissions & Packages +per-file *Permission* = patb@google.com +per-file *Package* = patb@google.com + +# OOM Adjuster +per-file *Oom* = file:/OOM_ADJUSTER_OWNERS + +# Miscellaneous +per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com, dzshen@google.com, zhidou@google.com, tedbauer@google.com +per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNERS + +# Londoners +michaelwr@google.com #{LAST_RESORT_SUGGESTION} +narayan@google.com #{LAST_RESORT_SUGGESTION} + +# Default +hackbod@google.com #{LAST_RESORT_SUGGESTION} +omakoto@google.com #{LAST_RESORT_SUGGESTION} +yamasani@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 22ec7904f972..78a0a117fe6f 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -3617,14 +3617,12 @@ public class OomAdjuster { if (changes != 0) { if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, "Changes in " + app + ": " + changes); - ActivityManagerService.ProcessChangeItem item = - mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid); - item.changes |= changes; - item.foregroundActivities = state.hasRepForegroundActivities(); + mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid, + changes, state.hasRepForegroundActivities()); if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS, - "Item " + Integer.toHexString(System.identityHashCode(item)) - + " " + app.toShortString() + ": changes=" + item.changes - + " foreground=" + item.foregroundActivities + "Enqueued process change item for " + + app.toShortString() + ": changes=" + changes + + " foreground=" + state.hasRepForegroundActivities() + " type=" + state.getAdjType() + " source=" + state.getAdjSource() + " target=" + state.getAdjTarget()); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 00250b4ef463..a93ae72fcfea 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -4998,50 +4998,67 @@ public final class ProcessList { } @GuardedBy("mService") - ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) { + void enqueueProcessChangeItemLocked(int pid, int uid, int changes, int foregroundServicetypes) { synchronized (mProcessChangeLock) { - int i = mPendingProcessChanges.size() - 1; - ActivityManagerService.ProcessChangeItem item = null; - while (i >= 0) { - item = mPendingProcessChanges.get(i); - if (item.pid == pid) { - if (DEBUG_PROCESS_OBSERVERS) { - Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item); - } - break; + final ProcessChangeItem item = enqueueProcessChangeItemLocked(pid, uid); + item.changes |= changes; + item.foregroundServiceTypes = foregroundServicetypes; + } + } + + @GuardedBy("mService") + void enqueueProcessChangeItemLocked(int pid, int uid, int changes, + boolean hasForegroundActivities) { + synchronized (mProcessChangeLock) { + final ProcessChangeItem item = enqueueProcessChangeItemLocked(pid, uid); + item.changes |= changes; + item.foregroundActivities = hasForegroundActivities; + } + } + + @GuardedBy({"mService", "mProcessChangeLock"}) + private ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) { + int i = mPendingProcessChanges.size() - 1; + ActivityManagerService.ProcessChangeItem item = null; + while (i >= 0) { + item = mPendingProcessChanges.get(i); + if (item.pid == pid) { + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item); } - i--; + break; } + i--; + } - if (i < 0) { - // No existing item in pending changes; need a new one. - final int num = mAvailProcessChanges.size(); - if (num > 0) { - item = mAvailProcessChanges.remove(num - 1); - if (DEBUG_PROCESS_OBSERVERS) { - Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item); - } - } else { - item = new ActivityManagerService.ProcessChangeItem(); - if (DEBUG_PROCESS_OBSERVERS) { - Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item); - } + if (i < 0) { + // No existing item in pending changes; need a new one. + final int num = mAvailProcessChanges.size(); + if (num > 0) { + item = mAvailProcessChanges.remove(num - 1); + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item); } - item.changes = 0; - item.pid = pid; - item.uid = uid; - if (mPendingProcessChanges.size() == 0) { - if (DEBUG_PROCESS_OBSERVERS) { - Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!"); - } - mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG) - .sendToTarget(); + } else { + item = new ActivityManagerService.ProcessChangeItem(); + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item); } - mPendingProcessChanges.add(item); } - - return item; + item.changes = 0; + item.pid = pid; + item.uid = uid; + if (mPendingProcessChanges.size() == 0) { + if (DEBUG_PROCESS_OBSERVERS) { + Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!"); + } + mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG) + .sendToTarget(); + } + mPendingProcessChanges.add(item); } + + return item; } @GuardedBy("mService") diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 6ae6f3d4713a..6af4be50b00c 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -619,7 +619,7 @@ public class AppOpsService extends IAppOpsService.Stub { this.op = op; this.uid = uid; this.uidState = uidState; - this.packageName = packageName; + this.packageName = packageName.intern(); // We keep an invariant that the persistent device will always have an entry in // mDeviceAttributedOps. mDeviceAttributedOps.put(PERSISTENT_DEVICE_ID_DEFAULT, @@ -1031,7 +1031,7 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - String pkgName = intent.getData().getEncodedSchemeSpecificPart(); + String pkgName = intent.getData().getEncodedSchemeSpecificPart().intern(); int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID); if (action.equals(ACTION_PACKAGE_ADDED) @@ -1235,7 +1235,7 @@ public class AppOpsService extends IAppOpsService.Stub { Ops ops = uidState.pkgOps.get(packageName); if (ops == null) { ops = new Ops(packageName, uidState); - uidState.pkgOps.put(packageName, ops); + uidState.pkgOps.put(packageName.intern(), ops); } SparseIntArray packageModes = @@ -4739,7 +4739,7 @@ public class AppOpsService extends IAppOpsService.Stub { return null; } ops = new Ops(packageName, uidState); - uidState.pkgOps.put(packageName, ops); + uidState.pkgOps.put(packageName.intern(), ops); } if (edit) { @@ -5076,7 +5076,7 @@ public class AppOpsService extends IAppOpsService.Stub { Ops ops = uidState.pkgOps.get(pkgName); if (ops == null) { ops = new Ops(pkgName, uidState); - uidState.pkgOps.put(pkgName, ops); + uidState.pkgOps.put(pkgName.intern(), ops); } ops.put(op.op, op); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 9940442824ca..df69afe6ed79 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -10809,7 +10809,8 @@ public class AudioService extends IAudioService.Stub //TODO move inside HardeningEnforcer after refactor that moves permission checks // in the blockFocusMethod if (permissionOverridesCheck) { - mHardeningEnforcer.metricsLogFocusReq(/*blocked*/false, focusReqType, uid); + mHardeningEnforcer.metricsLogFocusReq(/*blocked*/ false, focusReqType, uid, + /*unblockedBySdk*/ false); } if (!permissionOverridesCheck && mHardeningEnforcer.blockFocusMethod(uid, HardeningEnforcer.METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS, @@ -13383,19 +13384,39 @@ public class AudioService extends IAudioService.Stub } @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) - /** @see AudioPolicy#getFocusStack() */ + /* @see AudioPolicy#getFocusStack() */ public List<AudioFocusInfo> getFocusStack() { super.getFocusStack_enforcePermission(); return mMediaFocusControl.getFocusStack(); } - /** @see AudioPolicy#sendFocusLoss */ + /** + * @param focusLoser non-null entry that may be in the stack + * @see AudioPolicy#sendFocusLossAndUpdate(AudioFocusInfo) + */ + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) + public void sendFocusLossAndUpdate(@NonNull AudioFocusInfo focusLoser, + @NonNull IAudioPolicyCallback apcb) { + super.sendFocusLossAndUpdate_enforcePermission(); + Objects.requireNonNull(apcb); + if (!mAudioPolicies.containsKey(apcb.asBinder())) { + throw new IllegalStateException("Only registered AudioPolicy can change focus"); + } + if (!mAudioPolicies.get(apcb.asBinder()).mHasFocusListener) { + throw new IllegalStateException("AudioPolicy must have focus listener to change focus"); + } + + mMediaFocusControl.sendFocusLossAndUpdate(Objects.requireNonNull(focusLoser)); + } + + /* @see AudioPolicy#sendFocusLoss(AudioFocusInfo) */ + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser, @NonNull IAudioPolicyCallback apcb) { + super.sendFocusLoss_enforcePermission(); Objects.requireNonNull(focusLoser); Objects.requireNonNull(apcb); - enforceModifyAudioRoutingPermission(); if (!mAudioPolicies.containsKey(apcb.asBinder())) { throw new IllegalStateException("Only registered AudioPolicy can change focus"); } diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java index faeba5d068fc..661111070aae 100644 --- a/services/core/java/com/android/server/audio/HardeningEnforcer.java +++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java @@ -168,6 +168,8 @@ public class HardeningEnforcer { } boolean blocked = true; + // indicates the focus request was not blocked because of the SDK version + boolean unblockedBySdk = false; if (noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName, attributionTag)) { if (DEBUG) { Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking"); @@ -179,9 +181,10 @@ public class HardeningEnforcer { + targetSdk); } blocked = false; + unblockedBySdk = true; } - metricsLogFocusReq(blocked, focusReqType, callingUid); + metricsLogFocusReq(blocked, focusReqType, callingUid, unblockedBySdk); if (!blocked) { return false; @@ -195,7 +198,16 @@ public class HardeningEnforcer { return true; } - /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid) { + /** + * Log metrics for the focus request + * @param blocked true if the call blocked + * @param focusReq the type of focus request + * @param callingUid the UID of the caller + * @param unblockedBySdk if blocked is false, + * true indicates it was unblocked thanks to an older SDK + */ + /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid, + boolean unblockedBySdk) { final String metricId = blocked ? METRIC_COUNTERS_FOCUS_DENIAL.get(focusReq) : METRIC_COUNTERS_FOCUS_GRANT.get(focusReq); if (TextUtils.isEmpty(metricId)) { @@ -204,6 +216,12 @@ public class HardeningEnforcer { } try { Counter.logIncrementWithUid(metricId, callingUid); + if (!blocked && unblockedBySdk) { + // additional metric to capture focus requests that are currently granted + // because the app is on an older SDK, but would have been blocked otherwise + Counter.logIncrementWithUid( + "media_audio.value_audio_focus_grant_hardening_waived_by_sdk", callingUid); + } } catch (Exception e) { Slog.e(TAG, "Counter error metricId:" + metricId + " for focus req:" + focusReq + " from uid:" + callingUid, e); diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 7e263560b8a1..b4af46efcb38 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -280,6 +280,37 @@ public class MediaFocusControl implements PlayerFocusEnforcer { } /** + * Like {@link #sendFocusLoss(AudioFocusInfo)} but if the loser was at the top of stack, + * make the next entry gain focus with {@link AudioManager#AUDIOFOCUS_GAIN}. + * @param focusInfo the focus owner to discard + * @see AudioPolicy#sendFocusLossAndUpdate(AudioFocusInfo) + */ + protected void sendFocusLossAndUpdate(@NonNull AudioFocusInfo focusInfo) { + synchronized (mAudioFocusLock) { + if (mFocusStack.isEmpty()) { + return; + } + final FocusRequester currentFocusOwner = mFocusStack.peek(); + if (currentFocusOwner.toAudioFocusInfo().equals(focusInfo)) { + // focus loss is for the top of the stack + currentFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null, + false /*forceDuck*/); + currentFocusOwner.release(); + + mFocusStack.pop(); + // is there a new focus owner? + if (!mFocusStack.isEmpty()) { + mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN); + } + } else { + // focus loss if for another entry that's not at the top of the stack, + // just remove it from the stack and make it lose focus + sendFocusLoss(focusInfo); + } + } + } + + /** * Return a copy of the focus stack for external consumption (composed of AudioFocusInfo * instead of FocusRequester instances) * @return a SystemApi-friendly version of the focus stack, in the same order (last entry diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index e7fd8f7db182..ae33b83b49dc 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -571,6 +571,10 @@ public final class DisplayManagerService extends SystemService { private final DisplayNotificationManager mDisplayNotificationManager; private final ExternalDisplayStatsService mExternalDisplayStatsService; + // Manages the relative placement of extended displays + @Nullable + private final DisplayTopologyCoordinator mDisplayTopologyCoordinator; + /** * Applications use {@link android.view.Display#getRefreshRate} and * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate. @@ -644,6 +648,11 @@ public final class DisplayManagerService extends SystemService { mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext, mExternalDisplayStatsService); mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector()); + if (mFlags.isDisplayTopologyEnabled()) { + mDisplayTopologyCoordinator = new DisplayTopologyCoordinator(); + } else { + mDisplayTopologyCoordinator = null; + } } public void setupSchedulerPolicies() { @@ -3474,9 +3483,13 @@ public final class DisplayManagerService extends SystemService { mSmallAreaDetectionController.dump(pw); } + if (mDisplayTopologyCoordinator != null) { + pw.println(); + mDisplayTopologyCoordinator.dump(pw); + } + pw.println(); mFlags.dump(pw); - } private static float[] getFloatArray(TypedArray array) { diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java new file mode 100644 index 000000000000..631f14755b12 --- /dev/null +++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java @@ -0,0 +1,104 @@ +/* + * 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.annotation.Nullable; +import android.util.IndentingPrintWriter; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * This class manages the relative placement (topology) of extended displays. It is responsible for + * updating and persisting the topology. + */ +class DisplayTopologyCoordinator { + + /** + * The topology tree + */ + @Nullable + private TopologyTreeNode mRoot; + + /** + * The logical display ID of the primary display that will show certain UI elements. + * This is not necessarily the same as the default display. + */ + private int mPrimaryDisplayId; + + /** + * Print the object's state and debug information into the given stream. + * @param pw The stream to dump information to. + */ + public void dump(PrintWriter pw) { + pw.println("DisplayTopologyCoordinator:"); + pw.println("--------------------"); + IndentingPrintWriter ipw = new IndentingPrintWriter(pw); + ipw.increaseIndent(); + + ipw.println("mPrimaryDisplayId: " + mPrimaryDisplayId); + + ipw.println("Topology tree:"); + if (mRoot != null) { + ipw.increaseIndent(); + mRoot.dump(ipw); + ipw.decreaseIndent(); + } + } + + private static class TopologyTreeNode { + + /** + * The logical display ID + */ + private int mDisplayId; + + private final List<TopologyTreeNode> mChildren = new ArrayList<>(); + + /** + * The position of this display relative to its parent. + */ + private Position mPosition; + + /** + * The distance from the top edge of the parent display to the top edge of this display (in + * case of POSITION_LEFT or POSITION_RIGHT) or from the left edge of the parent display + * to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit + * used is density-independent pixels (dp). + */ + private double mOffset; + + /** + * Print the object's state and debug information into the given stream. + * @param ipw The stream to dump information to. + */ + void dump(IndentingPrintWriter ipw) { + ipw.println("Display {id=" + mDisplayId + ", position=" + mPosition + + ", offset=" + mOffset + "}"); + ipw.increaseIndent(); + for (TopologyTreeNode child : mChildren) { + child.dump(ipw); + } + ipw.decreaseIndent(); + } + + private enum Position { + POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM + } + } +} diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java index cf44ac029c82..a1fd16476706 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java @@ -74,6 +74,5 @@ abstract class BrightnessClamper<T> { protected enum Type { POWER, - WEAR_BEDTIME_MODE, } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index 9404034cdd34..a10094fdfbb8 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -218,9 +218,7 @@ public class BrightnessClamperController { return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; } else if (mClamperType == Type.POWER) { return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC; - } else if (mClamperType == Type.WEAR_BEDTIME_MODE) { - return BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE; - } else { + } else { Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType); return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; } @@ -350,10 +348,6 @@ public class BrightnessClamperController { data, currentBrightness)); } } - if (flags.isBrightnessWearBedtimeModeClamperEnabled()) { - clampers.add(new BrightnessWearBedtimeModeClamper(handler, context, - clamperChangeListener, data)); - } return clampers; } @@ -362,6 +356,10 @@ public class BrightnessClamperController { DisplayDeviceData data) { List<BrightnessStateModifier> modifiers = new ArrayList<>(); modifiers.add(new BrightnessThermalModifier(handler, listener, data)); + if (flags.isBrightnessWearBedtimeModeClamperEnabled()) { + modifiers.add(new BrightnessWearBedtimeModeModifier(handler, context, + listener, data)); + } modifiers.add(new DisplayDimModifier(context)); modifiers.add(new BrightnessLowPowerModeModifier()); @@ -395,7 +393,7 @@ public class BrightnessClamperController { */ public static class DisplayDeviceData implements BrightnessThermalModifier.ThermalData, BrightnessPowerClamper.PowerData, - BrightnessWearBedtimeModeClamper.WearBedtimeModeData { + BrightnessWearBedtimeModeModifier.WearBedtimeModeData { @NonNull private final String mUniqueDisplayId; @NonNull diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java deleted file mode 100644 index 1902e35ed397..000000000000 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.display.brightness.clamper; - -import android.annotation.NonNull; -import android.content.ContentResolver; -import android.content.Context; -import android.database.ContentObserver; -import android.os.Handler; -import android.os.UserHandle; -import android.provider.Settings; - -import com.android.internal.annotations.VisibleForTesting; - -public class BrightnessWearBedtimeModeClamper extends - BrightnessClamper<BrightnessWearBedtimeModeClamper.WearBedtimeModeData> { - - public static final int BEDTIME_MODE_OFF = 0; - public static final int BEDTIME_MODE_ON = 1; - - private final Context mContext; - - private final ContentObserver mSettingsObserver; - - BrightnessWearBedtimeModeClamper(Handler handler, Context context, - BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) { - this(new Injector(), handler, context, listener, data); - } - - @VisibleForTesting - BrightnessWearBedtimeModeClamper(Injector injector, Handler handler, Context context, - BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) { - super(handler, listener); - mContext = context; - mBrightnessCap = data.getBrightnessWearBedtimeModeCap(); - mSettingsObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - final int bedtimeModeSetting = Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.Wearable.BEDTIME_MODE, - BEDTIME_MODE_OFF); - mIsActive = bedtimeModeSetting == BEDTIME_MODE_ON; - mChangeListener.onChanged(); - } - }; - injector.registerBedtimeModeObserver(context.getContentResolver(), mSettingsObserver); - } - - @NonNull - @Override - Type getType() { - return Type.WEAR_BEDTIME_MODE; - } - - @Override - void onDeviceConfigChanged() {} - - @Override - void onDisplayChanged(WearBedtimeModeData displayData) { - mHandler.post(() -> { - mBrightnessCap = displayData.getBrightnessWearBedtimeModeCap(); - mChangeListener.onChanged(); - }); - } - - @Override - void stop() { - mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); - } - - interface WearBedtimeModeData { - float getBrightnessWearBedtimeModeCap(); - } - - @VisibleForTesting - static class Injector { - void registerBedtimeModeObserver(@NonNull ContentResolver cr, - @NonNull ContentObserver observer) { - cr.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE), - /* notifyForDescendants= */ false, observer, UserHandle.USER_ALL); - } - } -} diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifier.java new file mode 100644 index 000000000000..c9c8c33764a6 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifier.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.clamper; + +import android.annotation.NonNull; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; + +import java.io.PrintWriter; + +public class BrightnessWearBedtimeModeModifier implements BrightnessStateModifier, + BrightnessClamperController.DisplayDeviceDataListener, + BrightnessClamperController.StatefulModifier { + + public static final int BEDTIME_MODE_OFF = 0; + public static final int BEDTIME_MODE_ON = 1; + + private final Context mContext; + + private final ContentObserver mSettingsObserver; + protected final Handler mHandler; + protected final BrightnessClamperController.ClamperChangeListener mChangeListener; + + private float mBrightnessCap; + private boolean mIsActive = false; + private boolean mApplied = false; + + BrightnessWearBedtimeModeModifier(Handler handler, Context context, + BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) { + this(new Injector(), handler, context, listener, data); + } + + @VisibleForTesting + BrightnessWearBedtimeModeModifier(Injector injector, Handler handler, Context context, + BrightnessClamperController.ClamperChangeListener listener, WearBedtimeModeData data) { + mHandler = handler; + mChangeListener = listener; + mContext = context; + mBrightnessCap = data.getBrightnessWearBedtimeModeCap(); + mSettingsObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + final int bedtimeModeSetting = Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.Wearable.BEDTIME_MODE, + BEDTIME_MODE_OFF); + mIsActive = bedtimeModeSetting == BEDTIME_MODE_ON; + mChangeListener.onChanged(); + } + }; + injector.registerBedtimeModeObserver(context.getContentResolver(), mSettingsObserver); + } + + //region BrightnessStateModifier + @Override + public void apply(DisplayManagerInternal.DisplayPowerRequest request, + DisplayBrightnessState.Builder stateBuilder) { + if (mIsActive && stateBuilder.getMaxBrightness() > mBrightnessCap) { + stateBuilder.setMaxBrightness(mBrightnessCap); + stateBuilder.setBrightness(Math.min(stateBuilder.getBrightness(), mBrightnessCap)); + stateBuilder.setBrightnessMaxReason( + BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE); + stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED); + // set fast change only when modifier is activated. + // this will allow auto brightness to apply slow change even when modifier is active + if (!mApplied) { + stateBuilder.setIsSlowChange(false); + } + mApplied = true; + } else { + mApplied = false; + } + } + + @Override + public void stop() { + mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); + } + + @Override + public void dump(PrintWriter writer) { + writer.println("BrightnessWearBedtimeModeModifier:"); + writer.println(" mBrightnessCap: " + mBrightnessCap); + writer.println(" mIsActive: " + mIsActive); + writer.println(" mApplied: " + mApplied); + } + + @Override + public boolean shouldListenToLightSensor() { + return false; + } + + @Override + public void setAmbientLux(float lux) { + // noop + } + //endregion + + //region DisplayDeviceDataListener + @Override + public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData data) { + mHandler.post(() -> { + mBrightnessCap = data.getBrightnessWearBedtimeModeCap(); + mChangeListener.onChanged(); + }); + } + //endregion + + //region StatefulModifier + @Override + public void applyStateChange( + BrightnessClamperController.ModifiersAggregatedState aggregatedState) { + if (mIsActive && aggregatedState.mMaxBrightness > mBrightnessCap) { + aggregatedState.mMaxBrightness = mBrightnessCap; + aggregatedState.mMaxBrightnessReason = + BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE; + } + } + //endregion + + interface WearBedtimeModeData { + float getBrightnessWearBedtimeModeCap(); + } + + @VisibleForTesting + static class Injector { + void registerBedtimeModeObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer) { + cr.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE), + /* notifyForDescendants= */ false, observer, UserHandle.USER_ALL); + } + } +} diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index f600e7fc2946..df66893a2f35 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -69,6 +69,10 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY, Flags::enableModeLimitForExternalDisplay); + private final FlagState mDisplayTopology = new FlagState( + Flags.FLAG_DISPLAY_TOPOLOGY, + Flags::displayTopology); + private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState( Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING, Flags::enableConnectedDisplayErrorHandling); @@ -266,6 +270,10 @@ public class DisplayManagerFlags { return mExternalDisplayLimitModeState.isEnabled(); } + public boolean isDisplayTopologyEnabled() { + return mDisplayTopology.isEnabled(); + } + /** * @return Whether displays refresh rate synchronization is enabled. */ @@ -441,6 +449,7 @@ public class DisplayManagerFlags { pw.println(" " + mConnectedDisplayManagementFlagState); pw.println(" " + mDisplayOffloadFlagState); pw.println(" " + mExternalDisplayLimitModeState); + pw.println(" " + mDisplayTopology); pw.println(" " + mHdrClamperFlagState); pw.println(" " + mNbmControllerFlagState); pw.println(" " + mPowerThrottlingClamperFlagState); diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 9968ba57bba4..e3ebe5bcd9ed 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -92,6 +92,14 @@ flag { } flag { + name: "display_topology" + namespace: "display_manager" + description: "Display topology for moving cursors and windows between extended displays" + bug: "278199220" + is_fixed_read_only: true +} + +flag { name: "enable_displays_refresh_rates_synchronization" namespace: "display_manager" description: "Enables synchronization of refresh rates across displays" diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java index 73f18d17d058..92812670057a 100644 --- a/services/core/java/com/android/server/input/InputManagerInternal.java +++ b/services/core/java/com/android/server/input/InputManagerInternal.java @@ -232,6 +232,9 @@ public abstract class InputManagerInternal { /** * Notify key gesture was completed by the user. * + * NOTE: This is a temporary API added to assist in a long-term refactor, and is not meant for + * general use by system services. + * * @param deviceId the device ID of the keyboard using which the event was completed * @param keycodes the keys pressed for the event * @param modifierState the modifier state @@ -240,4 +243,20 @@ public abstract class InputManagerInternal { */ public abstract void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState, @KeyGestureEvent.KeyGestureType int event); + + /** + * Notify that a key gesture was detected by another system component, and it should be handled + * appropriately by KeyGestureController. + * + * NOTE: This is a temporary API added to assist in a long-term refactor, and is not meant for + * general use by system services. + * + * @param deviceId the device ID of the keyboard using which the event was completed + * @param keycodes the keys pressed for the event + * @param modifierState the modifier state + * @param event the gesture event that was completed + * + */ + public abstract void handleKeyGestureInKeyGestureController(int deviceId, int[] keycodes, + int modifierState, @KeyGestureEvent.KeyGestureType int event); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 65adaba62a4d..fd7479eb8d48 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -3408,6 +3408,12 @@ public class InputManagerService extends IInputManager.Stub mKeyGestureController.notifyKeyGestureCompleted(deviceId, keycodes, modifierState, gestureType); } + + @Override + public void handleKeyGestureInKeyGestureController(int deviceId, int[] keycodes, + int modifierState, @KeyGestureEvent.KeyGestureType int gestureType) { + mKeyGestureController.handleKeyGesture(deviceId, keycodes, modifierState, gestureType); + } } @Override diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java index 7fe7891af80d..4538b49b73c5 100644 --- a/services/core/java/com/android/server/input/KeyGestureController.java +++ b/services/core/java/com/android/server/input/KeyGestureController.java @@ -24,7 +24,6 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; - import android.hardware.input.AidlKeyGestureEvent; import android.hardware.input.IKeyGestureEventListener; import android.hardware.input.IKeyGestureHandler; @@ -582,8 +581,11 @@ final class KeyGestureController { boolean handleKeyGesture(int deviceId, int[] keycodes, int modifierState, @KeyGestureEvent.KeyGestureType int gestureType, int action, int displayId, IBinder focusedToken, int flags) { - AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, - modifierState, gestureType, action, displayId, flags); + return handleKeyGesture(createKeyGestureEvent(deviceId, keycodes, + modifierState, gestureType, action, displayId, flags), focusedToken); + } + + private boolean handleKeyGesture(AidlKeyGestureEvent event, @Nullable IBinder focusedToken) { synchronized (mKeyGestureHandlerRecords) { for (KeyGestureHandlerRecord handler : mKeyGestureHandlerRecords.values()) { if (handler.handleKeyGesture(event, focusedToken)) { @@ -616,6 +618,13 @@ final class KeyGestureController { mHandler.obtainMessage(MSG_NOTIFY_KEY_GESTURE_EVENT, event).sendToTarget(); } + public void handleKeyGesture(int deviceId, int[] keycodes, int modifierState, + @KeyGestureEvent.KeyGestureType int gestureType) { + AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, modifierState, + gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY, 0); + handleKeyGesture(event, null /*focusedToken*/); + } + @MainThread private void notifyKeyGestureEvent(AidlKeyGestureEvent event) { InputDevice device = getInputDevice(event.deviceId); diff --git a/services/core/java/com/android/server/input/KeyRemapper.java b/services/core/java/com/android/server/input/KeyRemapper.java index 7ba77698cb5d..82b36aff5273 100644 --- a/services/core/java/com/android/server/input/KeyRemapper.java +++ b/services/core/java/com/android/server/input/KeyRemapper.java @@ -17,27 +17,24 @@ package com.android.server.input; import android.content.Context; -import android.hardware.input.InputManager; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.ArrayMap; import android.util.FeatureFlagUtils; -import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; import java.util.Map; -import java.util.Objects; /** * A component of {@link InputManagerService} responsible for managing key remappings. * * @hide */ -final class KeyRemapper implements InputManager.InputDeviceListener { +final class KeyRemapper { - private static final int MSG_UPDATE_EXISTING_DEVICES = 1; + private static final int MSG_UPDATE_EXISTING_KEY_REMAPPING = 1; private static final int MSG_REMAP_KEY = 2; private static final int MSG_CLEAR_ALL_REMAPPING = 3; @@ -49,7 +46,7 @@ final class KeyRemapper implements InputManager.InputDeviceListener { private final Handler mHandler; KeyRemapper(Context context, NativeInputManagerService nativeService, - PersistentDataStore dataStore, Looper looper) { + PersistentDataStore dataStore, Looper looper) { mContext = context; mNative = nativeService; mDataStore = dataStore; @@ -57,13 +54,7 @@ final class KeyRemapper implements InputManager.InputDeviceListener { } public void systemRunning() { - InputManager inputManager = Objects.requireNonNull( - mContext.getSystemService(InputManager.class)); - inputManager.registerInputDeviceListener(this, mHandler); - - Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES, - inputManager.getInputDeviceIds()); - mHandler.sendMessage(msg); + Message.obtain(mHandler, MSG_UPDATE_EXISTING_KEY_REMAPPING).sendToTarget(); } public void remapKey(int fromKey, int toKey) { @@ -91,19 +82,19 @@ final class KeyRemapper implements InputManager.InputDeviceListener { } } - private void addKeyRemapping(int fromKey, int toKey) { - InputManager inputManager = Objects.requireNonNull( - mContext.getSystemService(InputManager.class)); - for (int deviceId : inputManager.getInputDeviceIds()) { - InputDevice inputDevice = inputManager.getInputDevice(deviceId); - if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) { - mNative.addKeyRemapping(deviceId, fromKey, toKey); - } + private void setKeyRemapping(Map<Integer, Integer> keyRemapping) { + int index = 0; + int[] fromKeycodesArr = new int[keyRemapping.size()]; + int[] toKeycodesArr = new int[keyRemapping.size()]; + for (Map.Entry<Integer, Integer> entry : keyRemapping.entrySet()) { + fromKeycodesArr[index] = entry.getKey(); + toKeycodesArr[index] = entry.getValue(); + index++; } + mNative.setKeyRemapping(fromKeycodesArr, toKeycodesArr); } private void remapKeyInternal(int fromKey, int toKey) { - addKeyRemapping(fromKey, toKey); synchronized (mDataStore) { try { if (fromKey == toKey) { @@ -114,6 +105,7 @@ final class KeyRemapper implements InputManager.InputDeviceListener { } finally { mDataStore.saveIfNeeded(); } + setKeyRemapping(mDataStore.getKeyRemapping()); } } @@ -123,45 +115,25 @@ final class KeyRemapper implements InputManager.InputDeviceListener { Map<Integer, Integer> keyRemapping = mDataStore.getKeyRemapping(); for (int fromKey : keyRemapping.keySet()) { mDataStore.clearMappedKey(fromKey); - - // Remapping to itself will clear the remapping on native side - addKeyRemapping(fromKey, fromKey); } } finally { mDataStore.saveIfNeeded(); } + setKeyRemapping(mDataStore.getKeyRemapping()); } } - @Override - public void onInputDeviceAdded(int deviceId) { + public void updateExistingKeyMapping() { if (!supportRemapping()) { return; } - InputManager inputManager = Objects.requireNonNull( - mContext.getSystemService(InputManager.class)); - InputDevice inputDevice = inputManager.getInputDevice(deviceId); - if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) { - Map<Integer, Integer> remapping = getKeyRemapping(); - remapping.forEach( - (fromKey, toKey) -> mNative.addKeyRemapping(deviceId, fromKey, toKey)); - } - } - - @Override - public void onInputDeviceRemoved(int deviceId) { - } - - @Override - public void onInputDeviceChanged(int deviceId) { + setKeyRemapping(getKeyRemapping()); } private boolean handleMessage(Message msg) { switch (msg.what) { - case MSG_UPDATE_EXISTING_DEVICES: - for (int deviceId : (int[]) msg.obj) { - onInputDeviceAdded(deviceId); - } + case MSG_UPDATE_EXISTING_KEY_REMAPPING: + updateExistingKeyMapping(); return true; case MSG_REMAP_KEY: remapKeyInternal(msg.arg1, msg.arg2); diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 5dd461dda061..d17e256e34fc 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -48,7 +48,7 @@ interface NativeInputManagerService { int getSwitchState(int deviceId, int sourceMask, int sw); - void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode); + void setKeyRemapping(int[] fromKeyCodes, int[] toKeyCodes); boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists); @@ -311,7 +311,7 @@ interface NativeInputManagerService { public native int getSwitchState(int deviceId, int sourceMask, int sw); @Override - public native void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode); + public native void setKeyRemapping(int[] fromKeyCodes, int[] toKeyCodes); @Override public native boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java index 0e940d281b09..f3514653518b 100644 --- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java +++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java @@ -52,6 +52,8 @@ public class TouchpadDebugView extends LinearLayout { private static final float DEFAULT_RES_Y = 45f; private static final int TEXT_PADDING_DP = 12; private static final int ROUNDED_CORNER_RADIUS_DP = 24; + private static final int BUTTON_PRESSED_BACKGROUND_COLOR = Color.rgb(118, 151, 99); + private static final int BUTTON_RELEASED_BACKGROUND_COLOR = Color.rgb(84, 85, 169); /** * Input device ID for the touchpad that this debug view is displaying. @@ -75,6 +77,8 @@ public class TouchpadDebugView extends LinearLayout { private int mWindowLocationBeforeDragY; private int mLatestGestureType = 0; private TextView mGestureInfoView; + private TextView mNameView; + @NonNull private TouchpadHardwareState mLastTouchpadState = new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0, @@ -119,35 +123,34 @@ public class TouchpadDebugView extends LinearLayout { LayoutParams.WRAP_CONTENT)); setBackgroundColor(Color.TRANSPARENT); - TextView nameView = new TextView(context); - nameView.setBackgroundColor(Color.RED); - nameView.setTextSize(TEXT_SIZE_SP); - nameView.setText(Objects.requireNonNull(Objects.requireNonNull( + mNameView = new TextView(context); + mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR); + mNameView.setTextSize(TEXT_SIZE_SP); + mNameView.setText(Objects.requireNonNull(Objects.requireNonNull( mContext.getSystemService(InputManager.class)) .getInputDevice(touchpadId)).getName()); - nameView.setGravity(Gravity.CENTER); - nameView.setTextColor(Color.WHITE); + mNameView.setGravity(Gravity.CENTER); + mNameView.setTextColor(Color.WHITE); int paddingInDP = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, TEXT_PADDING_DP, getResources().getDisplayMetrics()); - nameView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP); - nameView.setLayoutParams( + mNameView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP); + mNameView.setLayoutParams( new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); mTouchpadVisualizationView = new TouchpadVisualizationView(context, mTouchpadHardwareProperties); - mTouchpadVisualizationView.setBackgroundColor(Color.WHITE); mGestureInfoView = new TextView(context); - mGestureInfoView.setBackgroundColor(Color.BLACK); mGestureInfoView.setTextSize(TEXT_SIZE_SP); mGestureInfoView.setText("Latest Gesture: "); mGestureInfoView.setGravity(Gravity.CENTER); - mGestureInfoView.setTextColor(Color.WHITE); mGestureInfoView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP); mGestureInfoView.setLayoutParams( new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); - addView(nameView); + updateTheme(getResources().getConfiguration().uiMode); + + addView(mNameView); addView(mTouchpadVisualizationView); addView(mGestureInfoView); @@ -239,6 +242,8 @@ public class TouchpadDebugView extends LinearLayout { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + + updateTheme(newConfig.uiMode); updateScreenDimensions(); updateViewsDimensions(); @@ -250,6 +255,27 @@ public class TouchpadDebugView extends LinearLayout { mWindowManager.updateViewLayout(this, mWindowLayoutParams); } + private void updateTheme(int uiMode) { + int currentNightMode = uiMode & Configuration.UI_MODE_NIGHT_MASK; + if (currentNightMode == Configuration.UI_MODE_NIGHT_YES) { + setNightModeTheme(); + } else { + setLightModeTheme(); + } + } + + private void setLightModeTheme() { + mTouchpadVisualizationView.setLightModeTheme(); + mGestureInfoView.setBackgroundColor(Color.WHITE); + mGestureInfoView.setTextColor(Color.BLACK); + } + + private void setNightModeTheme() { + mTouchpadVisualizationView.setNightModeTheme(); + mGestureInfoView.setBackgroundColor(Color.BLACK); + mGestureInfoView.setTextColor(Color.WHITE); + } + private boolean isSlopExceeded(float deltaX, float deltaY) { return deltaX * deltaX + deltaY * deltaY >= mTouchSlop * mTouchSlop; } @@ -333,12 +359,12 @@ public class TouchpadDebugView extends LinearLayout { private void onTouchpadButtonPress() { Slog.d(TAG, "You clicked me!"); - getChildAt(0).setBackgroundColor(Color.BLUE); + mNameView.setBackgroundColor(BUTTON_PRESSED_BACKGROUND_COLOR); } private void onTouchpadButtonRelease() { Slog.d(TAG, "You released the click"); - getChildAt(0).setBackgroundColor(Color.RED); + mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR); } /** diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java index 2eed9ba95413..96426bbfe4f3 100644 --- a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java +++ b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java @@ -18,6 +18,7 @@ package com.android.server.input.debug; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.util.Slog; @@ -58,11 +59,9 @@ public class TouchpadVisualizationView extends View { mScaleFactor = 1; mOvalStrokePaint = new Paint(); mOvalStrokePaint.setAntiAlias(true); - mOvalStrokePaint.setARGB(255, 0, 0, 0); mOvalStrokePaint.setStyle(Paint.Style.STROKE); mOvalFillPaint = new Paint(); mOvalFillPaint.setAntiAlias(true); - mOvalFillPaint.setARGB(255, 0, 0, 0); mTracePaint = new Paint(); mTracePaint.setAntiAlias(false); mTracePaint.setARGB(255, 0, 0, 255); @@ -195,6 +194,24 @@ public class TouchpadVisualizationView extends View { mScaleFactor = scaleFactor; } + /** + * Change the colors of the objects inside the view to light mode theme. + */ + public void setLightModeTheme() { + this.setBackgroundColor(Color.rgb(20, 20, 20)); + mOvalFillPaint.setARGB(255, 255, 255, 255); + mOvalStrokePaint.setARGB(255, 255, 255, 255); + } + + /** + * Change the colors of the objects inside the view to night mode theme. + */ + public void setNightModeTheme() { + this.setBackgroundColor(Color.rgb(240, 240, 240)); + mOvalFillPaint.setARGB(255, 0, 0, 0); + mOvalStrokePaint.setARGB(255, 0, 0, 0); + } + private float translateX(float x) { return translateRange(mTouchpadHardwareProperties.getLeft(), mTouchpadHardwareProperties.getRight(), 0, getWidth(), x); diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index e9db1b529a63..0eb4cbda72bf 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -1358,7 +1358,8 @@ public class ZenModeHelper { if (isNew) { // Newly created rule with no provided policy; fill in with the default. zenRule.zenPolicy = - Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy(); + (Flags.modesUi() ? mDefaultConfig.getZenPolicy() : mConfig.getZenPolicy()) + .copy(); return true; } // Otherwise, a null policy means no policy changes, so we can stop here. @@ -1773,7 +1774,7 @@ public class ZenModeHelper { // definition cannot have a rule with TYPE_BEDTIME (or any other type). config.automaticRules = new ArrayMap<>(); for (ZenRule rule : mDefaultConfig.automaticRules.values()) { - config.automaticRules.put(rule.id, rule); + config.automaticRules.put(rule.id, rule.copy()); } reason += ", reset to default rules"; } diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java index 5aea356a4173..49a6ffde6783 100644 --- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java +++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java @@ -191,6 +191,7 @@ public class BackgroundUserSoundNotifier { /** * Stop player proxy for the ongoing alarm and drop focus for its AudioFocusInfo. */ + @SuppressLint("MissingPermission") @VisibleForTesting void muteAlarmSounds(Context context) { AudioManager audioManager = context.getSystemService(AudioManager.class); @@ -201,6 +202,11 @@ public class BackgroundUserSoundNotifier { } } } + + AudioFocusInfo currentAfi = getAudioFocusInfoForNotification(); + if (currentAfi != null) { + mFocusControlAudioPolicy.sendFocusLossAndUpdate(currentAfi); + } } /** diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 89417f3765ff..8bab9de903ba 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1101,8 +1101,6 @@ public class UserManagerService extends IUserManager.Stub { if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) { UserManager.invalidateIsUserUnlockedCache(); UserManager.invalidateQuietModeEnabledCache(); - UserManager.invalidateStaticUserProperties(); - UserManager.invalidateUserPropertiesCache(); UserManager.invalidateUserSerialNumberCache(); } } @@ -2647,11 +2645,15 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public int getMainDisplayIdAssignedToUser() { - // Not checking for any permission as it returns info about calling user - int userId = UserHandle.getUserId(Binder.getCallingUid()); - int displayId = mUserVisibilityMediator.getMainDisplayAssignedToUser(userId); - return displayId; + public int getMainDisplayIdAssignedToUser(int userId) { + final int callingUserId = UserHandle.getCallingUserId(); + if (callingUserId != userId + && !hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) { + throw new SecurityException("Caller from user " + callingUserId + " needs MANAGE_USERS " + + "or INTERACT_ACROSS_USERS permission to get the main display for (" + userId + + ")"); + } + return mUserVisibilityMediator.getMainDisplayAssignedToUser(userId); } @Override diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java index 46207c1860c0..b43ddaa92562 100644 --- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java +++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java @@ -572,7 +572,7 @@ public final class UserVisibilityMediator implements Dumpable { return false; } - // First check if the user started on display + // First check if the user is assigned to a display int userAssignedToDisplay = getUserStartedOnDisplay(displayId); if (userAssignedToDisplay != USER_NULL) { Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because display was assigned" @@ -918,10 +918,16 @@ public final class UserVisibilityMediator implements Dumpable { if (!isStartedVisibleProfileLocked(userId)) { return userId; } else if (DBG) { - Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's " - + "a profile", displayId, userId); + Slogf.d(TAG, + "getUserAssignedToDisplay(%d): skipping user %d because it's a profile", + displayId, userId); } } + int userAssignedToExtraDisplay = mExtraDisplaysAssignedToUsers.get(displayId, + USER_NULL); + if (userAssignedToExtraDisplay != USER_NULL) { + return userAssignedToExtraDisplay; + } } if (!returnCurrentUserByDefault) { if (DBG) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 02c02b04c5de..63491e8434bf 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -47,6 +47,7 @@ import static android.view.KeyEvent.KEYCODE_DPAD_DOWN; import static android.view.KeyEvent.KEYCODE_HOME; import static android.view.KeyEvent.KEYCODE_POWER; import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY; +import static android.view.KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL; import static android.view.KeyEvent.KEYCODE_UNKNOWN; import static android.view.KeyEvent.KEYCODE_VOLUME_DOWN; import static android.view.KeyEvent.KEYCODE_VOLUME_UP; @@ -2643,7 +2644,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - void onKeyUp(long eventTime, int count, int displayId) { + void onKeyUp(long eventTime, int count, int displayId, int deviceId, int metaState) { if (mShouldEarlyShortPressOnPower && count == 1) { powerPress(eventTime, 1 /*pressCount*/, displayId); } @@ -2763,7 +2764,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - void onKeyUp(long eventTime, int count, int unusedDisplayId) { + void onKeyUp(long eventTime, int count, int displayId, int deviceId, int metaState) { if (count == 1) { // Save info about the most recent task on the first press of the stem key. This // may be used later to switch to the most recent app using double press gesture. @@ -2816,6 +2817,33 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + // TODO(b/358569822): Move to KeyGestureController. + private final class StylusTailButtonRule extends SingleKeyGestureDetector.SingleKeyRule { + StylusTailButtonRule() { + super(KEYCODE_STYLUS_BUTTON_TAIL); + } + + @Override + int getMaxMultiPressCount() { + return 2; + } + + @Override + void onPress(long downTime, int displayId) { + + } + + @Override + void onKeyUp(long eventTime, int pressCount, int displayId, int deviceId, int metaState) { + if (pressCount != 1) { + return; + } + // Single press on tail button triggers the open notes gesture. + handleKeyGestureInKeyGestureController(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, + deviceId, KEYCODE_STYLUS_BUTTON_TAIL, metaState); + } + } + private void initSingleKeyGestureRules(Looper looper) { mSingleKeyGestureDetector = SingleKeyGestureDetector.get(mContext, looper); mSingleKeyGestureDetector.addRule(new PowerKeyRule()); @@ -2825,6 +2853,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (hasStemPrimaryBehavior()) { mSingleKeyGestureDetector.addRule(new StemPrimaryKeyRule()); } + mSingleKeyGestureDetector.addRule(new StylusTailButtonRule()); } /** @@ -3314,6 +3343,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { new int[]{event.getKeyCode()}, event.getMetaState(), gestureType); } + private void handleKeyGestureInKeyGestureController( + @KeyGestureEvent.KeyGestureType int gestureType, int deviceId, int keyCode, + int metaState) { + if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) { + return; + } + mInputManagerInternal.handleKeyGestureInKeyGestureController(deviceId, new int[]{keyCode}, + metaState, gestureType); + } + @Override public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) { return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId); diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java index a060f504b809..441d3eaf2348 100644 --- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -105,9 +105,9 @@ public final class SingleKeyGestureDetector { /** * Maximum count of multi presses. - * Return 1 will trigger onPress immediately when {@link KeyEvent.ACTION_UP}. + * Return 1 will trigger onPress immediately when {@link KeyEvent#ACTION_UP}. * Otherwise trigger onMultiPress immediately when reach max count when - * {@link KeyEvent.ACTION_DOWN}. + * {@link KeyEvent#ACTION_DOWN}. */ int getMaxMultiPressCount() { return 1; @@ -153,8 +153,10 @@ public final class SingleKeyGestureDetector { * @param eventTime the timestamp of this event * @param pressCount the number of presses detected leading up to this key up event * @param displayId the display ID of the event + * @param deviceId the ID of the input device that generated this event + * @param metaState the state of the modifiers when this gesture was detected */ - void onKeyUp(long eventTime, int pressCount, int displayId) {} + void onKeyUp(long eventTime, int pressCount, int displayId, int deviceId, int metaState) {} @Override public String toString() { @@ -183,7 +185,11 @@ public final class SingleKeyGestureDetector { } private record MessageObject(SingleKeyRule activeRule, int keyCode, int pressCount, - int displayId) { + int displayId, int metaState, int deviceId) { + MessageObject(SingleKeyRule activeRule, int keyCode, int pressCount, KeyEvent event) { + this(activeRule, keyCode, pressCount, event.getDisplayId(), event.getMetaState(), + event.getDeviceId()); + } } static SingleKeyGestureDetector get(Context context, Looper looper) { @@ -236,7 +242,7 @@ public final class SingleKeyGestureDetector { mHandler.removeMessages(MSG_KEY_LONG_PRESS); mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); MessageObject object = new MessageObject(mActiveRule, keyCode, /* pressCount= */ 1, - event.getDisplayId()); + event); final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessage(msg); @@ -284,7 +290,7 @@ public final class SingleKeyGestureDetector { if (mKeyPressCounter == 1) { if (mActiveRule.supportLongPress()) { MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, - event.getDisplayId()); + event); final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs()); @@ -292,7 +298,7 @@ public final class SingleKeyGestureDetector { if (mActiveRule.supportVeryLongPress()) { MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, - event.getDisplayId()); + event); final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, mActiveRule.getVeryLongPressTimeoutMs()); @@ -310,7 +316,7 @@ public final class SingleKeyGestureDetector { + " reached the max count " + mKeyPressCounter); } MessageObject object = new MessageObject(mActiveRule, keyCode, mKeyPressCounter, - event.getDisplayId()); + event); final Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessage(msg); @@ -351,7 +357,7 @@ public final class SingleKeyGestureDetector { if (event.getKeyCode() == mActiveRule.mKeyCode) { // key-up action should always be triggered if not processed by long press. MessageObject object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, - mKeyPressCounter, event.getDisplayId()); + mKeyPressCounter, event); Message msgKeyUp = mHandler.obtainMessage(MSG_KEY_UP, object); msgKeyUp.setAsynchronous(true); mHandler.sendMessage(msgKeyUp); @@ -362,7 +368,7 @@ public final class SingleKeyGestureDetector { Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode())); } object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, - /* pressCount= */ 1, event.getDisplayId()); + /* pressCount= */ 1, event); Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessage(msg); @@ -373,7 +379,7 @@ public final class SingleKeyGestureDetector { // This could be a multi-press. Wait a little bit longer to confirm. if (mKeyPressCounter < mActiveRule.getMaxMultiPressCount()) { object = new MessageObject(mActiveRule, mActiveRule.mKeyCode, - mKeyPressCounter, event.getDisplayId()); + mKeyPressCounter, event); Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, object); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT); @@ -452,7 +458,8 @@ public final class SingleKeyGestureDetector { Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode) + " on display " + displayId); } - rule.onKeyUp(mLastDownTime, pressCount, displayId); + rule.onKeyUp(mLastDownTime, pressCount, displayId, object.deviceId, + object.metaState); break; case MSG_KEY_LONG_PRESS: if (DEBUG) { diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java index 20184e9fd1a7..f69a017fc45a 100644 --- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java +++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java @@ -16,13 +16,19 @@ package com.android.server.power; +import android.app.AlarmManager; +import android.app.IAlarmCompleteListener; +import android.app.IAlarmListener; +import android.app.IAlarmManager; import android.content.Context; import android.content.Intent; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.PowerManagerInternal; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.ShellCommand; +import android.os.SystemClock; import android.util.SparseArray; import android.view.Display; @@ -34,12 +40,26 @@ class PowerManagerShellCommand extends ShellCommand { private final Context mContext; private final PowerManagerService.BinderService mService; + private final IAlarmListener mAlarmListener; + private IAlarmManager mAlarmManager; private SparseArray<WakeLock> mProxWakelocks = new SparseArray<>(); PowerManagerShellCommand(Context context, PowerManagerService.BinderService service) { mContext = context; mService = service; + mAlarmManager = + IAlarmManager.Stub.asInterface(ServiceManager.getService(Context.ALARM_SERVICE)); + mAlarmListener = new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + mService.wakeUp( + SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_APPLICATION, + "PowerManagerShellCommand", + mContext.getOpPackageName()); + } + }; } @Override @@ -65,6 +85,10 @@ class PowerManagerShellCommand extends ShellCommand { return runSetProx(); case "set-face-down-detector": return runSetFaceDownDetector(); + case "sleep": + return runSleep(); + case "wakeup": + return runWakeUp(); default: return handleDefaultCommands(cmd); } @@ -194,6 +218,70 @@ class PowerManagerShellCommand extends ShellCommand { return 0; } + private int runSleep() { + try { + mService.goToSleep( + SystemClock.uptimeMillis(), + PowerManager.GO_TO_SLEEP_REASON_APPLICATION, + PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE); + } catch (Exception e) { + final PrintWriter pw = getOutPrintWriter(); + pw.println("Error: " + e); + return -1; + } + return 0; + } + + private int runWakeUp() { + final PrintWriter pw = getOutPrintWriter(); + String delay = getNextArg(); + if (delay == null) { + try { + mService.wakeUp( + SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_APPLICATION, + "PowerManagerShellCommand", + mContext.getOpPackageName()); + } catch (Exception e) { + pw.println("Error: " + e); + return -1; + } + } else { + long delayMillis; + try { + delayMillis = Long.parseLong(delay); + } catch (NumberFormatException e) { + pw.println("Error: Can't parse arg " + delay + " as a long: " + e); + return -1; + } + if (delayMillis < 0) { + pw.println("Error: Can't set a negative delay: " + delayMillis); + return -1; + } + long wakeUpTime = System.currentTimeMillis() + delayMillis; + if (mAlarmManager == null) { + // PowerManagerShellCommand may be initialized before AlarmManagerService + // is brought up. Make sure mAlarmManager exists. + mAlarmManager = IAlarmManager.Stub.asInterface( + ServiceManager.getService(Context.ALARM_SERVICE)); + } + try { + // This command is called by the shell, which has "com.android.shell" as package + // name. + pw.println("Schedule an alarm to wakeup in " + + delayMillis + " ms, on behalf of shell."); + mAlarmManager.set("com.android.shell", + AlarmManager.RTC_WAKEUP, wakeUpTime, + 0, 0, AlarmManager.FLAG_PRIORITIZE, + null, mAlarmListener, "PowerManagerShellCommand", null, null); + } catch (Exception e) { + pw.println("Error: " + e); + return -1; + } + } + return 0; + } + @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); @@ -221,6 +309,11 @@ class PowerManagerShellCommand extends ShellCommand { pw.println(" created by set-prox including their held status."); pw.println(" set-face-down-detector [true|false]"); pw.println(" sets whether we use face down detector timeouts or not"); + pw.println(" sleep"); + pw.println(" requests to sleep the device"); + pw.println(" wakeup <delay>"); + pw.println(" requests to wake up the device. If a delay of milliseconds is specified,"); + pw.println(" alarm manager will schedule a wake up after the delay."); pw.println(); Intent.printIntentArgsHelp(pw , ""); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java index e3e83b3e1fd7..74ca23038666 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java @@ -171,63 +171,9 @@ public class WallpaperDataParser { stream = new FileInputStream(file); TypedXmlPullParser parser = Xml.resolvePullParser(stream); - int type; - do { - type = parser.next(); - if (type == XmlPullParser.START_TAG) { - String tag = parser.getName(); - if (("wp".equals(tag) && loadSystem) || ("kwp".equals(tag) && loadLock)) { - if ("kwp".equals(tag)) { - lockWallpaper = new WallpaperData(userId, FLAG_LOCK); - } - WallpaperData wallpaperToParse = - "wp".equals(tag) ? wallpaper : lockWallpaper; - - if (!multiCrop()) { - parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints); - } - - String comp = parser.getAttributeValue(null, "component"); - if (removeNextWallpaperComponent()) { - wallpaperToParse.setComponent(comp != null - ? ComponentName.unflattenFromString(comp) - : null); - if (wallpaperToParse.getComponent() == null - || "android".equals(wallpaperToParse.getComponent() - .getPackageName())) { - wallpaperToParse.setComponent(mImageWallpaper); - } - } else { - wallpaperToParse.nextWallpaperComponent = comp != null - ? ComponentName.unflattenFromString(comp) - : null; - if (wallpaperToParse.nextWallpaperComponent == null - || "android".equals(wallpaperToParse.nextWallpaperComponent - .getPackageName())) { - wallpaperToParse.nextWallpaperComponent = mImageWallpaper; - } - } + lockWallpaper = loadSettingsFromSerializer(parser, wallpaper, userId, loadSystem, + loadLock, keepDimensionHints, wpdData); - if (multiCrop()) { - parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints); - } - - if (DEBUG) { - Slog.v(TAG, "mWidth:" + wpdData.mWidth); - Slog.v(TAG, "mHeight:" + wpdData.mHeight); - Slog.v(TAG, "cropRect:" + wallpaper.cropHint); - Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors); - Slog.v(TAG, "mName:" + wallpaper.name); - if (removeNextWallpaperComponent()) { - Slog.v(TAG, "mWallpaperComponent:" + wallpaper.getComponent()); - } else { - Slog.v(TAG, "mNextWallpaperComponent:" - + wallpaper.nextWallpaperComponent); - } - } - } - } - } while (type != XmlPullParser.END_DOCUMENT); success = true; } catch (FileNotFoundException e) { Slog.w(TAG, "no current wallpaper -- first boot?"); @@ -275,6 +221,75 @@ public class WallpaperDataParser { return new WallpaperLoadingResult(wallpaper, lockWallpaper, success); } + // This method updates `wallpaper` in place, but returns `lockWallpaper`. This is because + // `wallpaper` already exists if it's being read per `loadSystem`, but `lockWallpaper` is + // created conditionally if there is lock screen wallpaper data to read. + @VisibleForTesting + WallpaperData loadSettingsFromSerializer(TypedXmlPullParser parser, WallpaperData wallpaper, + int userId, boolean loadSystem, boolean loadLock, boolean keepDimensionHints, + DisplayData wpdData) throws IOException, XmlPullParserException { + WallpaperData lockWallpaper = null; + int type; + do { + type = parser.next(); + if (type == XmlPullParser.START_TAG) { + String tag = parser.getName(); + if (("wp".equals(tag) && loadSystem) || ("kwp".equals(tag) && loadLock)) { + if ("kwp".equals(tag)) { + lockWallpaper = new WallpaperData(userId, FLAG_LOCK); + } + WallpaperData wallpaperToParse = + "wp".equals(tag) ? wallpaper : lockWallpaper; + + if (!multiCrop()) { + parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints); + } + + String comp = parser.getAttributeValue(null, "component"); + if (removeNextWallpaperComponent()) { + wallpaperToParse.setComponent(comp != null + ? ComponentName.unflattenFromString(comp) + : null); + if (wallpaperToParse.getComponent() == null + || "android".equals(wallpaperToParse.getComponent() + .getPackageName())) { + wallpaperToParse.setComponent(mImageWallpaper); + } + } else { + wallpaperToParse.nextWallpaperComponent = comp != null + ? ComponentName.unflattenFromString(comp) + : null; + if (wallpaperToParse.nextWallpaperComponent == null + || "android".equals(wallpaperToParse.nextWallpaperComponent + .getPackageName())) { + wallpaperToParse.nextWallpaperComponent = mImageWallpaper; + } + } + + if (multiCrop()) { + parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints); + } + + if (DEBUG) { + Slog.v(TAG, "mWidth:" + wpdData.mWidth); + Slog.v(TAG, "mHeight:" + wpdData.mHeight); + Slog.v(TAG, "cropRect:" + wallpaper.cropHint); + Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors); + Slog.v(TAG, "mName:" + wallpaper.name); + if (removeNextWallpaperComponent()) { + Slog.v(TAG, "mWallpaperComponent:" + wallpaper.getComponent()); + } else { + Slog.v(TAG, "mNextWallpaperComponent:" + + wallpaper.nextWallpaperComponent); + } + } + } + } + } while (type != XmlPullParser.END_DOCUMENT); + + return lockWallpaper; + } + private void ensureSaneWallpaperData(WallpaperData wallpaper) { // Only overwrite cropHint if the rectangle is invalid. if (wallpaper.cropHint.width() < 0 @@ -449,18 +464,7 @@ public class WallpaperDataParser { try { fstream = new FileOutputStream(journal.chooseForWrite(), false); TypedXmlSerializer out = Xml.resolveSerializer(fstream); - out.startDocument(null, true); - - if (wallpaper != null) { - writeWallpaperAttributes(out, "wp", wallpaper); - } - - if (lockWallpaper != null) { - writeWallpaperAttributes(out, "kwp", lockWallpaper); - } - - out.endDocument(); - + saveSettingsToSerializer(out, wallpaper, lockWallpaper); fstream.flush(); FileUtils.sync(fstream); fstream.close(); @@ -472,6 +476,22 @@ public class WallpaperDataParser { } @VisibleForTesting + void saveSettingsToSerializer(TypedXmlSerializer out, WallpaperData wallpaper, + WallpaperData lockWallpaper) throws IOException { + out.startDocument(null, true); + + if (wallpaper != null) { + writeWallpaperAttributes(out, "wp", wallpaper); + } + + if (lockWallpaper != null) { + writeWallpaperAttributes(out, "kwp", lockWallpaper); + } + + out.endDocument(); + } + + @VisibleForTesting void writeWallpaperAttributes(TypedXmlSerializer out, String tag, WallpaperData wallpaper) throws IllegalArgumentException, IllegalStateException, IOException { if (DEBUG) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ccc9b17ff840..12d733fc8c1a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2641,9 +2641,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return true; } // Only do transfer after transaction has done when starting window exist. - if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) { - mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT; - return true; + if (mStartingData != null) { + final boolean isWaitingForSyncTransactionCommit = + Flags.removeStartingWindowWaitForMultiTransitions() + ? getSyncTransactionCommitCallbackDepth() > 0 + : mStartingData.mWaitForSyncTransactionCommit; + if (isWaitingForSyncTransactionCommit) { + mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT; + return true; + } } requestCopySplashScreen(); return isTransferringSplashScreen(); @@ -2847,7 +2853,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean animate; final boolean hasImeSurface; if (mStartingData != null) { - if (mStartingData.mWaitForSyncTransactionCommit + final boolean isWaitingForSyncTransactionCommit = + Flags.removeStartingWindowWaitForMultiTransitions() + ? getSyncTransactionCommitCallbackDepth() > 0 + : mStartingData.mWaitForSyncTransactionCommit; + if (isWaitingForSyncTransactionCommit || mSyncState != SYNC_STATE_NONE) { mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY; mStartingData.mPrepareRemoveAnimation = prepareAnimation; diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java index 156d8a065b67..34b5f6a24d41 100644 --- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java +++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java @@ -103,7 +103,7 @@ public final class DesktopModeBoundsCalculator { final TaskDisplayArea displayArea = task.getDisplayArea(); final Rect screenBounds = displayArea.getBounds(); final Size idealSize = calculateIdealSize(screenBounds, DESKTOP_MODE_INITIAL_BOUNDS_SCALE); - if (!DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isEnabled()) { + if (!DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) { return centerInScreen(idealSize, screenBounds); } if (activity.mAppCompatController.getAppCompatAspectRatioOverrides() diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java index da7631723185..b5ea0bdfc27a 100644 --- a/services/core/java/com/android/server/wm/DesktopModeHelper.java +++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java @@ -36,7 +36,7 @@ public final class DesktopModeHelper { /** Whether desktop mode is enabled. */ static boolean isDesktopModeEnabled() { - return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isEnabled(); + return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue(); } /** diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java index b84ef37f6b06..2394da91684d 100644 --- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java +++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java @@ -50,7 +50,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.concurrent.CountDownLatch; import java.util.function.IntFunction; /** @@ -93,12 +92,6 @@ class LaunchParamsPersister { new SparseArray<>(); /** - * A map from user ID to the active {@link LoadingQueueItem} user when we're loading the launch - * params for that user. - */ - private final SparseArray<LoadingQueueItem> mLoadingItemMap = new SparseArray<>(); - - /** * A map from {@link android.content.pm.ActivityInfo.WindowLayout#windowLayoutAffinity} to * activity's component name for reverse queries from window layout affinities to activities. * Used to decide if we should use another activity's record with the same affinity. @@ -124,30 +117,112 @@ class LaunchParamsPersister { } void onUnlockUser(int userId) { - if (mLoadingItemMap.contains(userId)) { - Slog.e(TAG, "Duplicate onUnlockUser " + userId); - return; - } - final LoadingQueueItem item = new LoadingQueueItem(userId); - mLoadingItemMap.put(userId, item); - mPersisterQueue.addItem(item, /* flush */ false); + loadLaunchParams(userId); } void onCleanupUser(int userId) { - final LoadingQueueItem item = mLoadingItemMap.removeReturnOld(userId); - if (item != null) { - item.abort(); - - mPersisterQueue.removeItems( - queueItem -> queueItem.mUserId == userId, LoadingQueueItem.class); - } mLaunchParamsMap.remove(userId); } - private void waitForLoading(int userId) { - final LoadingQueueItem item = mLoadingItemMap.get(userId); - if (item != null) { - item.waitUntilFinish(); + private void loadLaunchParams(int userId) { + final List<File> filesToDelete = new ArrayList<>(); + final File launchParamsFolder = getLaunchParamFolder(userId); + if (!launchParamsFolder.isDirectory()) { + Slog.i(TAG, "Didn't find launch param folder for user " + userId); + return; + } + + final Set<String> packages = new ArraySet<>(mPackageList.getPackageNames()); + + final File[] paramsFiles = launchParamsFolder.listFiles(); + final ArrayMap<ComponentName, PersistableLaunchParams> map = + new ArrayMap<>(paramsFiles.length); + mLaunchParamsMap.put(userId, map); + + for (File paramsFile : paramsFiles) { + if (!paramsFile.isFile()) { + Slog.w(TAG, paramsFile.getAbsolutePath() + " is not a file."); + continue; + } + if (!paramsFile.getName().endsWith(LAUNCH_PARAMS_FILE_SUFFIX)) { + Slog.w(TAG, "Unexpected params file name: " + paramsFile.getName()); + filesToDelete.add(paramsFile); + continue; + } + String paramsFileName = paramsFile.getName(); + // Migrate all records from old separator to new separator. + final int oldSeparatorIndex = + paramsFileName.indexOf(OLD_ESCAPED_COMPONENT_SEPARATOR); + if (oldSeparatorIndex != -1) { + if (paramsFileName.indexOf( + OLD_ESCAPED_COMPONENT_SEPARATOR, oldSeparatorIndex + 1) != -1) { + // Rare case. We have more than one old escaped component separator probably + // because this app uses underscore in their package name. We can't distinguish + // which one is the real separator so let's skip it. + filesToDelete.add(paramsFile); + continue; + } + paramsFileName = paramsFileName.replace( + OLD_ESCAPED_COMPONENT_SEPARATOR, ESCAPED_COMPONENT_SEPARATOR); + final File newFile = new File(launchParamsFolder, paramsFileName); + if (paramsFile.renameTo(newFile)) { + paramsFile = newFile; + } else { + // Rare case. For some reason we can't rename the file. Let's drop this record + // instead. + filesToDelete.add(paramsFile); + continue; + } + } + final String componentNameString = paramsFileName.substring( + 0 /* beginIndex */, + paramsFileName.length() - LAUNCH_PARAMS_FILE_SUFFIX.length()) + .replace(ESCAPED_COMPONENT_SEPARATOR, ORIGINAL_COMPONENT_SEPARATOR); + final ComponentName name = ComponentName.unflattenFromString( + componentNameString); + if (name == null) { + Slog.w(TAG, "Unexpected file name: " + paramsFileName); + filesToDelete.add(paramsFile); + continue; + } + + if (!packages.contains(name.getPackageName())) { + // Rare case. PersisterQueue doesn't have a chance to remove files for removed + // packages last time. + filesToDelete.add(paramsFile); + continue; + } + + try (InputStream in = new FileInputStream(paramsFile)) { + final PersistableLaunchParams params = new PersistableLaunchParams(); + final TypedXmlPullParser parser = Xml.resolvePullParser(in); + int event; + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT + && event != XmlPullParser.END_TAG) { + if (event != XmlPullParser.START_TAG) { + continue; + } + + final String tagName = parser.getName(); + if (!TAG_LAUNCH_PARAMS.equals(tagName)) { + Slog.w(TAG, "Unexpected tag name: " + tagName); + continue; + } + + params.restore(paramsFile, parser); + } + + map.put(name, params); + addComponentNameToLaunchParamAffinityMapIfNotNull( + name, params.mWindowLayoutAffinity); + } catch (Exception e) { + Slog.w(TAG, "Failed to restore launch params for " + name, e); + filesToDelete.add(paramsFile); + } + } + + if (!filesToDelete.isEmpty()) { + mPersisterQueue.addItem(new CleanUpComponentQueueItem(filesToDelete), true); } } @@ -161,7 +236,6 @@ class LaunchParamsPersister { return; } final int userId = task.mUserId; - waitForLoading(userId); PersistableLaunchParams params; ArrayMap<ComponentName, PersistableLaunchParams> map = mLaunchParamsMap.get(userId); if (map == null) { @@ -223,7 +297,6 @@ class LaunchParamsPersister { void getLaunchParams(Task task, ActivityRecord activity, LaunchParams outParams) { final ComponentName name = task != null ? task.realActivity : activity.mActivityComponent; final int userId = task != null ? task.mUserId : activity.mUserId; - waitForLoading(userId); final String windowLayoutAffinity; if (task != null) { windowLayoutAffinity = task.mWindowLayoutAffinity; @@ -321,156 +394,6 @@ class LaunchParamsPersister { } } - /** - * The work item used to load launch parameters with {@link PersisterQueue} in a background - * thread, so that we don't block the thread {@link com.android.server.am.UserController} uses - * to broadcast user state changes for I/O operations. See b/365983567 for more details. - */ - private class LoadingQueueItem implements PersisterQueue.QueueItem { - private final int mUserId; - private final CountDownLatch mLatch = new CountDownLatch(1); - private boolean mAborted = false; - - private LoadingQueueItem(int userId) { - mUserId = userId; - } - - @Override - public void process() { - try { - loadLaunchParams(); - } finally { - synchronized (mSupervisor.mService.getGlobalLock()) { - mLoadingItemMap.remove(mUserId); - mLatch.countDown(); - } - } - } - - private void abort() { - mAborted = true; - } - - private void waitUntilFinish() { - if (mAborted) { - return; - } - - try { - mLatch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - private void loadLaunchParams() { - final List<File> filesToDelete = new ArrayList<>(); - final File launchParamsFolder = getLaunchParamFolder(mUserId); - if (!launchParamsFolder.isDirectory()) { - Slog.i(TAG, "Didn't find launch param folder for user " + mUserId); - return; - } - - final Set<String> packages = new ArraySet<>(mPackageList.getPackageNames()); - - final File[] paramsFiles = launchParamsFolder.listFiles(); - final ArrayMap<ComponentName, PersistableLaunchParams> map = - new ArrayMap<>(paramsFiles.length); - - for (File paramsFile : paramsFiles) { - if (!paramsFile.isFile()) { - Slog.w(TAG, paramsFile.getAbsolutePath() + " is not a file."); - continue; - } - if (!paramsFile.getName().endsWith(LAUNCH_PARAMS_FILE_SUFFIX)) { - Slog.w(TAG, "Unexpected params file name: " + paramsFile.getName()); - filesToDelete.add(paramsFile); - continue; - } - String paramsFileName = paramsFile.getName(); - // Migrate all records from old separator to new separator. - final int oldSeparatorIndex = - paramsFileName.indexOf(OLD_ESCAPED_COMPONENT_SEPARATOR); - if (oldSeparatorIndex != -1) { - if (paramsFileName.indexOf( - OLD_ESCAPED_COMPONENT_SEPARATOR, oldSeparatorIndex + 1) != -1) { - // Rare case. We have more than one old escaped component separator probably - // because this app uses underscore in their package name. We can't - // distinguish which one is the real separator so let's skip it. - filesToDelete.add(paramsFile); - continue; - } - paramsFileName = paramsFileName.replace( - OLD_ESCAPED_COMPONENT_SEPARATOR, ESCAPED_COMPONENT_SEPARATOR); - final File newFile = new File(launchParamsFolder, paramsFileName); - if (paramsFile.renameTo(newFile)) { - paramsFile = newFile; - } else { - // Rare case. For some reason we can't rename the file. Let's drop this - // record instead. - filesToDelete.add(paramsFile); - continue; - } - } - final String componentNameString = paramsFileName.substring( - 0 /* beginIndex */, - paramsFileName.length() - LAUNCH_PARAMS_FILE_SUFFIX.length()) - .replace(ESCAPED_COMPONENT_SEPARATOR, ORIGINAL_COMPONENT_SEPARATOR); - final ComponentName name = ComponentName.unflattenFromString( - componentNameString); - if (name == null) { - Slog.w(TAG, "Unexpected file name: " + paramsFileName); - filesToDelete.add(paramsFile); - continue; - } - - if (!packages.contains(name.getPackageName())) { - // Rare case. PersisterQueue doesn't have a chance to remove files for removed - // packages last time. - filesToDelete.add(paramsFile); - continue; - } - - try (InputStream in = new FileInputStream(paramsFile)) { - final PersistableLaunchParams params = new PersistableLaunchParams(); - final TypedXmlPullParser parser = Xml.resolvePullParser(in); - int event; - while ((event = parser.next()) != XmlPullParser.END_DOCUMENT - && event != XmlPullParser.END_TAG) { - if (event != XmlPullParser.START_TAG) { - continue; - } - - final String tagName = parser.getName(); - if (!TAG_LAUNCH_PARAMS.equals(tagName)) { - Slog.w(TAG, "Unexpected tag name: " + tagName); - continue; - } - - params.restore(paramsFile, parser); - } - - map.put(name, params); - addComponentNameToLaunchParamAffinityMapIfNotNull( - name, params.mWindowLayoutAffinity); - } catch (Exception e) { - Slog.w(TAG, "Failed to restore launch params for " + name, e); - filesToDelete.add(paramsFile); - } - } - - synchronized (mSupervisor.mService.getGlobalLock()) { - if (!mAborted) { - mLaunchParamsMap.put(mUserId, map); - } - } - - if (!filesToDelete.isEmpty()) { - mPersisterQueue.addItem(new CleanUpComponentQueueItem(filesToDelete), true); - } - } - } - private class LaunchParamsWriteQueueItem implements PersisterQueue.WriteQueueItem<LaunchParamsWriteQueueItem> { private final int mUserId; @@ -543,8 +466,7 @@ class LaunchParamsPersister { } } - private static class CleanUpComponentQueueItem - implements PersisterQueue.WriteQueueItem<CleanUpComponentQueueItem> { + private class CleanUpComponentQueueItem implements PersisterQueue.WriteQueueItem { private final List<File> mComponentFiles; private CleanUpComponentQueueItem(List<File> componentFiles) { @@ -561,7 +483,7 @@ class LaunchParamsPersister { } } - private static class PersistableLaunchParams { + private class PersistableLaunchParams { private static final String ATTR_WINDOWING_MODE = "windowing_mode"; private static final String ATTR_DISPLAY_UNIQUE_ID = "display_unique_id"; private static final String ATTR_BOUNDS = "bounds"; diff --git a/services/core/java/com/android/server/wm/PersisterQueue.java b/services/core/java/com/android/server/wm/PersisterQueue.java index f66069c6f1bc..9dc3d6a81338 100644 --- a/services/core/java/com/android/server/wm/PersisterQueue.java +++ b/services/core/java/com/android/server/wm/PersisterQueue.java @@ -49,16 +49,14 @@ class PersisterQueue { /** Special value for mWriteTime to mean don't wait, just write */ private static final long FLUSH_QUEUE = -1; - /** - * A {@link QueueItem} that doesn't do anything. Used to trigger - * {@link Listener#onPreProcessItem}. - */ - static final QueueItem EMPTY_ITEM = () -> { }; + /** An {@link WriteQueueItem} that doesn't do anything. Used to trigger {@link + * Listener#onPreProcessItem}. */ + static final WriteQueueItem EMPTY_ITEM = () -> { }; private final long mInterWriteDelayMs; private final long mPreTaskDelayMs; private final LazyTaskWriterThread mLazyTaskWriterThread; - private final ArrayList<QueueItem> mQueue = new ArrayList<>(); + private final ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<>(); private final ArrayList<Listener> mListeners = new ArrayList<>(); @@ -107,10 +105,10 @@ class PersisterQueue { mLazyTaskWriterThread.join(); } - synchronized void addItem(QueueItem item, boolean flush) { - mQueue.add(item); + synchronized void addItem(WriteQueueItem item, boolean flush) { + mWriteQueue.add(item); - if (flush || mQueue.size() > MAX_WRITE_QUEUE_LENGTH) { + if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) { mNextWriteTime = FLUSH_QUEUE; } else if (mNextWriteTime == 0) { mNextWriteTime = SystemClock.uptimeMillis() + mPreTaskDelayMs; @@ -118,12 +116,11 @@ class PersisterQueue { notify(); } - synchronized <T extends WriteQueueItem<T>> T findLastItem(Predicate<T> predicate, - Class<T> clazz) { - for (int i = mQueue.size() - 1; i >= 0; --i) { - QueueItem queueItem = mQueue.get(i); - if (clazz.isInstance(queueItem)) { - T item = clazz.cast(queueItem); + synchronized <T extends WriteQueueItem> T findLastItem(Predicate<T> predicate, Class<T> clazz) { + for (int i = mWriteQueue.size() - 1; i >= 0; --i) { + WriteQueueItem writeQueueItem = mWriteQueue.get(i); + if (clazz.isInstance(writeQueueItem)) { + T item = clazz.cast(writeQueueItem); if (predicate.test(item)) { return item; } @@ -137,7 +134,7 @@ class PersisterQueue { * Updates the last item found in the queue that matches the given item, or adds it to the end * of the queue if no such item is found. */ - synchronized <T extends WriteQueueItem<T>> void updateLastOrAddItem(T item, boolean flush) { + synchronized <T extends WriteQueueItem> void updateLastOrAddItem(T item, boolean flush) { final T itemToUpdate = findLastItem(item::matches, (Class<T>) item.getClass()); if (itemToUpdate == null) { addItem(item, flush); @@ -151,15 +148,15 @@ class PersisterQueue { /** * Removes all items with which given predicate returns {@code true}. */ - synchronized <T extends QueueItem> void removeItems(Predicate<T> predicate, + synchronized <T extends WriteQueueItem> void removeItems(Predicate<T> predicate, Class<T> clazz) { - for (int i = mQueue.size() - 1; i >= 0; --i) { - QueueItem queueItem = mQueue.get(i); - if (clazz.isInstance(queueItem)) { - T item = clazz.cast(queueItem); + for (int i = mWriteQueue.size() - 1; i >= 0; --i) { + WriteQueueItem writeQueueItem = mWriteQueue.get(i); + if (clazz.isInstance(writeQueueItem)) { + T item = clazz.cast(writeQueueItem); if (predicate.test(item)) { if (DEBUG) Slog.d(TAG, "Removing " + item + " from write queue."); - mQueue.remove(i); + mWriteQueue.remove(i); } } } @@ -204,7 +201,7 @@ class PersisterQueue { // See https://b.corp.google.com/issues/64438652#comment7 // If mNextWriteTime, then don't delay between each call to saveToXml(). - final QueueItem item; + final WriteQueueItem item; synchronized (this) { if (mNextWriteTime != FLUSH_QUEUE) { // The next write we don't have to wait so long. @@ -215,7 +212,7 @@ class PersisterQueue { } } - while (mQueue.isEmpty()) { + while (mWriteQueue.isEmpty()) { if (mNextWriteTime != 0) { mNextWriteTime = 0; // idle. notify(); // May need to wake up flush(). @@ -227,18 +224,17 @@ class PersisterQueue { } if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely."); wait(); - // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_TASK_DELAY_MS + // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS // from now. } - item = mQueue.remove(0); + item = mWriteQueue.remove(0); - final boolean isWriteItem = item instanceof WriteQueueItem<?>; long now = SystemClock.uptimeMillis(); if (DEBUG) { Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" + mNextWriteTime - + " mWriteQueue.size=" + mQueue.size() + " isWriteItem=" + isWriteItem); + + " mWriteQueue.size=" + mWriteQueue.size()); } - while (now < mNextWriteTime && isWriteItem) { + while (now < mNextWriteTime) { if (DEBUG) { Slog.d(TAG, "LazyTaskWriter: waiting " + (mNextWriteTime - now)); } @@ -252,18 +248,9 @@ class PersisterQueue { item.process(); } - /** - * An item the {@link PersisterQueue} processes. Used for loading tasks. Subclasses of this, but - * not {@link WriteQueueItem}, aren't subject to waiting. - */ - interface QueueItem { + interface WriteQueueItem<T extends WriteQueueItem<T>> { void process(); - } - /** - * A write item the {@link PersisterQueue} processes. Used for persisting tasks. - */ - interface WriteQueueItem<T extends WriteQueueItem<T>> extends QueueItem { default void updateFrom(T item) {} default boolean matches(T item) { @@ -301,7 +288,7 @@ class PersisterQueue { while (true) { final boolean probablyDone; synchronized (PersisterQueue.this) { - probablyDone = mQueue.isEmpty(); + probablyDone = mWriteQueue.isEmpty(); } for (int i = mListeners.size() - 1; i >= 0; --i) { diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java index 24fb20731c43..896612d3d27a 100644 --- a/services/core/java/com/android/server/wm/StartingData.java +++ b/services/core/java/com/android/server/wm/StartingData.java @@ -68,7 +68,9 @@ public abstract class StartingData { * window. * Note this isn't equal to transition playing, the period should be * Sync finishNow -> Start transaction apply. + * @deprecated TODO(b/362347290): cleanup after fix ramp up */ + @Deprecated boolean mWaitForSyncTransactionCommit; /** diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a2fda0afb9c6..86bb75ab3f8c 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -35,6 +35,8 @@ import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; +import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; +import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION; @@ -49,6 +51,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.SurfaceControl.METADATA_TASK_ID; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; @@ -133,6 +136,7 @@ import android.app.IActivityController; import android.app.PictureInPictureParams; import android.app.TaskInfo; import android.app.WindowConfiguration; +import android.app.compat.CompatChanges; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -503,6 +507,12 @@ class Task extends TaskFragment { int mOffsetXForInsets; int mOffsetYForInsets; + /** + * Whether the compatibility overrides that change the resizability of the app should be allowed + * for the specific app. + */ + boolean mAllowForceResizeOverride = true; + private final AnimatingActivityRegistry mAnimatingActivityRegistry = new AnimatingActivityRegistry(); @@ -666,6 +676,7 @@ class Task extends TaskFragment { intent = _intent; mMinWidth = minWidth; mMinHeight = minHeight; + updateAllowForceResizeOverride(); } mAtmService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity); mHandler = new ActivityTaskHandler(mTaskSupervisor.mLooper); @@ -1028,6 +1039,7 @@ class Task extends TaskFragment { mTaskSupervisor.mRecentTasks.remove(this); mTaskSupervisor.mRecentTasks.add(this); } + updateAllowForceResizeOverride(); } /** Sets the original minimal width and height. */ @@ -1823,6 +1835,17 @@ class Task extends TaskFragment { -1 /* don't check PID */, -1 /* don't check UID */, this); } + private void updateAllowForceResizeOverride() { + try { + mAllowForceResizeOverride = mAtmService.mContext.getPackageManager().getProperty( + PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, + getBasePackageName()).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + // Package not found or property not defined, reset to default value. + mAllowForceResizeOverride = true; + } + } + /** * Check that a given bounds matches the application requested orientation. * @@ -2812,7 +2835,18 @@ class Task extends TaskFragment { boolean isResizeable(boolean checkPictureInPictureSupport) { final boolean forceResizable = mAtmService.mForceResizableActivities && getActivityType() == ACTIVITY_TYPE_STANDARD; - return forceResizable || ActivityInfo.isResizeableMode(mResizeMode) + if (forceResizable) return true; + + final UserHandle userHandle = UserHandle.getUserHandleForUid(mUserId); + final boolean forceResizableOverride = mAllowForceResizeOverride + && CompatChanges.isChangeEnabled( + FORCE_RESIZE_APP, getBasePackageName(), userHandle); + final boolean forceNonResizableOverride = mAllowForceResizeOverride + && CompatChanges.isChangeEnabled( + FORCE_NON_RESIZE_APP, getBasePackageName(), userHandle); + + if (forceNonResizableOverride) return false; + return forceResizableOverride || ActivityInfo.isResizeableMode(mResizeMode) || (mSupportsPictureInPicture && checkPictureInPictureSupport); } diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 92953e5a5041..83e714d82dd2 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -429,7 +429,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } final IBinder activityToken; - if (activity.getPid() == mOrganizerPid) { + if (activity.getPid() == mOrganizerPid && activity.getUid() == mOrganizerUid) { // We only pass the actual token if the activity belongs to the organizer process. activityToken = activity.token; } else { @@ -458,7 +458,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr change.setTaskFragmentToken(lastParentTfToken); } // Only pass the activity token to the client if it belongs to the same process. - if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid) { + if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid + && nextFillTaskActivity.getUid() == mOrganizerUid) { change.setOtherActivityToken(nextFillTaskActivity.token); } return change; @@ -553,6 +554,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr "Replacing existing organizer currently unsupported"); } + if (pid <= 0) { + throw new IllegalStateException("Cannot register from invalid pid: " + pid); + } + if (restoreFromCachedStateIfPossible(organizer, pid, uid, outSavedState)) { return; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 7c3f0f22608e..1c03ba571923 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -457,7 +457,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< source.setFrame(provider.getArbitraryRectangle()) .updateSideHint(getBounds()) .setBoundingRects(provider.getBoundingRects()); - if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) { source.setFlags(provider.getFlags()); } mLocalInsetsSources.put(id, source); @@ -4351,4 +4351,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< t.merge(mSyncTransaction); } + int getSyncTransactionCommitCallbackDepth() { + return mSyncTransactionCommitCallbackDepth; + } } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index d2493c513b5f..5cd117b512d4 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -357,6 +357,7 @@ public: FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId); void setStylusPointerIconEnabled(bool enabled); void setInputMethodConnectionIsActive(bool isActive); + void setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping); /* --- InputReaderPolicyInterface implementation --- */ @@ -504,6 +505,9 @@ private: // True if there is an active input method connection. bool isInputMethodConnectionActive{false}; + + // Keycodes to be remapped. + std::map<int32_t /* fromKeyCode */, int32_t /* toKeyCode */> keyRemapping{}; } mLocked GUARDED_BY(mLock); std::atomic<bool> mInteractive; @@ -761,6 +765,8 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon outConfig->stylusButtonMotionEventsEnabled = mLocked.stylusButtonMotionEventsEnabled; outConfig->stylusPointerIconEnabled = mLocked.stylusPointerIconEnabled; + + outConfig->keyRemapping = mLocked.keyRemapping; } // release lock } @@ -1910,6 +1916,16 @@ void NativeInputManager::setInputMethodConnectionIsActive(bool isActive) { mInputManager->getDispatcher().setInputMethodConnectionIsActive(isActive); } +void NativeInputManager::setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping) { + { // acquire lock + std::scoped_lock _l(mLock); + mLocked.keyRemapping = keyRemapping; + } // release lock + + mInputManager->getReader().requestRefreshConfiguration( + InputReaderConfiguration::Change::KEY_REMAPPING); +} + // ---------------------------------------------------------------------------- static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) { @@ -1983,10 +1999,19 @@ static std::vector<int32_t> getIntArray(JNIEnv* env, jintArray arr) { return vec; } -static void nativeAddKeyRemapping(JNIEnv* env, jobject nativeImplObj, jint deviceId, - jint fromKeyCode, jint toKeyCode) { +static void nativeSetKeyRemapping(JNIEnv* env, jobject nativeImplObj, jintArray fromKeyCodesArr, + jintArray toKeyCodesArr) { + const std::vector<int32_t> fromKeycodes = getIntArray(env, fromKeyCodesArr); + const std::vector<int32_t> toKeycodes = getIntArray(env, toKeyCodesArr); + if (fromKeycodes.size() != toKeycodes.size()) { + jniThrowRuntimeException(env, "FromKeycodes and toKeycodes cannot match."); + } NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->getInputManager()->getReader().addKeyRemapping(deviceId, fromKeyCode, toKeyCode); + std::map<int32_t, int32_t> keyRemapping; + for (int i = 0; i < fromKeycodes.size(); i++) { + keyRemapping.insert_or_assign(fromKeycodes[i], toKeycodes[i]); + } + im->setKeyRemapping(keyRemapping); } static jboolean nativeHasKeys(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask, @@ -2491,7 +2516,7 @@ static jobject nativeGetLights(JNIEnv* env, jobject nativeImplObj, jint deviceId jTypeId = env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeKeyboardMicMute); } else { - ALOGW("Unknown light type %d", lightInfo.type); + ALOGW("Unknown light type %s", ftl::enum_string(lightInfo.type).c_str()); continue; } @@ -2955,7 +2980,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState}, {"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState}, {"getSwitchState", "(III)I", (void*)nativeGetSwitchState}, - {"addKeyRemapping", "(III)V", (void*)nativeAddKeyRemapping}, + {"setKeyRemapping", "([I[I)V", (void*)nativeSetKeyRemapping}, {"hasKeys", "(II[I[Z)Z", (void*)nativeHasKeys}, {"getKeyCodeForKeyLocation", "(II)I", (void*)nativeGetKeyCodeForKeyLocation}, {"createInputChannel", "(Ljava/lang/String;)Landroid/view/InputChannel;", diff --git a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt index dbbb2fe1bc72..da3e94f64e56 100644 --- a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt +++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt @@ -112,4 +112,28 @@ class AppFunctionRuntimeMetadataTest { assertThat(runtimeMetadata.appFunctionStaticMetadataQualifiedId) .isEqualTo("android\$apps-db/app_functions#com.pkg/funcId") } + + @Test + fun setEnabled_true() { + val runtimeMetadata = + AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(true).build() + + assertThat(runtimeMetadata.enabled).isTrue() + } + + @Test + fun setEnabled_false() { + val runtimeMetadata = + AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(false).build() + + assertThat(runtimeMetadata.enabled).isFalse() + } + + @Test + fun setEnabled_null() { + val runtimeMetadata = + AppFunctionRuntimeMetadata.Builder("com.pkg", "funcId").setEnabled(null).build() + + assertThat(runtimeMetadata.enabled).isNull() + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java deleted file mode 100644 index 306b4f86024e..000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.display.brightness.clamper; - -import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_OFF; -import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeClamper.BEDTIME_MODE_ON; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.verify; - -import android.content.ContentResolver; -import android.database.ContentObserver; -import android.provider.Settings; -import android.testing.TestableContext; - -import androidx.annotation.NonNull; -import androidx.test.platform.app.InstrumentationRegistry; - -import com.android.internal.display.BrightnessSynchronizer; -import com.android.server.testutils.TestHandler; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class BrightnessWearBedtimeModeClamperTest { - - private static final float BRIGHTNESS_CAP = 0.3f; - - @Mock - private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener; - - @Rule - public final TestableContext mContext = new TestableContext( - InstrumentationRegistry.getInstrumentation().getContext()); - - private final TestHandler mTestHandler = new TestHandler(null); - private final TestInjector mInjector = new TestInjector(); - private BrightnessWearBedtimeModeClamper mClamper; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mClamper = new BrightnessWearBedtimeModeClamper(mInjector, mTestHandler, mContext, - mMockClamperChangeListener, () -> BRIGHTNESS_CAP); - mTestHandler.flush(); - } - - @Test - public void testBrightnessCap() { - assertEquals(BRIGHTNESS_CAP, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON); - } - - @Test - public void testBedtimeModeOn() { - setBedtimeModeEnabled(true); - assertTrue(mClamper.isActive()); - verify(mMockClamperChangeListener).onChanged(); - } - - @Test - public void testBedtimeModeOff() { - setBedtimeModeEnabled(false); - assertFalse(mClamper.isActive()); - verify(mMockClamperChangeListener).onChanged(); - } - - @Test - public void testType() { - assertEquals(BrightnessClamper.Type.WEAR_BEDTIME_MODE, mClamper.getType()); - } - - @Test - public void testOnDisplayChanged() { - float newBrightnessCap = 0.61f; - - mClamper.onDisplayChanged(() -> newBrightnessCap); - mTestHandler.flush(); - - assertEquals(newBrightnessCap, mClamper.getBrightnessCap(), BrightnessSynchronizer.EPSILON); - verify(mMockClamperChangeListener).onChanged(); - } - - private void setBedtimeModeEnabled(boolean enabled) { - Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE, - enabled ? BEDTIME_MODE_ON : BEDTIME_MODE_OFF); - mInjector.notifyBedtimeModeChanged(); - mTestHandler.flush(); - } - - private static class TestInjector extends BrightnessWearBedtimeModeClamper.Injector { - - private ContentObserver mObserver; - - @Override - void registerBedtimeModeObserver(@NonNull ContentResolver cr, - @NonNull ContentObserver observer) { - mObserver = observer; - } - - private void notifyBedtimeModeChanged() { - if (mObserver != null) { - mObserver.dispatchChange(/* selfChange= */ false, - Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE)); - } - } - } -} diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifierTest.java new file mode 100644 index 000000000000..8271a0f44fc6 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeModifierTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.clamper; + +import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeModifier.BEDTIME_MODE_OFF; +import static com.android.server.display.brightness.clamper.BrightnessWearBedtimeModeModifier.BEDTIME_MODE_ON; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; +import android.os.IBinder; +import android.os.PowerManager; +import android.provider.Settings; +import android.testing.TestableContext; + +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.display.BrightnessSynchronizer; +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState; +import com.android.server.testutils.TestHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class BrightnessWearBedtimeModeModifierTest { + private static final int NO_MODIFIER = 0; + private static final float BRIGHTNESS_CAP = 0.3f; + private static final String DISPLAY_ID = "displayId"; + + @Mock + private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener; + @Mock + private DisplayManagerInternal.DisplayPowerRequest mMockRequest; + @Mock + private DisplayDeviceConfig mMockDisplayDeviceConfig; + @Mock + private IBinder mMockBinder; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + + private final TestHandler mTestHandler = new TestHandler(null); + private final TestInjector mInjector = new TestInjector(); + private BrightnessWearBedtimeModeModifier mModifier; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mModifier = new BrightnessWearBedtimeModeModifier(mInjector, mTestHandler, mContext, + mMockClamperChangeListener, () -> BRIGHTNESS_CAP); + mTestHandler.flush(); + } + + @Test + public void testBedtimeModeOff() { + setBedtimeModeEnabled(false); + assertModifierState( + 0.5f, true, + PowerManager.BRIGHTNESS_MAX, 0.5f, + false, true); + verify(mMockClamperChangeListener).onChanged(); + } + + @Test + public void testBedtimeModeOn() { + setBedtimeModeEnabled(true); + assertModifierState( + 0.5f, true, + BRIGHTNESS_CAP, BRIGHTNESS_CAP, + true, false); + verify(mMockClamperChangeListener).onChanged(); + } + + @Test + public void testOnDisplayChanged() { + setBedtimeModeEnabled(true); + clearInvocations(mMockClamperChangeListener); + float newBrightnessCap = 0.61f; + onDisplayChange(newBrightnessCap); + mTestHandler.flush(); + + assertModifierState( + 0.5f, true, + newBrightnessCap, 0.5f, + true, false); + verify(mMockClamperChangeListener).onChanged(); + } + + private void setBedtimeModeEnabled(boolean enabled) { + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.Wearable.BEDTIME_MODE, + enabled ? BEDTIME_MODE_ON : BEDTIME_MODE_OFF); + mInjector.notifyBedtimeModeChanged(); + mTestHandler.flush(); + } + + private void onDisplayChange(float brightnessCap) { + when(mMockDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode()) + .thenReturn(brightnessCap); + mModifier.onDisplayChanged(ClamperTestUtilsKt.createDisplayDeviceData( + mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID)); + } + + private void assertModifierState( + float currentBrightness, + boolean currentSlowChange, + float maxBrightness, float brightness, + boolean isActive, + boolean isSlowChange) { + ModifiersAggregatedState modifierState = new ModifiersAggregatedState(); + DisplayBrightnessState.Builder stateBuilder = DisplayBrightnessState.builder(); + stateBuilder.setBrightness(currentBrightness); + stateBuilder.setIsSlowChange(currentSlowChange); + + int maxBrightnessReason = isActive ? BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE + : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + int modifier = isActive ? BrightnessReason.MODIFIER_THROTTLED : NO_MODIFIER; + + mModifier.applyStateChange(modifierState); + assertThat(modifierState.mMaxBrightness).isEqualTo(maxBrightness); + assertThat(modifierState.mMaxBrightnessReason).isEqualTo(maxBrightnessReason); + + mModifier.apply(mMockRequest, stateBuilder); + + assertThat(stateBuilder.getMaxBrightness()) + .isWithin(BrightnessSynchronizer.EPSILON).of(maxBrightness); + assertThat(stateBuilder.getBrightness()) + .isWithin(BrightnessSynchronizer.EPSILON).of(brightness); + assertThat(stateBuilder.getBrightnessMaxReason()).isEqualTo(maxBrightnessReason); + assertThat(stateBuilder.getBrightnessReason().getModifier()).isEqualTo(modifier); + assertThat(stateBuilder.isSlowChange()).isEqualTo(isSlowChange); + } + + + private static class TestInjector extends BrightnessWearBedtimeModeModifier.Injector { + + private ContentObserver mObserver; + + @Override + void registerBedtimeModeObserver(@NonNull ContentResolver cr, + @NonNull ContentObserver observer) { + mObserver = observer; + } + + private void notifyBedtimeModeChanged() { + if (mObserver != null) { + mObserver.dispatchChange(/* selfChange= */ false, + Settings.Global.getUriFor(Settings.Global.Wearable.BEDTIME_MODE)); + } + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java index 3dd2f24aa4e4..1cad255b85d7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java @@ -509,8 +509,12 @@ public class ApplicationStartInfoTest { mAppStartInfoTracker.handleProcessBroadcastStart(3, app, buildIntent(COMPONENT), false /* isAlarm */); + // Add a brief delay between timestamps to make sure the clock, which is in milliseconds has + // actually incremented. + sleep(1); mAppStartInfoTracker.handleProcessBroadcastStart(2, app, buildIntent(COMPONENT), false /* isAlarm */); + sleep(1); mAppStartInfoTracker.handleProcessBroadcastStart(1, app, buildIntent(COMPONENT), false /* isAlarm */); @@ -557,9 +561,10 @@ public class ApplicationStartInfoTest { // Now load from disk. mAppStartInfoTracker.loadExistingProcessStartInfo(); - // Confirm clock has been set and that its current time is greater than the previous one. + // Confirm clock has been set and that its current time is greater than or equal to the + // previous one, thereby ensuring it was loaded from disk. assertNotNull(mAppStartInfoTracker.mMonotonicClock); - assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() > originalMonotonicTime); + assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() >= originalMonotonicTime); } private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index f6ad07d03673..2107406f9b13 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -229,8 +229,10 @@ public class MockingOomAdjusterTests { doCallRealMethod().when(mService).enqueueOomAdjTargetLocked(any(ProcessRecord.class)); doCallRealMethod().when(mService).updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_ACTIVITY); setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object()); - doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr) - .enqueueProcessChangeItemLocked(anyInt(), anyInt()); + doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(), + anyInt()); + doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(), + anyBoolean()); mService.mOomAdjuster = mService.mConstants.ENABLE_NEW_OOMADJ ? new OomAdjusterModernImpl(mService, mService.mProcessList, new ActiveUids(mService, false), mInjector) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java index 3062d5120e6f..9ba272446689 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java @@ -172,6 +172,7 @@ public class BackgroundUserSoundNotifierTest { mBackgroundUserSoundNotifier.muteAlarmSounds(mSpiedContext); verify(apc1.getPlayerProxy()).stop(); + verify(mockAudioPolicy).sendFocusLossAndUpdate(afi); verify(apc2.getPlayerProxy(), never()).stop(); } diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 0b762df86df9..9983fb4748a5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -32,6 +32,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER; +import static com.google.common.truth.Truth.assertThat; + import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -50,6 +52,7 @@ import static org.mockito.Mockito.verify; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.Flags; import android.app.WallpaperColors; import android.app.WallpaperManager; import android.content.ComponentName; @@ -64,7 +67,10 @@ import android.hardware.display.DisplayManager; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.SystemClock; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.service.wallpaper.IWallpaperConnection; import android.service.wallpaper.IWallpaperEngine; import android.service.wallpaper.WallpaperService; @@ -91,8 +97,10 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.RuleChain; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -100,6 +108,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.quality.Strictness; import org.xmlpull.v1.XmlPullParserException; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -125,6 +134,7 @@ public class WallpaperManagerServiceTests { @ClassRule public static final TestableContext sContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getTargetContext(), null); + private static ComponentName sImageWallpaperComponentName; private static ComponentName sDefaultWallpaperComponent; @@ -133,8 +143,11 @@ public class WallpaperManagerServiceTests { @Mock private DisplayManager mDisplayManager; + private final TemporaryFolder mFolder = new TemporaryFolder(); + private final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Rule - public final TemporaryFolder mFolder = new TemporaryFolder(); + public RuleChain rules = RuleChain.outerRule(mSetFlagsRule).around(mFolder); + private final SparseArray<File> mTempDirs = new SparseArray<>(); private WallpaperManagerService mService; private static IWallpaperConnection.Stub sWallpaperService; @@ -325,6 +338,7 @@ public class WallpaperManagerServiceTests { * is issued to the wallpaper. */ @Test + @Ignore("b/368345733") public void testSetCurrentComponent() throws Exception { final int testUserId = USER_SYSTEM; mService.switchUser(testUserId, null); @@ -411,26 +425,84 @@ public class WallpaperManagerServiceTests { } @Test - public void testXmlSerializationRoundtrip() { + @EnableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT) + public void testSaveLoadSettings() { + WallpaperData expectedData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0); + expectedData.setComponent(sDefaultWallpaperComponent); + expectedData.primaryColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + expectedData.mWallpaperDimAmount = 0.5f; + expectedData.mUidToDimAmount.put(0, 0.5f); + expectedData.mUidToDimAmount.put(1, 0.4f); + + ByteArrayOutputStream ostream = new ByteArrayOutputStream(); + try { + TypedXmlSerializer serializer = Xml.newBinarySerializer(); + serializer.setOutput(ostream, StandardCharsets.UTF_8.name()); + mService.mWallpaperDataParser.saveSettingsToSerializer(serializer, expectedData, null); + ostream.close(); + } catch (IOException e) { + fail("exception occurred while writing system wallpaper attributes"); + } + + WallpaperData actualData = new WallpaperData(0, FLAG_SYSTEM); + try { + ByteArrayInputStream istream = new ByteArrayInputStream(ostream.toByteArray()); + TypedXmlPullParser parser = Xml.newBinaryPullParser(); + parser.setInput(istream, StandardCharsets.UTF_8.name()); + mService.mWallpaperDataParser.loadSettingsFromSerializer(parser, + actualData, /* userId= */0, /* loadSystem= */ true, /* loadLock= */ + false, /* keepDimensionHints= */ true, + new WallpaperDisplayHelper.DisplayData(0)); + } catch (IOException | XmlPullParserException e) { + fail("exception occurred while parsing wallpaper"); + } + + assertThat(actualData.getComponent()).isEqualTo(expectedData.getComponent()); + assertThat(actualData.primaryColors).isEqualTo(expectedData.primaryColors); + assertThat(actualData.mWallpaperDimAmount).isEqualTo(expectedData.mWallpaperDimAmount); + assertThat(actualData.mUidToDimAmount).isNotNull(); + assertThat(actualData.mUidToDimAmount.size()).isEqualTo( + expectedData.mUidToDimAmount.size()); + for (int i = 0; i < actualData.mUidToDimAmount.size(); i++) { + int key = actualData.mUidToDimAmount.keyAt(0); + assertThat(actualData.mUidToDimAmount.get(key)).isEqualTo( + expectedData.mUidToDimAmount.get(key)); + } + } + + @Test + @DisableFlags(Flags.FLAG_REMOVE_NEXT_WALLPAPER_COMPONENT) + public void testSaveLoadSettings_legacyNextComponent() { WallpaperData systemWallpaperData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0); + systemWallpaperData.setComponent(sDefaultWallpaperComponent); + ByteArrayOutputStream ostream = new ByteArrayOutputStream(); try { TypedXmlSerializer serializer = Xml.newBinarySerializer(); - serializer.setOutput(new ByteArrayOutputStream(), StandardCharsets.UTF_8.name()); - serializer.startDocument(StandardCharsets.UTF_8.name(), true); - mService.mWallpaperDataParser.writeWallpaperAttributes( - serializer, "wp", systemWallpaperData); + serializer.setOutput(ostream, StandardCharsets.UTF_8.name()); + mService.mWallpaperDataParser.saveSettingsToSerializer(serializer, systemWallpaperData, + null); + ostream.close(); } catch (IOException e) { fail("exception occurred while writing system wallpaper attributes"); } WallpaperData shouldMatchSystem = new WallpaperData(0, FLAG_SYSTEM); try { + ByteArrayInputStream istream = new ByteArrayInputStream(ostream.toByteArray()); TypedXmlPullParser parser = Xml.newBinaryPullParser(); - mService.mWallpaperDataParser.parseWallpaperAttributes(parser, shouldMatchSystem, true); - } catch (XmlPullParserException e) { + parser.setInput(istream, StandardCharsets.UTF_8.name()); + mService.mWallpaperDataParser.loadSettingsFromSerializer(parser, + shouldMatchSystem, /* userId= */0, /* loadSystem= */ true, /* loadLock= */ + false, /* keepDimensionHints= */ true, + new WallpaperDisplayHelper.DisplayData(0)); + } catch (IOException | XmlPullParserException e) { fail("exception occurred while parsing wallpaper"); } - assertEquals(systemWallpaperData.primaryColors, shouldMatchSystem.primaryColors); + + assertThat(shouldMatchSystem.nextWallpaperComponent).isEqualTo( + systemWallpaperData.getComponent()); + assertThat(shouldMatchSystem.primaryColors).isEqualTo(systemWallpaperData.primaryColors); } @Test diff --git a/services/tests/servicestests/src/com/android/server/audio/MediaFocusControlTest.java b/services/tests/servicestests/src/com/android/server/audio/MediaFocusControlTest.java new file mode 100644 index 000000000000..34878c87747a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/MediaFocusControlTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.audio; + +import android.annotation.NonNull; +import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioFocusInfo; +import android.media.AudioManager; +import android.os.Binder; +import android.os.IBinder; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +@MediumTest +@RunWith(AndroidJUnit4.class) +public class MediaFocusControlTest { + private static final String TAG = "MediaFocusControlTest"; + + private Context mContext; + private MediaFocusControl mMediaFocusControl; + private final IBinder mICallBack = new Binder(); + + + private static class NoopPlayerFocusEnforcer implements PlayerFocusEnforcer { + public boolean duckPlayers(@NonNull FocusRequester winner, @NonNull FocusRequester loser, + boolean forceDuck) { + return true; + } + + public void restoreVShapedPlayers(@NonNull FocusRequester winner) { + } + + public void mutePlayersForCall(int[] usagesToMute) { + } + + public void unmutePlayersForCall() { + } + + public boolean fadeOutPlayers(@NonNull FocusRequester winner, + @NonNull FocusRequester loser) { + return true; + } + + public void forgetUid(int uid) { + } + + public long getFadeOutDurationMillis(@NonNull AudioAttributes aa) { + return 100; + } + + public long getFadeInDelayForOffendersMillis(@NonNull AudioAttributes aa) { + return 100; + } + + public boolean shouldEnforceFade() { + return false; + } + } + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + mMediaFocusControl = new MediaFocusControl(mContext, new NoopPlayerFocusEnforcer()); + } + + private static final AudioAttributes MEDIA_ATTRIBUTES = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA).build(); + private static final AudioAttributes ALARM_ATTRIBUTES = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ALARM).build(); + private static final int MEDIA_UID = 10300; + private static final int ALARM_UID = 10301; + + /** + * Test {@link MediaFocusControl#sendFocusLossAndUpdate(AudioFocusInfo)} + */ + @Test + public void testSendFocusLossAndUpdate() throws Exception { + // simulate a media app requesting focus, followed by an alarm + mMediaFocusControl.requestAudioFocus(MEDIA_ATTRIBUTES, AudioManager.AUDIOFOCUS_GAIN, + mICallBack, null /*focusDispatcher*/, "clientMedia", "packMedia", + AudioManager.AUDIOFOCUS_FLAG_TEST /*flags*/, 35 /*sdk*/, false/*forceDuck*/, + MEDIA_UID, true /*permissionOverridesCheck*/); + final AudioFocusInfo alarm = new AudioFocusInfo(ALARM_ATTRIBUTES, ALARM_UID, + "clientAlarm", "packAlarm", + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0/*lossReceived*/, + AudioManager.AUDIOFOCUS_FLAG_TEST /*flags*/, 35 /*sdk*/); + mMediaFocusControl.requestAudioFocus(alarm.getAttributes(), alarm.getGainRequest(), + mICallBack, null /*focusDispatcher*/, alarm.getClientId(), alarm.getPackageName(), + alarm.getFlags(), alarm.getSdkTarget(), false/*forceDuck*/, + alarm.getClientUid(), true /*permissionOverridesCheck*/); + // verify stack is in expected state + List<AudioFocusInfo> stack = mMediaFocusControl.getFocusStack(); + Assert.assertEquals("focus stack should have 2 entries", 2, stack.size()); + Assert.assertEquals("focus loser should have received LOSS_TRANSIENT_CAN_DUCK", + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK, stack.get(0).getLossReceived()); + + // make alarm app lose focus and check stack + mMediaFocusControl.sendFocusLossAndUpdate(alarm); + stack = mMediaFocusControl.getFocusStack(); + Assert.assertEquals("focus stack should have 1 entry after sendFocusLossAndUpdate", + 1, stack.size()); + Assert.assertEquals("new top of stack should be media app", + MEDIA_UID, stack.get(0).getClientUid()); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 9b87947b6980..12069284e462 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -6992,6 +6992,22 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(zenRule.condition).isNotNull(); } + @Test + @EnableFlags(FLAG_MODES_API) + public void addAutomaticZenRule_withoutPolicy_getsItsOwnInstanceOfDefaultPolicy() { + // Add a rule without policy -> uses default config + AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.parse("cond")) + .setPackage(mPkg) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, azr, ORIGIN_APP, "adding", + CUSTOM_PKG_UID); + + ZenRule zenRule = checkNotNull(mZenModeHelper.mConfig.automaticRules.get(ruleId)); + + assertThat(zenRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy()); + assertThat(zenRule.zenPolicy).isNotSameInstanceAs(mZenModeHelper.getDefaultZenPolicy()); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java index 7ea5010976ee..ff8b6d3c1962 100644 --- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java @@ -141,7 +141,8 @@ public class SingleKeyGestureTests { } @Override - void onKeyUp(long eventTime, int multiPressCount, int displayId) { + void onKeyUp(long eventTime, int multiPressCount, int displayId, int deviceId, + int metaState) { mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount)); } }); @@ -177,7 +178,8 @@ public class SingleKeyGestureTests { } @Override - void onKeyUp(long eventTime, int multiPressCount, int displayId) { + void onKeyUp(long eventTime, int multiPressCount, int displayId, int deviceId, + int metaState) { mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount)); } diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index 62d3949890ce..1be61c36f272 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -293,7 +293,6 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { mUserFolderGetter); target.onSystemReady(); target.onUnlockUser(TEST_USER_ID); - mPersisterQueue.flush(); target.getLaunchParams(mTestTask, null, mResult); @@ -312,7 +311,6 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { mUserFolderGetter); target.onSystemReady(); target.onUnlockUser(TEST_USER_ID); - mPersisterQueue.flush(); mTaskWithDifferentComponent.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY; target.getLaunchParams(mTaskWithDifferentComponent, null, mResult); @@ -341,7 +339,6 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { mUserFolderGetter); target.onSystemReady(); target.onUnlockUser(TEST_USER_ID); - mPersisterQueue.flush(); target.getLaunchParams(mTaskWithDifferentComponent, null, mResult); @@ -411,7 +408,6 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { mUserFolderGetter); target.onSystemReady(); target.onUnlockUser(TEST_USER_ID); - mPersisterQueue.flush(); target.getLaunchParams(mTestTask, null, mResult); @@ -429,7 +425,6 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { mUserFolderGetter); target.onSystemReady(); target.onUnlockUser(TEST_USER_ID); - mPersisterQueue.flush(); target.getLaunchParams(mTestTask, null, mResult); @@ -458,7 +453,6 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { mUserFolderGetter); target.onSystemReady(); target.onUnlockUser(TEST_USER_ID); - mPersisterQueue.flush(); target.getLaunchParams(mTestTask, null, mResult); @@ -476,7 +470,6 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { mUserFolderGetter); target.onSystemReady(); target.onUnlockUser(TEST_USER_ID); - mPersisterQueue.flush(); target.getLaunchParams(mTestTask, null, mResult); @@ -495,52 +488,12 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { mUserFolderGetter); target.onSystemReady(); target.onUnlockUser(TEST_USER_ID); - mPersisterQueue.flush(); target.getLaunchParams(mTestTask, null, mResult); assertTrue("Result should be empty.", mResult.isEmpty()); } - @Test - public void testAbortsLoadingWhenUserCleansUpBeforeLoadingFinishes() { - mTarget.saveTask(mTestTask); - mPersisterQueue.flush(); - - final LaunchParamsPersister target = new LaunchParamsPersister(mPersisterQueue, mSupervisor, - mUserFolderGetter); - target.onSystemReady(); - target.onUnlockUser(TEST_USER_ID); - assertEquals(1, mPersisterQueue.mQueue.size()); - PersisterQueue.QueueItem item = mPersisterQueue.mQueue.get(0); - - target.onCleanupUser(TEST_USER_ID); - mPersisterQueue.flush(); - - // Explicitly run the loading item to mimic the situation where the item already started. - item.process(); - - target.getLaunchParams(mTestTask, null, mResult); - assertTrue("Result should be empty.", mResult.isEmpty()); - } - - @Test - public void testGetLaunchParamsNotBlockedByAbortedLoading() { - mTarget.saveTask(mTestTask); - mPersisterQueue.flush(); - - final LaunchParamsPersister target = new LaunchParamsPersister(mPersisterQueue, mSupervisor, - mUserFolderGetter); - target.onSystemReady(); - target.onUnlockUser(TEST_USER_ID); - target.onCleanupUser(TEST_USER_ID); - - // As long as the call in the next line returns, we know it's not waiting for the loading to - // finish because we run items synchronously in this test. - target.getLaunchParams(mTestTask, null, mResult); - assertTrue("Result should be empty.", mResult.isEmpty()); - } - private static boolean deleteRecursively(File file) { boolean result = true; if (file.isDirectory()) { @@ -555,17 +508,17 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { /** * Test double to {@link PersisterQueue}. This is not thread-safe and caller should always use - * {@link #flush()} to execute items in it. + * {@link #flush()} to execute write items in it. */ static class TestPersisterQueue extends PersisterQueue { - private List<QueueItem> mQueue = new ArrayList<>(); + private List<WriteQueueItem> mWriteQueue = new ArrayList<>(); private List<Listener> mListeners = new ArrayList<>(); @Override void flush() { - while (!mQueue.isEmpty()) { - final QueueItem item = mQueue.remove(0); - final boolean queueEmpty = mQueue.isEmpty(); + while (!mWriteQueue.isEmpty()) { + final WriteQueueItem item = mWriteQueue.remove(0); + final boolean queueEmpty = mWriteQueue.isEmpty(); for (Listener listener : mListeners) { listener.onPreProcessItem(queueEmpty); } @@ -584,18 +537,18 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { } @Override - synchronized void addItem(QueueItem item, boolean flush) { - mQueue.add(item); + void addItem(WriteQueueItem item, boolean flush) { + mWriteQueue.add(item); if (flush) { flush(); } } @Override - synchronized <T extends WriteQueueItem<T>> T findLastItem(Predicate<T> predicate, + synchronized <T extends WriteQueueItem> T findLastItem(Predicate<T> predicate, Class<T> clazz) { - for (int i = mQueue.size() - 1; i >= 0; --i) { - QueueItem writeQueueItem = mQueue.get(i); + for (int i = mWriteQueue.size() - 1; i >= 0; --i) { + WriteQueueItem writeQueueItem = mWriteQueue.get(i); if (clazz.isInstance(writeQueueItem)) { T item = clazz.cast(writeQueueItem); if (predicate.test(item)) { @@ -608,14 +561,14 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { } @Override - synchronized <T extends QueueItem> void removeItems(Predicate<T> predicate, + synchronized <T extends WriteQueueItem> void removeItems(Predicate<T> predicate, Class<T> clazz) { - for (int i = mQueue.size() - 1; i >= 0; --i) { - QueueItem writeQueueItem = mQueue.get(i); + for (int i = mWriteQueue.size() - 1; i >= 0; --i) { + WriteQueueItem writeQueueItem = mWriteQueue.get(i); if (clazz.isInstance(writeQueueItem)) { T item = clazz.cast(writeQueueItem); if (predicate.test(item)) { - mQueue.remove(i); + mWriteQueue.remove(i); } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java index ce0e6f8a932a..3e87f1f96fcd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/PersisterQueueTests.java @@ -90,26 +90,8 @@ public class PersisterQueueTests { mFactory.setExpectedProcessedItemNumber(1); mListener.setExpectedOnPreProcessItemCallbackTimes(1); - mTarget.addItem(mFactory.createItem(), false); - assertTrue("Target didn't process item enough times.", - mFactory.waitForAllExpectedItemsProcessed(TIMEOUT_ALLOWANCE)); - assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount()); - - assertTrue("Target didn't call callback enough times.", - mListener.waitForAllExpectedCallbackDone(TIMEOUT_ALLOWANCE)); - // Once before processing this item, once after that. - assertEquals(2, mListener.mProbablyDoneResults.size()); - // The last one must be called with probably done being true. - assertTrue("The last probablyDone must be true.", mListener.mProbablyDoneResults.get(1)); - } - - @Test - public void testProcessOneWriteItem() throws Exception { - mFactory.setExpectedProcessedItemNumber(1); - mListener.setExpectedOnPreProcessItemCallbackTimes(1); - final long dispatchTime = SystemClock.uptimeMillis(); - mTarget.addItem(mFactory.createWriteItem(), false); + mTarget.addItem(mFactory.createItem(), false); assertTrue("Target didn't process item enough times.", mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE)); assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount()); @@ -127,12 +109,12 @@ public class PersisterQueueTests { } @Test - public void testProcessOneWriteItem_Flush() throws Exception { + public void testProcessOneItem_Flush() throws Exception { mFactory.setExpectedProcessedItemNumber(1); mListener.setExpectedOnPreProcessItemCallbackTimes(1); final long dispatchTime = SystemClock.uptimeMillis(); - mTarget.addItem(mFactory.createWriteItem(), true); + mTarget.addItem(mFactory.createItem(), true); assertTrue("Target didn't process item enough times.", mFactory.waitForAllExpectedItemsProcessed(TIMEOUT_ALLOWANCE)); assertEquals("Target didn't process item.", 1, mFactory.getTotalProcessedItemCount()); @@ -156,8 +138,8 @@ public class PersisterQueueTests { mListener.setExpectedOnPreProcessItemCallbackTimes(2); final long dispatchTime = SystemClock.uptimeMillis(); - mTarget.addItem(mFactory.createWriteItem(), false); - mTarget.addItem(mFactory.createWriteItem(), false); + mTarget.addItem(mFactory.createItem(), false); + mTarget.addItem(mFactory.createItem(), false); assertTrue("Target didn't call callback enough times.", mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + INTER_WRITE_DELAY_MS + TIMEOUT_ALLOWANCE)); @@ -183,7 +165,7 @@ public class PersisterQueueTests { mFactory.setExpectedProcessedItemNumber(1); mListener.setExpectedOnPreProcessItemCallbackTimes(1); long dispatchTime = SystemClock.uptimeMillis(); - mTarget.addItem(mFactory.createWriteItem(), false); + mTarget.addItem(mFactory.createItem(), false); assertTrue("Target didn't process item enough times.", mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE)); long processDuration = SystemClock.uptimeMillis() - dispatchTime; @@ -202,7 +184,7 @@ public class PersisterQueueTests { // Synchronize on the instance to make sure we schedule the item after it starts to wait for // task indefinitely. synchronized (mTarget) { - mTarget.addItem(mFactory.createWriteItem(), false); + mTarget.addItem(mFactory.createItem(), false); } assertTrue("Target didn't process item enough times.", mFactory.waitForAllExpectedItemsProcessed(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE)); @@ -224,9 +206,9 @@ public class PersisterQueueTests { @Test public void testFindLastItemNotReturnDifferentType() { synchronized (mTarget) { - mTarget.addItem(mFactory.createWriteItem(), false); - assertNull(mTarget.findLastItem(TestWriteItem::shouldKeepOnFilter, - FilterableTestWriteItem.class)); + mTarget.addItem(mFactory.createItem(), false); + assertNull(mTarget.findLastItem(TestItem::shouldKeepOnFilter, + FilterableTestItem.class)); } } @@ -234,18 +216,18 @@ public class PersisterQueueTests { public void testFindLastItemNotReturnMismatchItem() { synchronized (mTarget) { mTarget.addItem(mFactory.createFilterableItem(false), false); - assertNull(mTarget.findLastItem(TestWriteItem::shouldKeepOnFilter, - FilterableTestWriteItem.class)); + assertNull(mTarget.findLastItem(TestItem::shouldKeepOnFilter, + FilterableTestItem.class)); } } @Test public void testFindLastItemReturnMatchedItem() { synchronized (mTarget) { - final FilterableTestWriteItem item = mFactory.createFilterableItem(true); + final FilterableTestItem item = mFactory.createFilterableItem(true); mTarget.addItem(item, false); - assertSame(item, mTarget.findLastItem(TestWriteItem::shouldKeepOnFilter, - FilterableTestWriteItem.class)); + assertSame(item, mTarget.findLastItem(TestItem::shouldKeepOnFilter, + FilterableTestItem.class)); } } @@ -253,8 +235,8 @@ public class PersisterQueueTests { public void testRemoveItemsNotRemoveDifferentType() throws Exception { mListener.setExpectedOnPreProcessItemCallbackTimes(1); synchronized (mTarget) { - mTarget.addItem(mFactory.createWriteItem(), false); - mTarget.removeItems(TestWriteItem::shouldKeepOnFilter, FilterableTestWriteItem.class); + mTarget.addItem(mFactory.createItem(), false); + mTarget.removeItems(TestItem::shouldKeepOnFilter, FilterableTestItem.class); } assertTrue("Target didn't call callback enough times.", mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE)); @@ -266,7 +248,7 @@ public class PersisterQueueTests { mListener.setExpectedOnPreProcessItemCallbackTimes(1); synchronized (mTarget) { mTarget.addItem(mFactory.createFilterableItem(false), false); - mTarget.removeItems(TestWriteItem::shouldKeepOnFilter, FilterableTestWriteItem.class); + mTarget.removeItems(TestItem::shouldKeepOnFilter, FilterableTestItem.class); } assertTrue("Target didn't call callback enough times.", mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE)); @@ -276,8 +258,8 @@ public class PersisterQueueTests { @Test public void testUpdateLastOrAddItemUpdatesMatchedItem() throws Exception { mListener.setExpectedOnPreProcessItemCallbackTimes(1); - final FilterableTestWriteItem scheduledItem = mFactory.createFilterableItem(true); - final FilterableTestWriteItem expected = mFactory.createFilterableItem(true); + final FilterableTestItem scheduledItem = mFactory.createFilterableItem(true); + final FilterableTestItem expected = mFactory.createFilterableItem(true); synchronized (mTarget) { mTarget.addItem(scheduledItem, false); mTarget.updateLastOrAddItem(expected, false); @@ -292,8 +274,8 @@ public class PersisterQueueTests { @Test public void testUpdateLastOrAddItemUpdatesAddItemWhenNoMatch() throws Exception { mListener.setExpectedOnPreProcessItemCallbackTimes(2); - final FilterableTestWriteItem scheduledItem = mFactory.createFilterableItem(false); - final FilterableTestWriteItem expected = mFactory.createFilterableItem(true); + final FilterableTestItem scheduledItem = mFactory.createFilterableItem(false); + final FilterableTestItem expected = mFactory.createFilterableItem(true); synchronized (mTarget) { mTarget.addItem(scheduledItem, false); mTarget.updateLastOrAddItem(expected, false); @@ -310,9 +292,9 @@ public class PersisterQueueTests { public void testRemoveItemsRemoveMatchedItem() throws Exception { mListener.setExpectedOnPreProcessItemCallbackTimes(1); synchronized (mTarget) { - mTarget.addItem(mFactory.createWriteItem(), false); + mTarget.addItem(mFactory.createItem(), false); mTarget.addItem(mFactory.createFilterableItem(true), false); - mTarget.removeItems(TestWriteItem::shouldKeepOnFilter, FilterableTestWriteItem.class); + mTarget.removeItems(TestItem::shouldKeepOnFilter, FilterableTestItem.class); } assertTrue("Target didn't call callback enough times.", mListener.waitForAllExpectedCallbackDone(PRE_TASK_DELAY_MS + TIMEOUT_ALLOWANCE)); @@ -322,8 +304,8 @@ public class PersisterQueueTests { @Test public void testFlushWaitSynchronously() { final long dispatchTime = SystemClock.uptimeMillis(); - mTarget.addItem(mFactory.createWriteItem(), false); - mTarget.addItem(mFactory.createWriteItem(), false); + mTarget.addItem(mFactory.createItem(), false); + mTarget.addItem(mFactory.createItem(), false); mTarget.flush(); assertEquals("Flush should wait until all items are processed before return.", 2, mFactory.getTotalProcessedItemCount()); @@ -353,18 +335,15 @@ public class PersisterQueueTests { return new TestItem(mItemCount, mLatch); } - TestWriteItem createWriteItem() { - return new TestWriteItem(mItemCount, mLatch); - } - - FilterableTestWriteItem createFilterableItem(boolean shouldKeepOnFilter) { - return new FilterableTestWriteItem(shouldKeepOnFilter, mItemCount, mLatch); + FilterableTestItem createFilterableItem(boolean shouldKeepOnFilter) { + return new FilterableTestItem(shouldKeepOnFilter, mItemCount, mLatch); } } - private static class TestItem implements PersisterQueue.QueueItem { - private final AtomicInteger mItemCount; - private final CountDownLatch mLatch; + private static class TestItem<T extends TestItem<T>> + implements PersisterQueue.WriteQueueItem<T> { + private AtomicInteger mItemCount; + private CountDownLatch mLatch; TestItem(AtomicInteger itemCount, CountDownLatch latch) { mItemCount = itemCount; @@ -380,37 +359,30 @@ public class PersisterQueueTests { mLatch.countDown(); } } - } - - private static class TestWriteItem<T extends TestWriteItem<T>> - extends TestItem implements PersisterQueue.WriteQueueItem<T> { - TestWriteItem(AtomicInteger itemCount, CountDownLatch latch) { - super(itemCount, latch); - } boolean shouldKeepOnFilter() { return true; } } - private static class FilterableTestWriteItem extends TestWriteItem<FilterableTestWriteItem> { + private static class FilterableTestItem extends TestItem<FilterableTestItem> { private boolean mShouldKeepOnFilter; - private FilterableTestWriteItem mUpdateFromItem; + private FilterableTestItem mUpdateFromItem; - private FilterableTestWriteItem(boolean shouldKeepOnFilter, AtomicInteger mItemCount, + private FilterableTestItem(boolean shouldKeepOnFilter, AtomicInteger mItemCount, CountDownLatch mLatch) { super(mItemCount, mLatch); mShouldKeepOnFilter = shouldKeepOnFilter; } @Override - public boolean matches(FilterableTestWriteItem item) { + public boolean matches(FilterableTestItem item) { return item.mShouldKeepOnFilter; } @Override - public void updateFrom(FilterableTestWriteItem item) { + public void updateFrom(FilterableTestItem item) { mUpdateFromItem = item; } 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 7ff2e50926a5..4b03483d43b9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -29,6 +29,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY; +import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; +import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; @@ -75,6 +77,7 @@ import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.TaskInfo; import android.app.WindowConfiguration; +import android.compat.testing.PlatformCompatChangeRule; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -97,9 +100,13 @@ import androidx.test.filters.MediumTest; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import libcore.junit.util.compat.CoreCompatChangeRule; + import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.xmlpull.v1.XmlPullParser; @@ -122,6 +129,9 @@ import java.io.Reader; @RunWith(WindowTestRunner.class) public class TaskTests extends WindowTestsBase { + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + private static final String TASK_TAG = "task"; private Rect mParentBounds; @@ -404,6 +414,85 @@ public class TaskTests extends WindowTestsBase { } @Test + @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP}) + public void testIsResizeable_nonResizeable_forceResize_overridesEnabled_Resizeable() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setComponent( + ComponentName.createRelative(mContext, SizeCompatTests.class.getName())) + .build(); + task.setResizeMode(RESIZE_MODE_UNRESIZEABLE); + // Override should take effect and task should be resizeable. + assertTrue(task.getTaskInfo().isResizeable); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP}) + public void testIsResizeable_nonResizeable_forceResize_overridesDisabled_nonResizeable() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setComponent( + ComponentName.createRelative(mContext, SizeCompatTests.class.getName())) + .build(); + task.setResizeMode(RESIZE_MODE_UNRESIZEABLE); + + // Disallow resize overrides. + task.mAllowForceResizeOverride = false; + + // Override should not take effect and task should be un-resizeable. + assertFalse(task.getTaskInfo().isResizeable); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP}) + public void testIsResizeable_resizeable_forceNonResize_overridesEnabled_nonResizeable() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setComponent( + ComponentName.createRelative(mContext, SizeCompatTests.class.getName())) + .build(); + task.setResizeMode(RESIZE_MODE_RESIZEABLE); + + // Override should take effect and task should be un-resizeable. + assertFalse(task.getTaskInfo().isResizeable); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP}) + public void testIsResizeable_resizeable_forceNonResize_overridesDisabled_Resizeable() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setComponent( + ComponentName.createRelative(mContext, SizeCompatTests.class.getName())) + .build(); + task.setResizeMode(RESIZE_MODE_RESIZEABLE); + + // Disallow resize overrides. + task.mAllowForceResizeOverride = false; + + // Override should not take effect and task should be resizeable. + assertTrue(task.getTaskInfo().isResizeable); + } + + @Test + @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP}) + public void testIsResizeable_systemWideForceResize_compatForceNonResize__Resizeable() { + final Task task = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setComponent( + ComponentName.createRelative(mContext, SizeCompatTests.class.getName())) + .build(); + task.setResizeMode(RESIZE_MODE_RESIZEABLE); + + // Set system-wide force resizeable override. + task.mAtmService.mForceResizableActivities = true; + + // System wide override should tak priority over app compat override so the task should + // remain resizeable. + assertTrue(task.getTaskInfo().isResizeable); + } + + @Test public void testResolveNonResizableTaskWindowingMode() { // Test with no support non-resizable in multi window regardless the screen size. mAtm.mSupportsNonResizableMultiWindow = -1; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java index 410499916be9..12b744546f5e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static android.tools.traces.Utils.busyWaitForDataSourceRegistration; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions; @@ -28,7 +30,6 @@ import static org.mockito.ArgumentMatchers.eq; import static java.io.File.createTempFile; import static java.nio.file.Files.createTempDirectory; -import android.os.ParcelFileDescriptor; import android.platform.test.annotations.Presubmit; import android.tools.ScenarioBuilder; import android.tools.traces.io.ResultWriter; @@ -36,9 +37,6 @@ import android.tools.traces.monitors.PerfettoTraceMonitor; import android.view.Choreographer; import androidx.test.filters.SmallTest; -import androidx.test.platform.app.InstrumentationRegistry; - -import com.google.protobuf.InvalidProtocolBufferException; import org.junit.After; import org.junit.Before; @@ -46,12 +44,9 @@ import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; -import perfetto.protos.PerfettoConfig.TracingServiceState; import perfetto.protos.PerfettoConfig.WindowManagerConfig.LogFrequency; -import java.io.FileInputStream; import java.io.IOException; -import java.util.Optional; /** * Test class for {@link WindowTracingPerfetto}. @@ -74,7 +69,7 @@ public class WindowTracingPerfettoTest { sChoreographer = Mockito.mock(Choreographer.class); sWindowTracing = new WindowTracingPerfetto(sWmMock, sChoreographer, new WindowManagerGlobalLock(), TEST_DATA_SOURCE_NAME); - waitDataSourceIsAvailable(); + busyWaitForDataSourceRegistration(TEST_DATA_SOURCE_NAME); } @Before @@ -156,67 +151,4 @@ public class WindowTracingPerfettoTest { mTraceMonitor.stop(writer); } - - private static void waitDataSourceIsAvailable() { - final int timeoutMs = 10000; - final int busyWaitIntervalMs = 100; - - int elapsedMs = 0; - - while (!isDataSourceAvailable()) { - try { - Thread.sleep(busyWaitIntervalMs); - elapsedMs += busyWaitIntervalMs; - if (elapsedMs >= timeoutMs) { - throw new RuntimeException("Data source didn't become available." - + " Waited for: " + timeoutMs + " ms"); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - private static boolean isDataSourceAvailable() { - byte[] proto = executeShellCommand("perfetto --query-raw"); - - try { - TracingServiceState state = TracingServiceState.parseFrom(proto); - - Optional<Integer> producerId = Optional.empty(); - - for (TracingServiceState.Producer producer : state.getProducersList()) { - if (producer.getPid() == android.os.Process.myPid()) { - producerId = Optional.of(producer.getId()); - break; - } - } - - if (producerId.isEmpty()) { - return false; - } - - for (TracingServiceState.DataSource ds : state.getDataSourcesList()) { - if (ds.getDsDescriptor().getName().equals(TEST_DATA_SOURCE_NAME) - && ds.getProducerId() == producerId.get()) { - return true; - } - } - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(e); - } - - return false; - } - - private static byte[] executeShellCommand(String command) { - try { - ParcelFileDescriptor fd = InstrumentationRegistry.getInstrumentation().getUiAutomation() - .executeShellCommand(command); - FileInputStream is = new ParcelFileDescriptor.AutoCloseInputStream(fd); - return is.readAllBytes(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } } diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java index e20e4d200251..e42b41f90f4a 100644 --- a/telephony/java/android/telephony/BarringInfo.java +++ b/telephony/java/android/telephony/BarringInfo.java @@ -159,7 +159,7 @@ public final class BarringInfo implements Parcelable { /** * @return the conditional barring factor as a percentage 0-100, which is the probability of - * a random device being barred for the service type. + * a random device being allowed for a conditionally barred service. */ public int getConditionalBarringFactor() { return mConditionalBarringFactor; diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/OWNERS new file mode 100644 index 000000000000..981b3168cdbb --- /dev/null +++ b/tests/FlickerTests/ActivityEmbedding/OWNERS @@ -0,0 +1 @@ +# Bug component: 1168918 diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java index b3a998ebca0d..945907009a9c 100644 --- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java +++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java @@ -321,26 +321,26 @@ public class TouchpadDebugViewTest { new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0, new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID); - assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE); + assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99)); mTouchpadDebugView.updateHardwareState( new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0, new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID); - assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.RED); + assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(84, 85, 169)); mTouchpadDebugView.updateHardwareState( new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0, new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID); - assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE); + assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99)); // Color should not change because hardware state of a different touchpad mTouchpadDebugView.updateHardwareState( new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0, new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID + 1); - assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE); + assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99)); } @Test diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index cfb2645dee0d..6f3deab1d4fa 100644 --- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -16,7 +16,7 @@ package com.android.internal.protolog; -import static android.tools.traces.Utils.executeShellCommand; +import static android.tools.traces.Utils.busyWaitForDataSourceRegistration; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; @@ -50,7 +50,6 @@ import com.android.internal.protolog.common.LogDataType; import com.android.internal.protolog.common.LogLevel; import com.google.common.truth.Truth; -import com.google.protobuf.InvalidProtocolBufferException; import org.junit.After; import org.junit.Before; @@ -60,14 +59,12 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mockito; -import perfetto.protos.PerfettoConfig.TracingServiceState; import perfetto.protos.Protolog; import perfetto.protos.ProtologCommon; import java.io.File; import java.io.IOException; import java.util.List; -import java.util.Optional; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; @@ -184,7 +181,7 @@ public class PerfettoProtoLogImplTest { TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService); } - waitDataSourceIsAvailable(); + busyWaitForDataSourceRegistration(TEST_PROTOLOG_DATASOURCE_NAME); } @Before @@ -870,54 +867,6 @@ public class PerfettoProtoLogImplTest { .isEqualTo("This message should also be logged 567"); } - private static void waitDataSourceIsAvailable() { - final int timeoutMs = 10000; - final int busyWaitIntervalMs = 100; - - int elapsedMs = 0; - - while (!isDataSourceAvailable()) { - SystemClock.sleep(busyWaitIntervalMs); - elapsedMs += busyWaitIntervalMs; - if (elapsedMs >= timeoutMs) { - throw new RuntimeException("Data source didn't become available." - + " Waited for: " + timeoutMs + " ms"); - } - } - } - - private static boolean isDataSourceAvailable() { - byte[] proto = executeShellCommand("perfetto --query-raw"); - - try { - TracingServiceState state = TracingServiceState.parseFrom(proto); - - Optional<Integer> producerId = Optional.empty(); - - for (TracingServiceState.Producer producer : state.getProducersList()) { - if (producer.getPid() == android.os.Process.myPid()) { - producerId = Optional.of(producer.getId()); - break; - } - } - - if (producerId.isEmpty()) { - return false; - } - - for (TracingServiceState.DataSource ds : state.getDataSourcesList()) { - if (ds.getDsDescriptor().getName().equals(TEST_PROTOLOG_DATASOURCE_NAME) - && ds.getProducerId() == producerId.get()) { - return true; - } - } - } catch (InvalidProtocolBufferException e) { - throw new RuntimeException(e); - } - - return false; - } - private enum TestProtoLogGroup implements IProtoLogGroup { TEST_GROUP(true, true, false, "TEST_TAG"); diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt index 675a59e6ae3e..caa018d8c013 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt @@ -256,7 +256,7 @@ val exemptAidlInterfaces = setOf( "android.devicelock.IGetDeviceIdCallback", "android.devicelock.IGetKioskAppsCallback", "android.devicelock.IIsDeviceLockedCallback", - "android.devicelock.IVoidResultCallback", + "android.devicelock.ILockUnlockDeviceCallback", "android.federatedcompute.aidl.IExampleStoreCallback", "android.federatedcompute.aidl.IExampleStoreIterator", "android.federatedcompute.aidl.IExampleStoreIteratorCallback", @@ -364,8 +364,6 @@ val exemptAidlInterfaces = setOf( "android.health.connect.aidl.IGetPriorityResponseCallback", "android.health.connect.aidl.IHealthConnectService", "android.health.connect.aidl.IInsertRecordsResponseCallback", - "android.health.connect.aidl.IMedicalDataSourceResponseCallback", - "android.health.connect.aidl.IMedicalResourcesResponseCallback", "android.health.connect.aidl.IMigrationCallback", "android.health.connect.aidl.IReadMedicalResourcesResponseCallback", "android.health.connect.aidl.IReadRecordsResponseCallback", @@ -462,6 +460,7 @@ val exemptAidlInterfaces = setOf( "android.net.ipmemorystore.IOnBlobRetrievedListener", "android.net.ipmemorystore.IOnL2KeyResponseListener", "android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener", + "android.net.ipmemorystore.IOnNetworkEventCountRetrievedListener", "android.net.ipmemorystore.IOnSameL3NetworkResponseListener", "android.net.ipmemorystore.IOnStatusAndCountListener", "android.net.ipmemorystore.IOnStatusListener", diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/PermissionAnnotationDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/PermissionAnnotationDetector.kt index d44c271e734c..8d6e32022ae7 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/PermissionAnnotationDetector.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/PermissionAnnotationDetector.kt @@ -57,7 +57,10 @@ class PermissionAnnotationDetector : AidlImplementationDetector() { ISSUE_MISSING_PERMISSION_ANNOTATION, node, context.getLocation(node), - "The method ${node.name} is not permission-annotated." + """ + ${node.name} should be annotated with either @EnforcePermission, \ + @RequiresNoPermission or @PermissionManuallyEnforced. + """.trimMargin() ) } diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt index 824be9309dbc..f985d026de16 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt @@ -73,7 +73,7 @@ class PermissionAnnotationDetectorTest : LintDetectorTest() { .run() .expect( """ - src/frameworks/base/services/java/com/android/server/Bar.java:3: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation] + src/frameworks/base/services/java/com/android/server/Bar.java:3: Error: testMethod should be annotated with either @EnforcePermission, @RequiresNoPermission or @PermissionManuallyEnforced. [MissingPermissionAnnotation] public void testMethod(int parameter1, int parameter2) { } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 errors, 0 warnings |