Merge "Remove UserManager.invalidateStaticUserProperties() and UserManager.invalidateUserPropertiesCache() from UserManagerService.initPropertyInvalidatedCaches()" into main
diff --git a/ACTIVITY_MANAGER_OWNERS b/ACTIVITY_MANAGER_OWNERS
new file mode 100644
index 0000000..47782d1
--- /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/BATTERY_STATS_OWNERS b/BATTERY_STATS_OWNERS
index 7728975..575bded 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 0000000..7727f9f
--- /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 0000000..5c3bff7
--- /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 0000000..62db91d
--- /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 0000000..05bf25c
--- /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 22b6489..ffa5802 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 0b1e559..0000000
--- 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 542543d..d0e2003 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -19396,7 +19396,7 @@
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 @@
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 @@
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 @@
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 b2a49e1..cc0354c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4950,94 +4950,94 @@
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);
+ 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);
}
- @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);
+ 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);
}
- @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 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 36daaab..83b5aa0 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -164,7 +164,13 @@
*/
@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 @@
/**
* 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 9b87df9..3cf508a 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -259,10 +259,8 @@
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 @@
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 @@
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 @@
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);
+ // 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);
- }
+ 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*/);
- }
+ 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 2d9433e..20f89a5 100644
--- a/core/java/android/hardware/camera2/CameraExtensionSession.java
+++ b/core/java/android/hardware/camera2/CameraExtensionSession.java
@@ -156,7 +156,6 @@
* @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 75d617c..a18a634 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5293,7 +5293,6 @@
@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 @@
* @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 @@
* @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 @@
*/
@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 8fa09a8..df66f59 100644
--- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -53,7 +53,6 @@
* @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 @@
*
* @param cameraManager the system camera manager
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
public AdvancedExtender(@NonNull CameraManager cameraManager) {
mCameraManager = cameraManager;
try {
@@ -101,7 +99,6 @@
* @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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* @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 @@
* @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 @@
* {@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 @@
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 01698d5..9cc4f16 100644
--- a/core/java/android/hardware/camera2/extension/CameraExtensionService.java
+++ b/core/java/android/hardware/camera2/extension/CameraExtensionService.java
@@ -42,7 +42,6 @@
* @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 @@
}
};
- @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 @@
* 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 @@
*
* @param token Binder token
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
public abstract void onUnregisterClient(@NonNull IBinder token);
/**
@@ -204,7 +199,6 @@
* 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 32139b8..e8b20e3 100644
--- a/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
+++ b/core/java/android/hardware/camera2/extension/CameraOutputSurface.java
@@ -42,11 +42,9 @@
* @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 @@
* @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 @@
/**
* Return the current output {@link Surface}
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
@NonNull
public Surface getSurface() {
return mOutputSurface.surface;
@@ -84,7 +80,6 @@
/**
* 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 @@
/**
* 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 495abc8..e578f6e 100644
--- a/core/java/android/hardware/camera2/extension/CharacteristicsMap.java
+++ b/core/java/android/hardware/camera2/extension/CharacteristicsMap.java
@@ -36,7 +36,6 @@
* @hide
*/
@SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
public class CharacteristicsMap {
private final HashMap<String, CameraCharacteristics> mCharMap;
@@ -46,7 +45,6 @@
* @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 @@
*
* @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 @@
* @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 32de1ce..43dfaa5 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionConfiguration.java
@@ -43,7 +43,6 @@
* @hide
*/
@SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
public class ExtensionConfiguration {
private final int mSessionType;
private final int mSessionTemplateId;
@@ -65,7 +64,6 @@
* @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 @@
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 8a47430..ff0d8d7b 100644
--- a/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
+++ b/core/java/android/hardware/camera2/extension/ExtensionOutputConfiguration.java
@@ -35,7 +35,6 @@
* @hide
*/
@SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
public class ExtensionOutputConfiguration {
private final List<CameraOutputSurface> mSurfaces;
private final String mPhysicalCameraId;
@@ -57,7 +56,6 @@
* @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 0ad27c2..936d57b 100644
--- a/core/java/android/hardware/camera2/extension/RequestProcessor.java
+++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java
@@ -45,19 +45,16 @@
* @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 @@
* @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 @@
* 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 @@
* 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 @@
* @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 @@
* @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 @@
* or {@link CaptureFailure#getFrameNumber}) in
* the capture sequence.
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
void onCaptureSequenceCompleted(int sequenceId, long frameNumber);
/**
@@ -221,17 +212,14 @@
* @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 @@
* 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 @@
/**
* Gets all the parameters.
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
@NonNull
public List<Pair<CaptureRequest.Key, Object>> getParameters() {
return mParameters;
@@ -265,7 +251,6 @@
*
* @see CameraDevice.RequestTemplate
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
Integer getTemplateId() {
return mTemplateId;
}
@@ -322,7 +307,6 @@
* @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 @@
* @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 @@
* @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 @@
/**
* Abort all ongoing capture requests.
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
public void abortCaptures() {
try {
mRequestProcessor.abortCaptures();
@@ -425,7 +406,6 @@
/**
* 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 eead4ef..cf9f30d 100644
--- a/core/java/android/hardware/camera2/extension/SessionProcessor.java
+++ b/core/java/android/hardware/camera2/extension/SessionProcessor.java
@@ -76,7 +76,6 @@
* @hide
*/
@SystemApi
-@FlaggedApi(Flags.FLAG_CONCERT_MODE)
public abstract class SessionProcessor {
private static final String TAG = "SessionProcessor";
private CameraUsageTracker mCameraUsageTracker;
@@ -84,7 +83,6 @@
/**
* Initialize a session process instance
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
public SessionProcessor() {}
void setCameraUsageTracker(CameraUsageTracker tracker) {
@@ -97,7 +95,6 @@
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
public interface CaptureCallback {
/**
* This method is called when the camera device has started
@@ -116,7 +113,6 @@
* first frame in a multi-frame capture,
* in nanoseconds.
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
void onCaptureStarted(int captureSequenceId, long timestamp);
/**
@@ -126,7 +122,6 @@
*
* @param captureSequenceId id of the current capture sequence
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
void onCaptureProcessStarted(int captureSequenceId);
/**
@@ -139,7 +134,6 @@
* @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 @@
*
* @param captureSequenceId id of the current capture sequence
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
void onCaptureSequenceCompleted(int captureSequenceId);
/**
@@ -162,7 +155,6 @@
*
* @param captureSequenceId id of the current capture sequence
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
void onCaptureSequenceAborted(int captureSequenceId);
/**
@@ -192,7 +184,6 @@
* 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 @@
* 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 @@
* @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 @@
*
*
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
public abstract void onCaptureSessionStart(@NonNull RequestProcessor requestProcessor,
@NonNull String statsKey);
@@ -279,7 +267,6 @@
* will no longer accept any requests after onCaptureSessionEnd()
* returns.
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
public abstract void onCaptureSessionEnd();
/**
@@ -293,7 +280,6 @@
* @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 @@
* forward calling {@link RequestProcessor#setRepeating} will simply
* do nothing.
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
public abstract void stopRepeating();
/**
@@ -326,7 +311,6 @@
* @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 @@
* 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 @@
* @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 323712d..fc03b51 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -873,17 +873,15 @@
@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 0cd1c8c..ef7f3f8 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1797,57 +1797,49 @@
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");
- }
-
- 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;
- } else {
return null;
}
+
+ 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");
+ }
+
+ 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 9a4ec5c..9157ef10 100644
--- a/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
+++ b/core/java/android/hardware/camera2/params/LensIntrinsicsSample.java
@@ -31,7 +31,6 @@
* 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 @@
*
* @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 @@
*
* @return a long value (guaranteed to be finite)
*/
- @FlaggedApi(Flags.FLAG_CONCERT_MODE)
public long getTimestampNanos() {
return mTimestampNs;
}
@@ -72,7 +69,6 @@
* @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 0cabc4c..bdbec55 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -22,8 +22,6 @@
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 @@
}
/**
+ * 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 00ba3bf..18f9b2b 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -139,7 +139,7 @@
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 1ca4574..461f1e0 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3706,9 +3706,13 @@
* @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/window/flags/DesktopModeFlags.java b/core/java/android/window/flags/DesktopModeFlags.java
index 395a853..944a106 100644
--- a/core/java/android/window/flags/DesktopModeFlags.java
+++ b/core/java/android/window/flags/DesktopModeFlags.java
@@ -43,7 +43,7 @@
// 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(
@@ -58,12 +58,13 @@
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, false),
+ 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);
+ Flags::enableDesktopWindowingTaskbarRunningApps, true),
+ ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false);
private static final String TAG = "DesktopModeFlagsUtil";
// Function called to obtain aconfig flag value.
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index bb654f0..d836563 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -62,6 +62,9 @@
// 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 421b7d2..fe3d4f6 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 0396659..06b36b8 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 58834e6..a311d0d 100644
--- a/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
+++ b/core/tests/coretests/src/android/window/flags/DesktopModeFlagsTest.java
@@ -23,7 +23,7 @@
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;
@@ -198,145 +198,145 @@
@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 isTrue_dwFlagOn_overrideUnset_featureFlagOn_returnsTrue() {
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).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)
+ @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.isTrue()).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 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.isTrue()).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)
+ @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.isTrue()).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 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.isTrue()).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)
+ @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.isTrue()).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 isTrue_dwFlagOff_overrideUnset_featureFlagOn_returnsTrue() {
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).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 isTrue_dwFlagOff_overrideUnset_featureFlagOff_returnsFalse() {
setOverride(OVERRIDE_UNSET_SETTING);
// For unset overrides, follow flag
- assertThat(DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()).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 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.isTrue()).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 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.isTrue()).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 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.isTrue()).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 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.isTrue()).isFalse();
+ assertThat(DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TRANSITIONS.isTrue()).isFalse();
}
@Test
diff --git a/graphics/java/android/graphics/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java
index dc785c5..7a012bc 100644
--- a/graphics/java/android/graphics/GraphicsStatsService.java
+++ b/graphics/java/android/graphics/GraphicsStatsService.java
@@ -311,7 +311,7 @@
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 @@
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 @@
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 @@
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/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index dfc5ab3..2bc01b2 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 @@
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 @@
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 @@
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 @@
}
}
}
+ state.surfaceLayers = layers
state.startTransitionFinishCb = finishCallback
state.startTransitionFinishTransaction = finishTransaction
startTransaction.apply()
@@ -522,6 +523,10 @@
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 @@
}
}
- 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 @@
/**
* 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 @@
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 @@
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 @@
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 @@
*/
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 @@
*/
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/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index b63f82b..1537a1e 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
@@ -1053,7 +1053,8 @@
loadAppInfoIfNeeded();
updateGenericLink();
final boolean supportsMultiInstance = mMultiInstanceHelper
- .supportsMultiInstanceSplit(mTaskInfo.baseActivity);
+ .supportsMultiInstanceSplit(mTaskInfo.baseActivity)
+ && Flags.enableDesktopWindowingMultiInstanceFeatures();
final boolean shouldShowManageWindowsButton = supportsMultiInstance
&& mMinimumInstancesFound;
mHandleMenu = mHandleMenuFactory.create(
@@ -1370,7 +1371,7 @@
*/
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/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 c7cbc3e..22adf6c 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 @@
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 24f4d92..e6bd05b 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 @@
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 @@
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 @@
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 497d0e5..d9387d2 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 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 @@
}
@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 @@
// 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 @@
// 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/appfunctions/Android.bp b/libs/appfunctions/Android.bp
index 09e2f42..c6cee07 100644
--- a/libs/appfunctions/Android.bp
+++ b/libs/appfunctions/Android.bp
@@ -29,3 +29,11 @@
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 0000000..bef8b6e
--- /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 589abb4..2c23864 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -404,13 +404,19 @@
}
}
+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 @@
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 c904542..afbbce7 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -234,6 +234,7 @@
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 54369b9..80a8ae1 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -42,8 +42,9 @@
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 @@
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 @@
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 @@
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 @@
} // 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 745393c..a6e786c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -58,6 +58,9 @@
// 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 ece5905..702f2a5 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
};
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 @@
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 @@
// 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 4063f74..68c7355 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -44,13 +44,14 @@
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 c2d23e6..eb164f9 100644
--- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
+++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
@@ -62,6 +62,7 @@
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 @@
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, 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 @@
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 @@
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 9af6b28..8394daf 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -709,6 +709,10 @@
@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 293a8f8..2c8e352 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -927,6 +927,29 @@
}
/**
+ * @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/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index 244292d..bfc1324 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -393,6 +393,16 @@
/**
* 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 baeff7e..1661dfb 100644
--- a/packages/SettingsLib/SettingsTheme/Android.bp
+++ b/packages/SettingsLib/SettingsTheme/Android.bp
@@ -15,7 +15,10 @@
"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 0000000..309dbdf
--- /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_close.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_close.xml
new file mode 100644
index 0000000..e6df8a4
--- /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 0000000..342729d
--- /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 f4766ee..543b237 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 40eafc2..6d2cd1a 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 f4766ee..bcdbf1d 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 40eafc2..d4b658c 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 0000000..2475dfd
--- /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 0000000..f5017a5
--- /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 0000000..4cbdfd5
--- /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 0000000..e3e689b
--- /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/values-v35/dimens_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml
new file mode 100644
index 0000000..2320aab
--- /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 0000000..04ae80e
--- /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 0000000..3c69027
--- /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 0000000..fea8739
--- /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 0000000..41fe225
--- /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/res/drawable/ic_media_microphone.xml b/packages/SettingsLib/res/drawable/ic_media_microphone.xml
new file mode 100644
index 0000000..209dea5
--- /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/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index 9dd2dbb..dae69e6 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 @@
@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 bc1ea6c..088d554 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 @@
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 @@
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 907c39d..f5d01d7 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 @@
}
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 fc4cf1d..859fc4e0 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 @@
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)
+ // 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)
+ 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 f8d0588..8e6cb3f 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.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.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.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 @@
}
@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 @@
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 @@
BrightnessSliderContainer(
viewModel = viewModel.brightnessSliderViewModel,
modifier =
- Modifier.fillMaxWidth()
- .height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
+ Modifier.fillMaxWidth().height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
)
- TileGrid(
- viewModel = viewModel.tileGridViewModel,
- modifier =
- Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight),
- viewModel.editModeViewModel::startEditing,
- )
+ 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 6c4edf4..4162891 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 @@
/** 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 492543f..af3ddfc 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 @@
.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 @@
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 @@
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 @@
subtypes =
List(auxiliarySubtypes + nonAuxiliarySubtypes) {
InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes)
- }
+ },
)
}
@@ -383,9 +415,6 @@
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 b3ffc71..d6734e8 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 @@
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.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.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 @@
}
@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 @@
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 41cc953..ba68917 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 @@
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 7203b61..6f20e70 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 @@
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 @@
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 @@
testableContext.orCreateTestableResources.addOverride(
R.bool.config_use_large_screen_shade_header,
- true
+ true,
)
fakeConfigurationRepository.onConfigurationChange()
@@ -126,7 +133,7 @@
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 32bcca1..1f4dea9 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 2f2b10f..66c54a3 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 0140d52..19cc3bc5 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 bb450c0..18a7739 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 @@
/** 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 873d1b3..4185aed 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 @@
_animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
clearInput()
+ if (authenticationResult == AuthenticationResult.SUCCEEDED) {
+ onSuccessfulAuthentication()
+ }
}
awaitCancellation()
}
@@ -116,6 +119,9 @@
/** 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 2493cf1..1427d78 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 @@
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 @@
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 f77dd58..f0f7ca5 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 @@
}
_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 7453368..f7cd2ab 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 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 @@
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 @@
* 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/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index 77c54ec..3992c3f 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.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
class HomeControlsDreamService
@Inject
@@ -53,7 +54,7 @@
) : 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 7e2c9f8..4caf95b 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 @@
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 0000000..62ab18b
--- /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 b271356..f32c94b 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 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.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 @@
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 @@
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 @@
@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 TutorialAnimation(
actionState: TutorialActionState,
config: TutorialScreenConfig,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
) {
Box(modifier = modifier.fillMaxWidth()) {
AnimatedContent(
@@ -152,18 +157,18 @@
// 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 @@
@Composable
private fun FrozenSuccessAnimation(
@RawRes successAnimationId: Int,
- animationProperties: LottieDynamicProperties
+ animationProperties: LottieDynamicProperties,
) {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(successAnimationId))
LottieAnimation(
@@ -188,7 +193,7 @@
@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 @@
@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 @@
@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 eb9b07a..ea80911 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 @@
powerInteractor: PowerInteractor,
alternateBouncerInteractor: AlternateBouncerInteractor,
shadeInteractor: Lazy<ShadeInteractor>,
+ keyguardInteractor: Lazy<KeyguardInteractor>,
) {
val dismissAction: Flow<DismissAction> = repository.dismissAction
@@ -111,9 +112,9 @@
} 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 0b8f741..cef9a4e 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 @@
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 c2b5d98..5559698 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.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.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 @@
): ViewLifecycleOwner {
return ViewLifecycleOwner(view).apply {
onCreate()
- lifecycleScope.launch(nameForTrace, coroutineContext) { block(view) }
+ lifecycleScope.launch(coroutineContext) { traceCoroutine(nameForTrace) { block(view) } }
}
}
@@ -367,7 +368,8 @@
* 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 8bec46a..70ca824 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 @@
/** 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 @@
// 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 @@
// 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 6440205..544dbdd 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 @@
@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/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 44460ed..eff5fc0 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 @@
* [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 @@
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 @@
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 @@
"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 af167d4..c174038 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 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.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.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.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.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 @@
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,
- )
- },
+ Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+ .thenIf(notificationScrimClippingParams.isEnabled) {
+ Modifier.notificationScrimClip(
+ notificationScrimClippingParams.leftInset,
+ notificationScrimClippingParams.top,
+ notificationScrimClippingParams.rightInset,
+ notificationScrimClippingParams.bottom,
+ notificationScrimClippingParams.radius,
+ )
+ }
+ .graphicsLayer { elevation = 4.dp.toPx() },
) {
- AnimatedContent(targetState = qsState) {
- when (it) {
- QSFragmentComposeViewModel.QSExpansionState.QQS -> {
- QuickQuickSettingsElement()
- }
- QSFragmentComposeViewModel.QSExpansionState.QS -> {
- QuickSettingsElement()
- }
- else -> {}
- }
+ 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 @@
}
@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 @@
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 @@
}
@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 @@
) {
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 @@
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 @@
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 0000000..1514986
--- /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 0000000..f0f46d3
--- /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 7ab11d2..7300ee1 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 @@
.stateIn(
lifecycleScope,
SharingStarted.WhileSubscribed(),
- disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled()
+ disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled(),
)
private val _showCollapsedOnKeyguard = MutableStateFlow(false)
@@ -213,19 +213,11 @@
}
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 @@
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 fd276c2..0c02b40 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 @@
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 @@
/** 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 @@
*/
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 08a56bf..083f529 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.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 @@
@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 @@
) {
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 @@
) {
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 f4acbec..8998a7f 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 @@
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 8c57d41..1a5297b 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.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 4946c01..8a96065 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.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 @@
) : PaginatableGridLayout {
@Composable
- override fun TileGrid(
+ override fun SceneScope.TileGrid(
tiles: List<TileViewModel>,
modifier: Modifier,
editModeStart: () -> Unit,
@@ -57,15 +61,18 @@
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 0000000..625459d
--- /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 d0437a7..b8f4ab4 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 @@
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 246fe38..ae56c2a 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 @@
}
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 cca947f..ac75932 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 @@
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 b25c61c..468e180 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 @@
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 b7e2cf2..286cac1 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 @@
// 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 f69b0cb..7724abd 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 @@
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 fe58bc9..0000000
--- 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 0000000..29208f8
--- /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 50215af..c1ea3ad 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 @@
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 @@
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 @@
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 @@
fun prepareScrollingTransition(
response: ScrollCaptureResponse,
- screenBitmap: Bitmap, // unused
newScreenshot: Bitmap,
screenshotTakenInPortrait: Boolean,
onTransitionPrepared: Runnable,
@@ -224,7 +223,7 @@
0,
0,
context.resources.displayMetrics.widthPixels,
- context.resources.displayMetrics.heightPixels
+ context.resources.displayMetrics.heightPixels,
)
)
return r
@@ -239,7 +238,7 @@
animationController.runLongScreenshotTransition(
transitionDestination,
longScreenshot,
- onTransitionEnd
+ onTransitionEnd,
)
transitionAnimation.doOnEnd { callbacks?.onDismiss() }
transitionAnimation.start()
@@ -295,7 +294,7 @@
.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 f76c5fd..0c05dbd 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 @@
@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 @@
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 @@
}
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 bf88807..5473af3 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.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 @@
}
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 @@
}
}
- 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 2bb4765..ce25cf5 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 @@
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 @@
* 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 @@
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 @@
},
{
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 13b74b4..9004e5d 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 @@
// [QuickSettingsInteractor]-type class. However, that's out of
// scope for the CentralSurfaces removal project.
remoteInputQuickSettingsDisabler.adjustDisableFlags(state2),
+ animate,
)
)
}
@@ -82,5 +83,5 @@
.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 6584556..a8b4728 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.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 @@
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 @@
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 @@
mNotificationIconAreaInner = notificationIcons;
mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
- updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false);
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false);
+ }
Trace.endSection();
}
@@ -509,11 +516,17 @@
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 @@
boolean hasPrimaryOngoingActivity,
boolean hasSecondaryOngoingActivity,
boolean shouldAnimate) {
+ if (StatusBarSimpleFragment.isEnabled()) {
+ return;
+ }
mHasPrimaryOngoingActivity = hasPrimaryOngoingActivity;
mHasSecondaryOngoingActivity = hasSecondaryOngoingActivity;
updateStatusBarVisibilities(shouldAnimate);
@@ -530,6 +546,9 @@
@Override
public void onIsHomeStatusBarAllowedBySceneChanged(
boolean isHomeStatusBarAllowedByScene) {
+ if (StatusBarSimpleFragment.isEnabled()) {
+ return;
+ }
mHomeStatusBarAllowedByScene = isHomeStatusBarAllowedByScene;
updateStatusBarVisibilities(/* animate= */ true);
}
@@ -537,17 +556,22 @@
@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 @@
private StatusBarVisibilityModel calculateInternalModel(
StatusBarVisibilityModel externalModel) {
+ StatusBarSimpleFragment.assertInLegacyMode();
+
// TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead.
boolean headsUpVisible =
mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
@@ -639,6 +665,8 @@
* mLastModifiedVisibility.
*/
private void updateNotificationIconAreaAndOngoingActivityChip(boolean animate) {
+ StatusBarSimpleFragment.assertInLegacyMode();
+
StatusBarVisibilityModel visibilityModel = mLastModifiedVisibility;
boolean disableNotifications = !visibilityModel.getShowNotificationIcons();
boolean hasOngoingActivity = visibilityModel.getShowPrimaryOngoingActivityChip();
@@ -674,6 +702,8 @@
}
private boolean shouldHideStatusBar() {
+ StatusBarSimpleFragment.assertInLegacyMode();
+
if (!mShadeExpansionStateManager.isClosed()
&& mPanelExpansionInteractor.shouldHideStatusBarIconsWhenExpanded()) {
return true;
@@ -728,6 +758,7 @@
}
private void hideEndSideContent(boolean animate) {
+ StatusBarSimpleFragment.assertInLegacyMode();
if (!animate || !mAnimationsEnabled) {
mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER);
} else {
@@ -737,6 +768,7 @@
}
private void showEndSideContent(boolean animate) {
+ StatusBarSimpleFragment.assertInLegacyMode();
if (!animate || !mAnimationsEnabled) {
mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER);
return;
@@ -753,15 +785,18 @@
}
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 @@
* 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 @@
* 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 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 @@
* 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 @@
* Hides a view.
*/
private void animateHide(final View v, boolean animate) {
+ StatusBarSimpleFragment.assertInLegacyMode();
animateHiddenState(v, View.INVISIBLE, animate);
}
@@ -847,6 +892,7 @@
* 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 @@
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 @@
@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 c8836e4..eaf15a8 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 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 @@
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 @@
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 deae576..bad6f80 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.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 @@
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 @@
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 0000000..9164da7
--- /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 0000000..69e9746
--- /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 49eabba..4cb66c1 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.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.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 @@
}
}
}
+
+ 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 @@
)
.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 9cce2b8..692e0e4 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 @@
/**
* 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 @@
* [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 @@
}
.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 1127f6f..caf09a3 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 @@
)
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 8f424b2..fa9c6b2 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 @@
/**
* 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 @@
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 0000000..678576d
--- /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 0000000..a99046e
--- /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 8ecf250..2af84c7 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 @@
@Application
fun applicationScope(
@Main dispatcherContext: CoroutineContext,
- ): CoroutineScope = CoroutineScope(dispatcherContext)
+ ): CoroutineScope = CoroutineScope(dispatcherContext + createCoroutineTracingContext("ApplicationScope"))
@Provides
@Singleton
@@ -51,15 +51,7 @@
@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 a03221e..3c06828 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 @@
@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 @@
@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 82f41a7..4d9aaa6 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 @@
*/
@AnyThread
fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-A")).launch {
registerContentObserverSync(getUriFor(name), settingsObserver)
}
@@ -110,7 +111,7 @@
settingsObserver: ContentObserver,
@WorkerThread registered: Runnable
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-B")).launch {
registerContentObserverSync(getUriFor(name), settingsObserver)
registered.run()
}
@@ -143,7 +144,7 @@
*/
@AnyThread
fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-C")).launch {
registerContentObserverSync(uri, settingsObserver)
}
@@ -160,7 +161,7 @@
settingsObserver: ContentObserver,
@WorkerThread registered: Runnable
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-D")).launch {
registerContentObserverSync(uri, settingsObserver)
registered.run()
}
@@ -205,7 +206,7 @@
notifyForDescendants: Boolean,
settingsObserver: ContentObserver
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-E")).launch {
registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
}
@@ -223,7 +224,7 @@
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 @@
notifyForDescendants: Boolean,
settingsObserver: ContentObserver
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-G")).launch {
registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
}
@@ -292,7 +293,7 @@
settingsObserver: ContentObserver,
@WorkerThread registered: Runnable
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-H")).launch {
registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
registered.run()
}
@@ -329,7 +330,7 @@
*/
@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 8e3b813..c820c07 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 @@
}
override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-A")).launch {
registerContentObserverForUserSync(uri, settingsObserver, userId)
}
@@ -112,7 +113,7 @@
notifyForDescendants: Boolean,
settingsObserver: ContentObserver
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-B")).launch {
registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId)
}
@@ -157,7 +158,7 @@
settingsObserver: ContentObserver,
userHandle: Int
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-C")).launch {
registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle)
}
@@ -198,7 +199,7 @@
settingsObserver: ContentObserver,
userHandle: Int
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-D")).launch {
registerContentObserverForUserSync(uri, settingsObserver, userHandle)
}
@@ -215,7 +216,7 @@
userHandle: Int,
@WorkerThread registered: Runnable
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-E")).launch {
registerContentObserverForUserSync(uri, settingsObserver, userHandle)
registered.run()
}
@@ -274,7 +275,7 @@
settingsObserver: ContentObserver,
userHandle: Int
) {
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-F")).launch {
registerContentObserverForUserSync(
getUriFor(name),
notifyForDescendants,
@@ -338,7 +339,7 @@
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 b86d571..ab846f1 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.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.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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
@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 @@
}
@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 @@
}
@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 @@
}
@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 d2dfc92..907c684 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 @@
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 @@
@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 @@
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 @@
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 @@
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 135fab8..63a560f 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 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 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void testDisableNone() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -167,6 +169,7 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -184,6 +187,7 @@
}
@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 @@
}
@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 @@
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 @@
}
@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 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void testDisableNotifications() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -285,6 +292,25 @@
}
@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 @@
}
@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 @@
@Test
@DisableSceneContainer
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void disable_shadeOpenButNotShouldHide_everythingShown() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -338,6 +384,7 @@
/** 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 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void disable_notTransitioningToOccluded_everythingShown() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -380,6 +428,7 @@
@Test
@DisableSceneContainer
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void disable_isTransitioningToOccluded_everythingHidden() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -395,6 +444,7 @@
@Test
@DisableSceneContainer
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -425,7 +475,7 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
@Test
@EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void noOngoingActivity_chipHidden() {
resumeAndGetFragment();
@@ -568,6 +619,7 @@
@Test
@EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() {
resumeAndGetFragment();
@@ -581,8 +633,36 @@
}
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@Test
@EnableSceneContainer
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void isHomeStatusBarAllowedByScene_false_everythingHidden() {
resumeAndGetFragment();
@@ -861,6 +949,7 @@
@Test
@EnableSceneContainer
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void isHomeStatusBarAllowedByScene_true_everythingShown() {
resumeAndGetFragment();
@@ -874,6 +963,7 @@
@Test
@EnableSceneContainer
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -891,6 +981,7 @@
@Test
@EnableSceneContainer
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -908,6 +999,7 @@
@Test
@DisableSceneContainer
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() {
resumeAndGetFragment();
@@ -921,6 +1013,7 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void disable_isDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(true);
@@ -932,6 +1025,7 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void disable_NotDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(false);
@@ -943,6 +1037,7 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
@@ -953,6 +1048,7 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false);
@@ -1006,6 +1102,7 @@
@Test
@DisableSceneContainer
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -1028,6 +1125,7 @@
@Test
@DisableSceneContainer
+ @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() {
final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -1063,6 +1161,7 @@
}
@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 997c00c..c435d3d 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 @@
@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 @@
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 @@
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 @@
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 0000000..5036e77
--- /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 7ae6ea5..bd85780 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.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 @@
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 @@
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 4834d36..cc90c11 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 @@
override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
- override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut
+ override val isClockVisible =
+ MutableStateFlow(
+ CollapsedStatusBarViewModel.VisibilityModel(
+ visibility = View.GONE,
+ shouldAnimateChange = false,
+ )
+ )
- fun setNotificationLightsOut(lightsOut: Boolean) {
- areNotificationLightsOut.value = lightsOut
- }
+ override val isNotificationIconContainerVisible =
+ MutableStateFlow(
+ CollapsedStatusBarViewModel.VisibilityModel(
+ visibility = View.GONE,
+ shouldAnimateChange = false,
+ )
+ )
+
+ 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 0000000..38e04bb
--- /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 8124224..3d41362 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 @@
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 574bbcd..e2b283b 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 @@
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 0000000..385a813
--- /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 0000000..1c7fd48
--- /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 93cdde0..75d07bb 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 @@
@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 2417262..90bb93d 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 @@
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 @@
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 @@
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 d4090e2..3946dd84 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 @@
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 5d251bd..5ba972df 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 @@
return runner;
}
+ private final Class<?> mTestJavaClass;
private TestClass mTestClass = null;
private Runner mRealRunner = null;
private Description mDescription = null;
@@ -192,6 +193,7 @@
* Constructor.
*/
public RavenwoodAwareTestRunner(Class<?> testClass) {
+ mTestJavaClass = testClass;
try {
performGlobalInitialization();
@@ -320,7 +322,7 @@
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 ea33aa6..446f819 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 @@
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 984106b..4196d8e 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 @@
*/
@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 96746c6..989bb6b 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 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 38d1b299..41e45e5 100644
--- a/ravenwood/tests/bivalentinst/Android.bp
+++ b/ravenwood/tests/bivalentinst/Android.bp
@@ -27,8 +27,7 @@
"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 @@
"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 9f3ca6f..92d43d7 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 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 @@
}
@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 @@
}
@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 fdff222..2f35923 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 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 @@
}
@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 @@
}
@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 09ed12d..bd01313 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.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 @@
@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 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/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index c3b7087..1f98334 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,7 +16,15 @@
package com.android.server.appfunctions;
+import android.annotation.NonNull;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -33,5 +41,50 @@
/* unit= */ TimeUnit.SECONDS,
/* workQueue= */ new LinkedBlockingQueue<>());
+ /** A map of per-user executors for queued work. */
+ @GuardedBy("sLock")
+ private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>();
+
+ private static final Object sLock = new Object();
+
+ /**
+ * Returns a per-user executor for queued metadata sync request.
+ *
+ * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence
+ * the use of a single thread.
+ *
+ * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code
+ * MetadataSyncAdapter}.
+ */
+ // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself.
+ public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) {
+ synchronized (sLock) {
+ ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null);
+ if (executor == null) {
+ executor = Executors.newSingleThreadExecutor();
+ mPerUserExecutorsLocked.put(user.getIdentifier(), executor);
+ }
+ return executor;
+ }
+ }
+
+ /**
+ * Shuts down and removes the per-user executor for queued work.
+ *
+ * <p>This should be called when the user is removed.
+ */
+ public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user)
+ throws InterruptedException {
+ ExecutorService executor;
+ synchronized (sLock) {
+ executor = mPerUserExecutorsLocked.get(user.getIdentifier());
+ mPerUserExecutorsLocked.remove(user.getIdentifier());
+ }
+ if (executor != null) {
+ executor.shutdown();
+ var unused = executor.awaitTermination(30, TimeUnit.SECONDS);
+ }
+ }
+
private AppFunctionExecutors() {}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 1e723b5..b4713d9 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -95,7 +95,12 @@
public void onUserStopping(@NonNull TargetUser user) {
Objects.requireNonNull(user);
- MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
+ try {
+ AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle());
+ MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
+ } catch (InterruptedException e) {
+ Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e);
+ }
}
@Override
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index 759f02e..e29b6e4 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -42,7 +42,6 @@
import android.util.ArraySet;
import android.util.Slog;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
@@ -53,12 +52,8 @@
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;
+import java.util.concurrent.Executor;
/**
* This class implements helper methods for synchronously interacting with AppSearch while
@@ -68,14 +63,9 @@
*/
public class MetadataSyncAdapter {
private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
-
- private final ExecutorService mExecutor;
-
+ private final Executor mSyncExecutor;
private final AppSearchManager mAppSearchManager;
private final PackageManager mPackageManager;
- private final Object mLock = new Object();
- @GuardedBy("mLock")
- private Future<AndroidFuture<Boolean>> mCurrentSyncTask;
// Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
// by permissions.
@@ -83,10 +73,12 @@
public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
public MetadataSyncAdapter(
- @NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
+ @NonNull Executor syncExecutor,
+ @NonNull PackageManager packageManager,
+ @NonNull AppSearchManager appSearchManager) {
+ mSyncExecutor = Objects.requireNonNull(syncExecutor);
mPackageManager = Objects.requireNonNull(packageManager);
mAppSearchManager = Objects.requireNonNull(appSearchManager);
- mExecutor = Executors.newSingleThreadExecutor();
}
/**
@@ -105,7 +97,7 @@
AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB)
.build();
AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
- Callable<AndroidFuture<Boolean>> callableTask =
+ mSyncExecutor.execute(
() -> {
try (FutureAppSearchSession staticMetadataSearchSession =
new FutureAppSearchSessionImpl(
@@ -125,28 +117,10 @@
} catch (Exception ex) {
settableSyncStatus.completeExceptionally(ex);
}
- return settableSyncStatus;
- };
-
- synchronized (mLock) {
- if (mCurrentSyncTask != null && !mCurrentSyncTask.isDone()) {
- boolean cancel = mCurrentSyncTask.cancel(false);
- }
- mCurrentSyncTask = mExecutor.submit(callableTask);
- }
-
+ });
return settableSyncStatus;
}
- /** 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);
- }
- }
-
@WorkerThread
@VisibleForTesting
void trySyncAppFunctionMetadataBlocking(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
index e933ec1..f421527 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
@@ -55,7 +55,10 @@
PackageManager perUserPackageManager = userContext.getPackageManager();
if (perUserAppSearchManager != null) {
metadataSyncAdapter =
- new MetadataSyncAdapter(perUserPackageManager, perUserAppSearchManager);
+ new MetadataSyncAdapter(
+ AppFunctionExecutors.getPerUserSyncExecutor(user),
+ perUserPackageManager,
+ perUserAppSearchManager);
sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter);
return metadataSyncAdapter;
}
@@ -71,12 +74,7 @@
*/
public static void removeUserSyncAdapter(UserHandle user) {
synchronized (sLock) {
- MetadataSyncAdapter metadataSyncAdapter =
- sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null);
- if (metadataSyncAdapter != null) {
- metadataSyncAdapter.shutDown();
- sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
- }
+ sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
}
}
}
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 46d60f9..0c54720 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 @@
@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 871c320..414a4e6 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 @@
}
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 d6f04db..1bcf825 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 @@
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
+# Content Provider
+per-file ContentProvider* = varunshah@google.com, yamasani@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
-
+# 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 @@
# 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 22ec790..78a0a11 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 @@
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 00250b4..a93ae72 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4998,53 +4998,70 @@
}
@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;
- }
- 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);
- }
- }
- 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;
+ 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);
+ }
+ 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);
+ }
+ }
+ 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")
void scheduleDispatchProcessDiedLocked(int pid, int uid) {
synchronized (mProcessChangeLock) {
for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 99404428..df69afe 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 @@
//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 @@
}
@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 faeba5d..6611110 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 @@
}
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 @@
+ targetSdk);
}
blocked = false;
+ unblockedBySdk = true;
}
- metricsLogFocusReq(blocked, focusReqType, callingUid);
+ metricsLogFocusReq(blocked, focusReqType, callingUid, unblockedBySdk);
if (!blocked) {
return false;
@@ -195,7 +198,16 @@
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 @@
}
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 7e26356..b4af46e 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 @@
}
/**
+ * 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/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index cf44ac0..a1fd164 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 @@
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 9404034..a10094f 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 @@
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 @@
data, currentBrightness));
}
}
- if (flags.isBrightnessWearBedtimeModeClamperEnabled()) {
- clampers.add(new BrightnessWearBedtimeModeClamper(handler, context,
- clamperChangeListener, data));
- }
return clampers;
}
@@ -362,6 +356,10 @@
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 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 1902e35..0000000
--- 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 0000000..c9c8c33
--- /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/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 73f18d1..9281267 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 @@
/**
* 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 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 65adaba..fd7479e 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 @@
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 7fe7891..4538b49 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.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 @@
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 @@
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 7ba7769..82b36af 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 @@
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 @@
}
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 @@
}
}
- 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 @@
} finally {
mDataStore.saveIfNeeded();
}
+ setKeyRemapping(mDataStore.getKeyRemapping());
}
}
@@ -123,45 +115,25 @@
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 5dd461d..d17e256 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 @@
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 @@
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 0e940d2..f351465 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 @@
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 @@
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 @@
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 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+
+ updateTheme(newConfig.uiMode);
updateScreenDimensions();
updateViewsDimensions();
@@ -250,6 +255,27 @@
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 @@
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 2eed9ba..96426bb 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 @@
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 @@
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 @@
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 e9db1b5..0eb4cbd 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 @@
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 @@
// 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 5aea356..49a6ffd 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 @@
/**
* 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 @@
}
}
}
+
+ 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 cd49340..8bab9de 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2645,11 +2645,15 @@
}
@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 46207c1..b43ddaa 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 @@
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 @@
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 02c02b0..63491e8 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_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 @@
}
@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 @@
}
@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 @@
}
}
+ // 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 @@
if (hasStemPrimaryBehavior()) {
mSingleKeyGestureDetector.addRule(new StemPrimaryKeyRule());
}
+ mSingleKeyGestureDetector.addRule(new StylusTailButtonRule());
}
/**
@@ -3314,6 +3343,16 @@
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 a060f50..441d3ea 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 @@
/**
* 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 @@
* @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 @@
}
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 @@
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 @@
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 @@
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 @@
+ " 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 @@
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 @@
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 @@
// 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 @@
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 20184e9f..f69a017 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 @@
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 @@
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 @@
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 @@
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 e3e83b3..74ca230 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 @@
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;
+ lockWallpaper = loadSettingsFromSerializer(parser, wallpaper, userId, loadSystem,
+ loadLock, keepDimensionHints, wpdData);
- 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);
success = true;
} catch (FileNotFoundException e) {
Slog.w(TAG, "no current wallpaper -- first boot?");
@@ -275,6 +221,75 @@
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 @@
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 @@
}
@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/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index b84ef37..2394da9 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.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.concurrent.CountDownLatch;
import java.util.function.IntFunction;
/**
@@ -93,12 +92,6 @@
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 @@
}
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 @@
return;
}
final int userId = task.mUserId;
- waitForLoading(userId);
PersistableLaunchParams params;
ArrayMap<ComponentName, PersistableLaunchParams> map = mLaunchParamsMap.get(userId);
if (map == null) {
@@ -223,7 +297,6 @@
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 @@
}
}
- /**
- * 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 @@
}
}
- 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 @@
}
}
- 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 f66069c..9dc3d6a 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 @@
/** 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 @@
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 @@
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 @@
* 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 @@
/**
* 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 @@
// 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 @@
}
}
- while (mQueue.isEmpty()) {
+ while (mWriteQueue.isEmpty()) {
if (mNextWriteTime != 0) {
mNextWriteTime = 0; // idle.
notify(); // May need to wake up flush().
@@ -227,18 +224,17 @@
}
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 @@
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 @@
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/Task.java b/services/core/java/com/android/server/wm/Task.java
index a2fda0a..86bb75a 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_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.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.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 @@
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 @@
intent = _intent;
mMinWidth = minWidth;
mMinHeight = minHeight;
+ updateAllowForceResizeOverride();
}
mAtmService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity);
mHandler = new ActivityTaskHandler(mTaskSupervisor.mLooper);
@@ -1028,6 +1039,7 @@
mTaskSupervisor.mRecentTasks.remove(this);
mTaskSupervisor.mRecentTasks.add(this);
}
+ updateAllowForceResizeOverride();
}
/** Sets the original minimal width and height. */
@@ -1823,6 +1835,17 @@
-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 @@
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/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index d2493c5..5cd117b 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 @@
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 @@
// 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 @@
outConfig->stylusButtonMotionEventsEnabled = mLocked.stylusButtonMotionEventsEnabled;
outConfig->stylusPointerIconEnabled = mLocked.stylusPointerIconEnabled;
+
+ outConfig->keyRemapping = mLocked.keyRemapping;
} // release lock
}
@@ -1910,6 +1916,16 @@
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 @@
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 @@
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 @@
{"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 dbbb2fe..da3e94f 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 @@
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/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index bc64e15..c05c381 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -36,6 +36,7 @@
import com.android.internal.infra.AndroidFuture
import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults
import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.atomic.AtomicBoolean
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,6 +46,7 @@
class MetadataSyncAdapterTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val appSearchManager = context.getSystemService(AppSearchManager::class.java)
+ private val testExecutor = MoreExecutors.directExecutor()
private val packageManager = context.packageManager
@Test
@@ -136,7 +138,8 @@
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
runtimeSearchSession.put(putDocumentsRequest).get()
staticSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
+ val metadataSyncAdapter =
+ MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -177,7 +180,8 @@
val putDocumentsRequest: PutDocumentsRequest =
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
staticSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
+ val metadataSyncAdapter =
+ MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -232,7 +236,8 @@
val putDocumentsRequest: PutDocumentsRequest =
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
runtimeSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
+ val metadataSyncAdapter =
+ MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
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 306b4f8..0000000
--- 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 0000000..8271a0f
--- /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/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index f6ad07d..2107406 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 @@
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 3062d51..9ba2724 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 @@
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 0b762df..9983fb4 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.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 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.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.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.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 @@
@ClassRule
public static final TestableContext sContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+
private static ComponentName sImageWallpaperComponentName;
private static ComponentName sDefaultWallpaperComponent;
@@ -133,8 +143,11 @@
@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 @@
* 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 @@
}
@Test
- public void testXmlSerializationRoundtrip() {
- WallpaperData systemWallpaperData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0);
+ @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(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, 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(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 0000000..34878c8
--- /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 9b87947..1206928 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 @@
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 7ea5010..ff8b6d3 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 @@
}
@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 @@
}
@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 62d3949..1be61c3 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 @@
mUserFolderGetter);
target.onSystemReady();
target.onUnlockUser(TEST_USER_ID);
- mPersisterQueue.flush();
target.getLaunchParams(mTestTask, null, mResult);
@@ -312,7 +311,6 @@
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 @@
mUserFolderGetter);
target.onSystemReady();
target.onUnlockUser(TEST_USER_ID);
- mPersisterQueue.flush();
target.getLaunchParams(mTaskWithDifferentComponent, null, mResult);
@@ -411,7 +408,6 @@
mUserFolderGetter);
target.onSystemReady();
target.onUnlockUser(TEST_USER_ID);
- mPersisterQueue.flush();
target.getLaunchParams(mTestTask, null, mResult);
@@ -429,7 +425,6 @@
mUserFolderGetter);
target.onSystemReady();
target.onUnlockUser(TEST_USER_ID);
- mPersisterQueue.flush();
target.getLaunchParams(mTestTask, null, mResult);
@@ -458,7 +453,6 @@
mUserFolderGetter);
target.onSystemReady();
target.onUnlockUser(TEST_USER_ID);
- mPersisterQueue.flush();
target.getLaunchParams(mTestTask, null, mResult);
@@ -476,7 +470,6 @@
mUserFolderGetter);
target.onSystemReady();
target.onUnlockUser(TEST_USER_ID);
- mPersisterQueue.flush();
target.getLaunchParams(mTestTask, null, mResult);
@@ -495,52 +488,12 @@
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 @@
/**
* 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 @@
}
@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 @@
}
@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 ce0e6f8..3e87f1f 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 @@
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 @@
}
@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 @@
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 @@
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 @@
// 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 @@
@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 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 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 @@
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 @@
@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 @@
@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 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 @@
@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 @@
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 @@
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 7ff2e50..4b03483 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.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.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 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 @@
@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 @@
}
@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/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
index e20e4d2..e42b41f 100644
--- a/telephony/java/android/telephony/BarringInfo.java
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -159,7 +159,7 @@
/**
* @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/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index b3a998e..9459070 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 @@
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