Merge "Health: Remove non fixed-read-only replace_body_sensors flag" 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/AconfigFlags.bp b/AconfigFlags.bp
index dd919ca..a0f38d9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -680,6 +680,11 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "com.android.media.flags.editing-aconfig-cc",
+ aconfig_declarations: "com.android.media.flags.editing-aconfig",
+}
+
// MediaProjection
aconfig_declarations {
name: "com.android.media.flags.projection-aconfig",
diff --git a/Android.bp b/Android.bp
index 5b9f2cb..8c01585 100644
--- a/Android.bp
+++ b/Android.bp
@@ -99,6 +99,7 @@
":android.hardware.biometrics.common-V4-java-source",
":android.hardware.biometrics.fingerprint-V5-java-source",
":android.hardware.biometrics.fingerprint.virtualhal-java-source",
+ ":android.hardware.biometrics.face.virtualhal-java-source",
":android.hardware.biometrics.face-V4-java-source",
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
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..f817241 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6495,6 +6495,7 @@
field public static final int FLAG_NO_CLEAR = 32; // 0x20
field public static final int FLAG_ONGOING_EVENT = 2; // 0x2
field public static final int FLAG_ONLY_ALERT_ONCE = 8; // 0x8
+ field @FlaggedApi("android.app.api_rich_ongoing") public static final int FLAG_PROMOTED_ONGOING = 262144; // 0x40000
field @Deprecated public static final int FLAG_SHOW_LIGHTS = 1; // 0x1
field public static final int FOREGROUND_SERVICE_DEFAULT = 0; // 0x0
field public static final int FOREGROUND_SERVICE_DEFERRED = 2; // 0x2
@@ -19396,7 +19397,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 +19922,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 +19948,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 +20108,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 {
@@ -36934,14 +36935,16 @@
@FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState {
ctor public ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState(int, @Nullable android.accounts.Account);
- method @Nullable public android.accounts.Account getCloudAccount();
+ method @Nullable public android.accounts.Account getAccount();
method public int getState();
method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofCloud(@NonNull android.accounts.Account);
method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofLocal();
method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofNotSet();
+ method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofSim(@NonNull android.accounts.Account);
field public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3; // 0x3
field public static final int DEFAULT_ACCOUNT_STATE_LOCAL = 2; // 0x2
field public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1; // 0x1
+ field public static final int DEFAULT_ACCOUNT_STATE_SIM = 4; // 0x4
}
public static final class ContactsContract.RawContacts.DisplayPhoto {
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/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java
new file mode 100644
index 0000000..d6f061b
--- /dev/null
+++ b/core/java/android/app/BroadcastStickyCache.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
+import android.net.TetheringManager;
+import android.net.nsd.NsdManager;
+import android.net.wifi.WifiManager;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.SystemProperties;
+import android.os.UpdateLock;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.view.WindowManagerPolicyConstants;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+
+/** @hide */
+public class BroadcastStickyCache {
+
+ private static final String[] CACHED_BROADCAST_ACTIONS = {
+ AudioManager.ACTION_HDMI_AUDIO_PLUG,
+ AudioManager.ACTION_HEADSET_PLUG,
+ AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED,
+ AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED,
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+ AudioManager.RINGER_MODE_CHANGED_ACTION,
+ ConnectivityManager.CONNECTIVITY_ACTION,
+ Intent.ACTION_BATTERY_CHANGED,
+ Intent.ACTION_DEVICE_STORAGE_FULL,
+ Intent.ACTION_DEVICE_STORAGE_LOW,
+ Intent.ACTION_SIM_STATE_CHANGED,
+ NsdManager.ACTION_NSD_STATE_CHANGED,
+ TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED,
+ TetheringManager.ACTION_TETHER_STATE_CHANGED,
+ UpdateLock.UPDATE_LOCK_CHANGED,
+ UsbManager.ACTION_USB_STATE,
+ WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED,
+ WifiManager.NETWORK_STATE_CHANGED_ACTION,
+ WifiManager.SUPPLICANT_STATE_CHANGED_ACTION,
+ WifiManager.WIFI_STATE_CHANGED_ACTION,
+ WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
+ WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED,
+ "android.net.conn.INET_CONDITION_ACTION" // ConnectivityManager.INET_CONDITION_ACTION
+ };
+
+ @GuardedBy("sCachedStickyBroadcasts")
+ private static final ArrayList<CachedStickyBroadcast> sCachedStickyBroadcasts =
+ new ArrayList<>();
+
+ @GuardedBy("sCachedPropertyHandles")
+ private static final ArrayMap<String, SystemProperties.Handle> sCachedPropertyHandles =
+ new ArrayMap<>();
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static boolean useCache(@Nullable IntentFilter filter) {
+ if (!shouldCache(filter)) {
+ return false;
+ }
+ synchronized (sCachedStickyBroadcasts) {
+ final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
+ if (cachedStickyBroadcast == null) {
+ return false;
+ }
+ final long version = cachedStickyBroadcast.propertyHandle.getLong(-1 /* def */);
+ return version > 0 && cachedStickyBroadcast.version == version;
+ }
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static void add(@Nullable IntentFilter filter, @Nullable Intent intent) {
+ if (!shouldCache(filter)) {
+ return;
+ }
+ synchronized (sCachedStickyBroadcasts) {
+ CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
+ if (cachedStickyBroadcast == null) {
+ final String key = getKey(filter.getAction(0));
+ final SystemProperties.Handle handle = SystemProperties.find(key);
+ final long version = handle == null ? -1 : handle.getLong(-1 /* def */);
+ if (version == -1) {
+ return;
+ }
+ cachedStickyBroadcast = new CachedStickyBroadcast(filter, handle);
+ sCachedStickyBroadcasts.add(cachedStickyBroadcast);
+ cachedStickyBroadcast.intent = intent;
+ cachedStickyBroadcast.version = version;
+ } else {
+ cachedStickyBroadcast.intent = intent;
+ cachedStickyBroadcast.version = cachedStickyBroadcast.propertyHandle
+ .getLong(-1 /* def */);
+ }
+ }
+ }
+
+ private static boolean shouldCache(@Nullable IntentFilter filter) {
+ if (!Flags.useStickyBcastCache()) {
+ return false;
+ }
+ if (filter == null || filter.safeCountActions() != 1) {
+ return false;
+ }
+ if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, filter.getAction(0))) {
+ return false;
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ public static String getKey(@NonNull String action) {
+ return "cache_key.system_server.sticky_bcast." + action;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @Nullable
+ public static Intent getIntentUnchecked(@NonNull IntentFilter filter) {
+ synchronized (sCachedStickyBroadcasts) {
+ final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
+ return cachedStickyBroadcast.intent;
+ }
+ }
+
+ @GuardedBy("sCachedStickyBroadcasts")
+ @Nullable
+ private static CachedStickyBroadcast getValueUncheckedLocked(@NonNull IntentFilter filter) {
+ for (int i = sCachedStickyBroadcasts.size() - 1; i >= 0; --i) {
+ final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
+ if (IntentFilter.filterEquals(filter, cachedStickyBroadcast.filter)) {
+ return cachedStickyBroadcast;
+ }
+ }
+ return null;
+ }
+
+ public static void incrementVersion(@NonNull String action) {
+ if (!shouldIncrementVersion(action)) {
+ return;
+ }
+ final String key = getKey(action);
+ synchronized (sCachedPropertyHandles) {
+ SystemProperties.Handle handle = sCachedPropertyHandles.get(key);
+ final long version;
+ if (handle == null) {
+ handle = SystemProperties.find(key);
+ if (handle != null) {
+ sCachedPropertyHandles.put(key, handle);
+ }
+ }
+ version = handle == null ? 0 : handle.getLong(0 /* def */);
+ SystemProperties.set(key, String.valueOf(version + 1));
+ if (handle == null) {
+ sCachedPropertyHandles.put(key, SystemProperties.find(key));
+ }
+ }
+ }
+
+ public static void incrementVersionIfExists(@NonNull String action) {
+ if (!shouldIncrementVersion(action)) {
+ return;
+ }
+ final String key = getKey(action);
+ synchronized (sCachedPropertyHandles) {
+ final SystemProperties.Handle handle = sCachedPropertyHandles.get(key);
+ if (handle == null) {
+ return;
+ }
+ final long version = handle.getLong(0 /* def */);
+ SystemProperties.set(key, String.valueOf(version + 1));
+ }
+ }
+
+ private static boolean shouldIncrementVersion(@NonNull String action) {
+ if (!Flags.useStickyBcastCache()) {
+ return false;
+ }
+ if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, action)) {
+ return false;
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ public static void clearForTest() {
+ synchronized (sCachedStickyBroadcasts) {
+ sCachedStickyBroadcasts.clear();
+ }
+ synchronized (sCachedPropertyHandles) {
+ sCachedPropertyHandles.clear();
+ }
+ }
+
+ private static final class CachedStickyBroadcast {
+ @NonNull public final IntentFilter filter;
+ @Nullable public Intent intent;
+ @IntRange(from = 0) public long version;
+ @NonNull public final SystemProperties.Handle propertyHandle;
+
+ CachedStickyBroadcast(@NonNull IntentFilter filter,
+ @NonNull SystemProperties.Handle propertyHandle) {
+ this.filter = filter;
+ this.propertyHandle = propertyHandle;
+ }
+ }
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 90fba29..ecef0db 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1921,10 +1921,19 @@
}
}
try {
- final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
- mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
- AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
- flags);
+ final Intent intent;
+ if (receiver == null && BroadcastStickyCache.useCache(filter)) {
+ intent = BroadcastStickyCache.getIntentUnchecked(filter);
+ } else {
+ intent = ActivityManager.getService().registerReceiverWithFeature(
+ mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
+ AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission,
+ userId,
+ flags);
+ if (receiver == null) {
+ BroadcastStickyCache.add(filter, intent);
+ }
+ }
if (intent != null) {
intent.setExtrasClassLoader(getClassLoader());
// TODO: determine at registration time if caller is
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index e2bee64..b9fe356 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -258,4 +258,6 @@
@EnforcePermission(allOf={"INTERACT_ACROSS_USERS", "ACCESS_NOTIFICATIONS"})
void unregisterCallNotificationEventListener(String packageName, in UserHandle userHandle, in ICallNotificationEventCallback listener);
+ void setCanBePromoted(String pkg, int uid, boolean promote);
+ boolean canBePromoted(String pkg, int uid);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 81d2c89..4d73c35 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -772,6 +772,17 @@
@FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
public static final int FLAG_SILENT = 1 << 17; //0x00020000
+ /**
+ * Bit to be bitwise-ored into the {@link #flags} field that should be
+ * set by the system if this notification is a promoted ongoing notification, either via a
+ * user setting or allowlist.
+ *
+ * Applications cannot set this flag directly, but the posting app and
+ * {@link android.service.notification.NotificationListenerService} can read it.
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final int FLAG_PROMOTED_ONGOING = 0x00040000;
+
private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
@@ -3110,6 +3121,53 @@
}
/**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+ public boolean containsCustomViews() {
+ return contentView != null
+ || bigContentView != null
+ || headsUpContentView != null
+ || (publicVersion != null
+ && (publicVersion.contentView != null
+ || publicVersion.bigContentView != null
+ || publicVersion.headsUpContentView != null));
+ }
+
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+ public boolean hasTitle() {
+ return extras != null
+ && (!TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE))
+ || !TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE_BIG)));
+ }
+
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+ public boolean hasPromotableStyle() {
+ //TODO(b/367739672): Add progress style
+ return extras == null || !extras.containsKey(Notification.EXTRA_TEMPLATE)
+ || isStyle(Notification.BigPictureStyle.class)
+ || isStyle(Notification.BigTextStyle.class)
+ || isStyle(Notification.CallStyle.class);
+ }
+
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+ public boolean hasPromotableCharacteristics() {
+ return isColorized()
+ && hasTitle()
+ && !containsCustomViews()
+ && hasPromotableStyle();
+ }
+
+ /**
* Whether this notification was posted by a headless system app.
*
* If we don't have enough information to figure this out, this will return false. Therefore,
@@ -7636,7 +7694,6 @@
if (mLargeIcon != null || largeIcon != null) {
Resources resources = context.getResources();
- Class<? extends Style> style = getNotificationStyle();
int maxSize = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_right_icon_size_low_ram
: R.dimen.notification_right_icon_size);
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 5ed1f4e..637187e 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -177,6 +177,10 @@
{
"file_patterns": ["(/|^)AppOpsManager.java"],
"name": "CtsAppOpsTestCases"
+ },
+ {
+ "file_patterns": ["(/|^)BroadcastStickyCache.java"],
+ "name": "BroadcastUnitTests"
}
]
}
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 38bd576..56488e7 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -147,3 +147,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "backstage_power"
+ name: "use_sticky_bcast_cache"
+ description: "Use cache for sticky broadcast intents"
+ is_fixed_read_only: true
+ bug: "356148006"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
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/face/FaceSensorConfigurations.java b/core/java/android/hardware/face/FaceSensorConfigurations.java
index 1247168..51c5f4c 100644
--- a/core/java/android/hardware/face/FaceSensorConfigurations.java
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.face.virtualhal.IVirtualHal;
import android.os.Binder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -160,6 +161,41 @@
dest.writeByte((byte) (mResetLockoutRequiresChallenge ? 1 : 0));
dest.writeMap(mSensorPropsMap);
}
+ /**
+ * Remap fqName of VHAL because the `virtual` instance is registered
+ * with IVirtulalHal now (IFace previously)
+ * @param fqName fqName to be translated
+ * @return real fqName
+ */
+ public static String remapFqName(String fqName) {
+ if (!fqName.contains(IFace.DESCRIPTOR + "/virtual")) {
+ return fqName; //no remap needed for real hardware HAL
+ } else {
+ //new Vhal instance name
+ return fqName.replace("IFace", "virtualhal.IVirtualHal");
+ }
+ }
+ /**
+ * @param fqName aidl interface instance name
+ * @return aidl interface
+ */
+ public static IFace getIFace(String fqName) {
+ if (fqName.contains("virtual")) {
+ String fqNameMapped = remapFqName(fqName);
+ Slog.i(TAG, "getIFace fqName is mapped: " + fqName + "->" + fqNameMapped);
+ try {
+ IVirtualHal vhal = IVirtualHal.Stub.asInterface(
+ Binder.allowBlocking(ServiceManager.waitForService(fqNameMapped)));
+ return vhal.getFaceHal();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in vhal.getFaceHal() call" + fqNameMapped);
+ }
+ }
+
+ return IFace.Stub.asInterface(
+ Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+ }
+
/**
* Returns face sensor props for the HAL {@param instance}.
@@ -173,14 +209,13 @@
return props;
}
- final String fqName = IFace.DESCRIPTOR + "/" + instance;
- IFace face = IFace.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(fqName)));
try {
- if (face != null) {
- props = face.getSensorProps();
+ final String fqName = IFace.DESCRIPTOR + "/" + instance;
+ final IFace fp = getIFace(fqName);
+ if (fp != null) {
+ props = fp.getSensorProps();
} else {
- Slog.e(TAG, "Unable to get declared service: " + fqName);
+ Log.d(TAG, "IFace null for instance " + instance);
}
} catch (RemoteException e) {
Log.d(TAG, "Unable to get sensor properties!");
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/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index a622810..27b1dfb 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -3046,6 +3046,11 @@
* <li> {@link #DEFAULT_ACCOUNT_STATE_CLOUD}: The default account is set to a
* cloud-synced account. New raw contacts requested for insertion without a specified
* {@link Account} will be saved in the default cloud account. </li>
+ * <li> {@link #DEFAULT_ACCOUNT_STATE_SIM}: The default account is set to a
+ * account that is associated with one of
+ * {@link SimContacts#getSimAccounts(ContentResolver)}. New raw contacts requested
+ * for insertion without a specified {@link Account} will be
+ * saved in this SIM account. </li>
* </ul>
*/
@FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
@@ -3063,44 +3068,51 @@
public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3;
/**
+ * A state indicating that the default account is set as an account that is
+ * associated with one of {@link SimContacts#getSimAccounts(ContentResolver)}.
+ */
+ public static final int DEFAULT_ACCOUNT_STATE_SIM = 4;
+
+ /**
* The state of the default account. One of
* {@link #DEFAULT_ACCOUNT_STATE_NOT_SET},
- * {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or
- * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+ * {@link #DEFAULT_ACCOUNT_STATE_LOCAL},
+ * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}
+ * {@link #DEFAULT_ACCOUNT_STATE_SIM}.
*/
@DefaultAccountState
private final int mState;
/**
- * The account of the default account, when {@link mState} is {
+ * The account of the default account, when {@link #mState} is {
*
- * @link #STATE_SET_TO_CLOUD}, or null otherwise.
+ * @link #DEFAULT_ACCOUNT_STATE_CLOUD} or {@link #DEFAULT_ACCOUNT_STATE_SIM}, or
+ * null otherwise.
*/
- private final Account mCloudAccount;
+ private final Account mAccount;
/**
* Constructs a new `DefaultAccountAndState` instance with the specified state and
* cloud
* account.
*
- * @param state The state of the default account.
- * @param cloudAccount The cloud account associated with the default account,
- * or null if the state is not
- * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+ * @param state The state of the default account.
+ * @param account The account associated with the default account if the state is
+ * {@link #DEFAULT_ACCOUNT_STATE_CLOUD} or
+ * {@link #DEFAULT_ACCOUNT_STATE_SIM}, or null otherwise.
*/
public DefaultAccountAndState(@DefaultAccountState int state,
- @Nullable Account cloudAccount) {
+ @Nullable Account account) {
if (!isValidDefaultAccountState(state)) {
throw new IllegalArgumentException("Invalid default account state.");
}
- if ((state == DEFAULT_ACCOUNT_STATE_CLOUD) != (cloudAccount != null)) {
+ if (isCloudOrSimAccount(state) != (account != null)) {
throw new IllegalArgumentException(
- "Default account can be set to cloud if and only if the cloud "
+ "Default account can be set to cloud or SIM if and only if the "
+ "account is provided.");
}
this.mState = state;
- this.mCloudAccount =
- (mState == DEFAULT_ACCOUNT_STATE_CLOUD) ? cloudAccount : null;
+ this.mAccount = isCloudOrSimAccount(state) ? account : null;
}
/**
@@ -3118,6 +3130,21 @@
return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_CLOUD, cloudAccount);
}
+
+ /**
+ * Creates a `DefaultAccountAndState` instance representing a default account
+ * that is set to the sim and associated with the specified sim account.
+ *
+ * @param simAccount The non-null sim account associated with the default
+ * contacts account.
+ * @return A new `DefaultAccountAndState` instance with state
+ * {@link #DEFAULT_ACCOUNT_STATE_SIM}.
+ */
+ public static @NonNull DefaultAccountAndState ofSim(
+ @NonNull Account simAccount) {
+ return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_SIM, simAccount);
+ }
+
/**
* Creates a `DefaultAccountAndState` instance representing a default account
* that is set to the local device storage.
@@ -3140,6 +3167,18 @@
return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_NOT_SET, null);
}
+ private static boolean isCloudOrSimAccount(@DefaultAccountState int state) {
+ return state == DEFAULT_ACCOUNT_STATE_CLOUD
+ || state == DEFAULT_ACCOUNT_STATE_SIM;
+ }
+
+ private static boolean isValidDefaultAccountState(int state) {
+ return state == DEFAULT_ACCOUNT_STATE_NOT_SET
+ || state == DEFAULT_ACCOUNT_STATE_LOCAL
+ || state == DEFAULT_ACCOUNT_STATE_CLOUD
+ || state == DEFAULT_ACCOUNT_STATE_SIM;
+ }
+
/**
* @return the state of the default account.
*/
@@ -3149,16 +3188,17 @@
}
/**
- * @return the cloud account associated with the default account, or null if the
- * state is not {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+ * @return the cloud account associated with the default account if the
+ * state is {@link #DEFAULT_ACCOUNT_STATE_CLOUD} or
+ * {@link #DEFAULT_ACCOUNT_STATE_SIM}.
*/
- public @Nullable Account getCloudAccount() {
- return mCloudAccount;
+ public @Nullable Account getAccount() {
+ return mAccount;
}
@Override
public int hashCode() {
- return Objects.hash(mState, mCloudAccount);
+ return Objects.hash(mState, mAccount);
}
@Override
@@ -3170,14 +3210,8 @@
return false;
}
- return mState == that.mState && Objects.equals(mCloudAccount,
- that.mCloudAccount);
- }
-
- private static boolean isValidDefaultAccountState(int state) {
- return state == DEFAULT_ACCOUNT_STATE_NOT_SET
- || state == DEFAULT_ACCOUNT_STATE_LOCAL
- || state == DEFAULT_ACCOUNT_STATE_CLOUD;
+ return mState == that.mState && Objects.equals(mAccount,
+ that.mAccount);
}
/**
@@ -3189,7 +3223,8 @@
@IntDef(
prefix = {"DEFAULT_ACCOUNT_STATE_"},
value = {DEFAULT_ACCOUNT_STATE_NOT_SET,
- DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD})
+ DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD,
+ DEFAULT_ACCOUNT_STATE_SIM})
public @interface DefaultAccountState {
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index d45b24e..303197d 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -202,10 +202,8 @@
private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR;
public static final String MANUAL_RULE_ID = "MANUAL_RULE";
- public static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
+ public static final String EVENTS_OBSOLETE_RULE_ID = "EVENTS_DEFAULT_RULE";
public static final String EVERY_NIGHT_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
- public static final List<String> DEFAULT_RULE_IDS = Arrays.asList(EVERY_NIGHT_DEFAULT_RULE_ID,
- EVENTS_DEFAULT_RULE_ID);
public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY };
@@ -424,21 +422,10 @@
return policy;
}
+ @FlaggedApi(Flags.FLAG_MODES_UI)
public static ZenModeConfig getDefaultConfig() {
ZenModeConfig config = new ZenModeConfig();
- EventInfo eventInfo = new EventInfo();
- eventInfo.reply = REPLY_YES_OR_MAYBE;
- ZenRule events = new ZenRule();
- events.id = EVENTS_DEFAULT_RULE_ID;
- events.conditionId = toEventConditionId(eventInfo);
- events.component = ComponentName.unflattenFromString(
- "android/com.android.server.notification.EventConditionProvider");
- events.enabled = false;
- events.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- events.pkg = "android";
- config.automaticRules.put(EVENTS_DEFAULT_RULE_ID, events);
-
ScheduleInfo scheduleInfo = new ScheduleInfo();
scheduleInfo.days = new int[] {1, 2, 3, 4, 5, 6, 7};
scheduleInfo.startHour = 22;
@@ -457,6 +444,13 @@
return config;
}
+ // TODO: b/368247671 - Can be made a constant again when modes_ui is inlined
+ public static List<String> getDefaultRuleIds() {
+ return Flags.modesUi()
+ ? List.of(EVERY_NIGHT_DEFAULT_RULE_ID)
+ : List.of(EVERY_NIGHT_DEFAULT_RULE_ID, EVENTS_OBSOLETE_RULE_ID);
+ }
+
void ensureManualZenRule() {
if (manualRule == null) {
final ZenRule newRule = new ZenRule();
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 384add5..2ab16e9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2397,7 +2397,11 @@
// it hasn't changed and there is no need to update.
ret = mBlastBufferQueue.createSurface();
} else {
- mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format);
+ if (mBbqSurfaceControl != null && mBbqSurfaceControl.isValid()) {
+ mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format);
+ } else {
+ Log.w(TAG, "Skipping BlastBufferQueue update - invalid surface control");
+ }
}
return ret;
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index c83285a..3599332 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -168,3 +168,13 @@
description: "Decouple variation settings, weight and style information from Typeface class"
bug: "361260253"
}
+
+flag {
+ name: "handwriting_track_disabled"
+ namespace: "text"
+ description: "Handwriting initiator tracks focused view even if handwriting is disabled to fix initiation bug."
+ bug: "361256391"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index ab9bd1f..f132963 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -17,6 +17,7 @@
package android.view;
import static com.android.text.flags.Flags.handwritingCursorPosition;
+import static com.android.text.flags.Flags.handwritingTrackDisabled;
import static com.android.text.flags.Flags.handwritingUnsupportedMessage;
import android.annotation.FlaggedApi;
@@ -352,7 +353,7 @@
final View focusedView = getFocusedView();
- if (!view.isAutoHandwritingEnabled()) {
+ if (!handwritingTrackDisabled() && !view.isAutoHandwritingEnabled()) {
clearFocusedView(focusedView);
return;
}
@@ -363,7 +364,8 @@
updateFocusedView(view);
if (mState != null && mState.mPendingFocusedView != null
- && mState.mPendingFocusedView.get() == view) {
+ && mState.mPendingFocusedView.get() == view
+ && (!handwritingTrackDisabled() || view.isAutoHandwritingEnabled())) {
startHandwriting(view);
}
}
@@ -416,7 +418,7 @@
*/
@VisibleForTesting
public boolean updateFocusedView(@NonNull View view) {
- if (!view.shouldInitiateHandwriting()) {
+ if (!handwritingTrackDisabled() && !view.shouldInitiateHandwriting()) {
mFocusedView = null;
return false;
}
@@ -424,8 +426,10 @@
final View focusedView = getFocusedView();
if (focusedView != view) {
mFocusedView = new WeakReference<>(view);
- // A new view just gain focus. By default, we should show hover icon for it.
- mShowHoverIconForConnectedView = true;
+ if (!handwritingTrackDisabled() || view.shouldInitiateHandwriting()) {
+ // A new view just gain focus. By default, we should show hover icon for it.
+ mShowHoverIconForConnectedView = true;
+ }
}
return true;
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index e90b1c0..229e8ee7 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -26,7 +26,6 @@
import android.os.IBinder;
import android.os.Trace;
import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl.Transaction;
import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
@@ -34,8 +33,6 @@
import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.SoftInputShowHideReason;
-import java.util.function.Supplier;
-
/**
* Controls the visibility and animations of IME window insets source.
* @hide
@@ -54,10 +51,8 @@
*/
private boolean mIsRequestedVisibleAwaitingLeash;
- public ImeInsetsSourceConsumer(
- int id, InsetsState state, Supplier<Transaction> transactionSupplier,
- InsetsController controller) {
- super(id, WindowInsets.Type.ime(), state, transactionSupplier, controller);
+ public ImeInsetsSourceConsumer(int id, InsetsState state, InsetsController controller) {
+ super(id, WindowInsets.Type.ime(), state, controller);
}
@Override
diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java
index 04bb609..a0d8a17 100644
--- a/core/java/android/view/InsetsAnimationControlCallbacks.java
+++ b/core/java/android/view/InsetsAnimationControlCallbacks.java
@@ -54,13 +54,6 @@
void notifyFinished(InsetsAnimationControlRunner runner, boolean shown);
/**
- * Apply the new params to the surface.
- * @param params The {@link android.view.SyncRtSurfaceTransactionApplier.SurfaceParams} to
- * apply.
- */
- void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params);
-
- /**
* Post a message to release the Surface, guaranteed to happen after all
* previous calls to applySurfaceParams.
*/
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 91e9230..97facc1 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -99,6 +99,7 @@
private final @InsetsType int mTypes;
private @InsetsType int mControllingTypes;
private final InsetsAnimationControlCallbacks mController;
+ private final SurfaceParamsApplier mSurfaceParamsApplier;
private final WindowInsetsAnimation mAnimation;
private final long mDurationMs;
private final Interpolator mInterpolator;
@@ -123,6 +124,7 @@
public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls,
@Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types, InsetsAnimationControlCallbacks controller,
+ SurfaceParamsApplier surfaceParamsApplier,
InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) {
@@ -131,6 +133,7 @@
mTypes = types;
mControllingTypes = types;
mController = controller;
+ mSurfaceParamsApplier = surfaceParamsApplier;
mInitialInsetsState = new InsetsState(state, true /* copySources */);
if (frame != null) {
final SparseIntArray idSideMap = new SparseIntArray();
@@ -258,6 +261,11 @@
}
@Override
+ public SurfaceParamsApplier getSurfaceParamsApplier() {
+ return mSurfaceParamsApplier;
+ }
+
+ @Override
@Nullable
public ImeTracker.Token getStatsToken() {
return mStatsToken;
@@ -305,7 +313,7 @@
updateLeashesForSide(SIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
updateLeashesForSide(SIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
- mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
+ mSurfaceParamsApplier.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = mPendingInsets;
mAnimation.setFraction(mPendingFraction);
mCurrentAlpha = mPendingAlpha;
diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java
index 8cb8b47..4f102da 100644
--- a/core/java/android/view/InsetsAnimationControlRunner.java
+++ b/core/java/android/view/InsetsAnimationControlRunner.java
@@ -77,6 +77,11 @@
@AnimationType int getAnimationType();
/**
+ * @return The {@link SurfaceParamsApplier} this runner is using.
+ */
+ SurfaceParamsApplier getSurfaceParamsApplier();
+
+ /**
* @return The token tracking the current IME request or {@code null} otherwise.
*/
@Nullable
@@ -99,4 +104,27 @@
* @param fieldId FieldId of the implementation class
*/
void dumpDebug(ProtoOutputStream proto, long fieldId);
+
+ /**
+ * Interface applying given surface operations.
+ */
+ interface SurfaceParamsApplier {
+
+ SurfaceParamsApplier DEFAULT = params -> {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (int i = params.length - 1; i >= 0; i--) {
+ SyncRtSurfaceTransactionApplier.applyParams(t, params[i], new float[9]);
+ }
+ t.apply();
+ t.close();
+ };
+
+ /**
+ * Apply the new params to the surface.
+ *
+ * @param params The {@link SyncRtSurfaceTransactionApplier.SurfaceParams} to apply.
+ */
+ void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params);
+
+ }
}
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index fc185bc..8c2c495 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -17,7 +17,6 @@
package android.view;
import static android.view.InsetsController.DEBUG;
-import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
import android.annotation.Nullable;
import android.annotation.UiThread;
@@ -30,7 +29,6 @@
import android.util.proto.ProtoOutputStream;
import android.view.InsetsController.AnimationType;
import android.view.InsetsController.LayoutInsetsDuringAnimation;
-import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.inputmethod.ImeTracker;
@@ -50,8 +48,6 @@
private final InsetsAnimationControlCallbacks mCallbacks =
new InsetsAnimationControlCallbacks() {
- private final float[] mTmpFloat9 = new float[9];
-
@Override
@UiThread
public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController>
@@ -81,19 +77,6 @@
}
@Override
- public void applySurfaceParams(SurfaceParams... params) {
- if (DEBUG) Log.d(TAG, "applySurfaceParams");
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- for (int i = params.length - 1; i >= 0; i--) {
- SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i];
- applyParams(t, surfaceParams, mTmpFloat9);
- }
- t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
- t.apply();
- t.close();
- }
-
- @Override
public void releaseSurfaceControlFromRt(SurfaceControl sc) {
if (DEBUG) Log.d(TAG, "releaseSurfaceControlFromRt");
// Since we don't push the SurfaceParams to the RT we can release directly
@@ -106,6 +89,22 @@
}
};
+ private SurfaceParamsApplier mSurfaceParamsApplier = new SurfaceParamsApplier() {
+
+ private final float[] mTmpFloat9 = new float[9];
+
+ @Override
+ public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (int i = params.length - 1; i >= 0; i--) {
+ SyncRtSurfaceTransactionApplier.applyParams(t, params[i], mTmpFloat9);
+ }
+ t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ t.apply();
+ t.close();
+ }
+ };
+
@UiThread
public InsetsAnimationThreadControlRunner(SparseArray<InsetsSourceControl> controls,
@Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
@@ -117,8 +116,8 @@
mMainThreadHandler = mainThreadHandler;
mOuterCallbacks = controller;
mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types,
- mCallbacks, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
- translator, statsToken);
+ mCallbacks, mSurfaceParamsApplier, insetsAnimationSpec, animationType,
+ layoutInsetsDuringAnimation, translator, statsToken);
InsetsAnimationThread.getHandler().post(() -> {
if (mControl.isCancelled()) {
return;
@@ -187,6 +186,11 @@
}
@Override
+ public SurfaceParamsApplier getSurfaceParamsApplier() {
+ return mSurfaceParamsApplier;
+ }
+
+ @Override
public void updateLayoutInsetsDuringAnimation(
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
InsetsAnimationThread.getHandler().post(
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8fdf91a..e38281f 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -55,7 +55,6 @@
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSourceConsumer.ShowResult;
-import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
@@ -85,7 +84,8 @@
* Implements {@link WindowInsetsController} on the client.
* @hide
*/
-public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks {
+public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks,
+ InsetsAnimationControlRunner.SurfaceParamsApplier {
private int mTypesBeingCancelled;
@@ -307,7 +307,6 @@
}
/** Not running an animation. */
- @VisibleForTesting
public static final int ANIMATION_TYPE_NONE = -1;
/** Running animation will show insets */
@@ -317,11 +316,9 @@
public static final int ANIMATION_TYPE_HIDE = 1;
/** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
- @VisibleForTesting(visibility = PACKAGE)
public static final int ANIMATION_TYPE_USER = 2;
/** Running animation will resize insets */
- @VisibleForTesting
public static final int ANIMATION_TYPE_RESIZE = 3;
@Retention(RetentionPolicy.SOURCE)
@@ -757,11 +754,9 @@
public InsetsController(Host host) {
this(host, (controller, id, type) -> {
if (!Flags.refactorInsetsController() && type == ime()) {
- return new ImeInsetsSourceConsumer(id, controller.mState,
- Transaction::new, controller);
+ return new ImeInsetsSourceConsumer(id, controller.mState, controller);
} else {
- return new InsetsSourceConsumer(id, type, controller.mState,
- Transaction::new, controller);
+ return new InsetsSourceConsumer(id, type, controller.mState, controller);
}
}, host.getHandler());
}
@@ -1525,9 +1520,15 @@
insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
mHost.getTranslator(), mHost.getHandler(), statsToken)
: new InsetsAnimationControlImpl(controls,
- frame, mState, listener, typesReady, this, insetsAnimationSpec,
+ frame, mState, listener, typesReady, this, this, insetsAnimationSpec,
animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
statsToken);
+ for (int i = controls.size() - 1; i >= 0; i--) {
+ final InsetsSourceConsumer consumer = mSourceConsumers.get(controls.keyAt(i));
+ if (consumer != null) {
+ consumer.setSurfaceParamsApplier(runner.getSurfaceParamsApplier());
+ }
+ }
if ((typesReady & WindowInsets.Type.ime()) != 0) {
ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
mHost.getInputMethodManager(), null /* icProto */);
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index f90b841..5262751 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -94,6 +94,11 @@
}
@Override
+ public SurfaceParamsApplier getSurfaceParamsApplier() {
+ return SurfaceParamsApplier.DEFAULT;
+ }
+
+ @Override
@Nullable
public ImeTracker.Token getStatsToken() {
// Return null as resizing the IME view is not explicitly tracked.
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 391d757..2e2ff1d 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -17,6 +17,7 @@
package android.view;
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsController.DEBUG;
import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE;
@@ -32,12 +33,13 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type.InsetsType;
import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
@@ -48,7 +50,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
-import java.util.function.Supplier;
/**
* Controls the visibility and animations of a single window insets source.
@@ -92,10 +93,12 @@
private final int mType;
private static final String TAG = "InsetsSourceConsumer";
- private final Supplier<Transaction> mTransactionSupplier;
@Nullable
private InsetsSourceControl mSourceControl;
private boolean mHasWindowFocus;
+ private InsetsAnimationControlRunner.SurfaceParamsApplier mSurfaceParamsApplier =
+ InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT;
+ private final Matrix mTmpMatrix = new Matrix();
/**
* Whether the view has focus returned by {@link #onWindowFocusGained(boolean)}.
@@ -108,16 +111,13 @@
* @param id The ID of the consumed insets.
* @param type The {@link InsetsType} of the consumed insets.
* @param state The current {@link InsetsState} of the consumed insets.
- * @param transactionSupplier The source of new {@link Transaction} instances. The supplier
- * must provide *new* instances, which will be explicitly closed by this class.
* @param controller The {@link InsetsController} to use for insets interaction.
*/
public InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state,
- Supplier<Transaction> transactionSupplier, InsetsController controller) {
+ InsetsController controller) {
mId = id;
mType = type;
mState = state;
- mTransactionSupplier = transactionSupplier;
mController = controller;
}
@@ -162,6 +162,9 @@
if (localVisible != serverVisible) {
mController.notifyVisibilityChanged();
}
+
+ // Reset the applier to the default one which has the most lightweight implementation.
+ setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT);
} else {
final boolean requestedVisible = isRequestedVisibleAwaitingControl();
final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null;
@@ -184,10 +187,11 @@
mController.notifyVisibilityChanged();
}
- // If we have a new leash, make sure visibility is up-to-date, even though we
- // didn't want to run an animation above.
- if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) {
- applyRequestedVisibilityToControl();
+ // If there is no animation controlling the leash, make sure the visibility and the
+ // position is up-to-date. Note: ANIMATION_TYPE_RESIZE doesn't control the leash.
+ final int animType = mController.getAnimationType(mType);
+ if (animType == ANIMATION_TYPE_NONE || animType == ANIMATION_TYPE_RESIZE) {
+ applyRequestedVisibilityAndPositionToControl();
}
// Remove the surface that owned by last control when it lost.
@@ -229,6 +233,15 @@
}
/**
+ * Sets the SurfaceParamsApplier that the latest animation runner is using. The leash owned by
+ * this class is always applied by the applier, so that the transaction order can always be
+ * aligned with the calling sequence.
+ */
+ void setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier applier) {
+ mSurfaceParamsApplier = applier;
+ }
+
+ /**
* Called right after the animation is started or finished.
*/
@VisibleForTesting(visibility = PACKAGE)
@@ -431,24 +444,30 @@
if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
}
- private void applyRequestedVisibilityToControl() {
- if (mSourceControl == null || mSourceControl.getLeash() == null) {
+ private void applyRequestedVisibilityAndPositionToControl() {
+ if (mSourceControl == null) {
+ return;
+ }
+ final SurfaceControl leash = mSourceControl.getLeash();
+ if (leash == null) {
return;
}
- final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
- try (Transaction t = mTransactionSupplier.get()) {
- if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible);
- if (requestedVisible) {
- t.show(mSourceControl.getLeash());
- } else {
- t.hide(mSourceControl.getLeash());
- }
- // Ensure the alpha value is aligned with the actual requested visibility.
- t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0);
- t.apply();
- }
- onPerceptible(requestedVisible);
+ final boolean visible = (mController.getRequestedVisibleTypes() & mType) != 0;
+ final Point surfacePosition = mSourceControl.getSurfacePosition();
+
+ if (DEBUG) Log.d(TAG, "applyRequestedVisibilityAndPositionToControl: visible=" + visible
+ + " position=" + surfacePosition);
+
+ mTmpMatrix.setTranslate(surfacePosition.x, surfacePosition.y);
+ mSurfaceParamsApplier.applySurfaceParams(
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(leash)
+ .withVisibility(visible)
+ .withAlpha(visible ? 1 : 0)
+ .withMatrix(mTmpMatrix)
+ .build());
+
+ onPerceptible(visible);
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
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/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index e1402f8..b6aad11 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -266,3 +266,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "remove_starting_window_wait_for_multi_transitions"
+ namespace: "windowing_frontend"
+ description: "Avoid remove starting window too early when playing multiple transitions"
+ bug: "362347290"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/os/anr/OWNERS b/core/java/com/android/internal/os/anr/OWNERS
index 9816752..1ad642f 100644
--- a/core/java/com/android/internal/os/anr/OWNERS
+++ b/core/java/com/android/internal/os/anr/OWNERS
@@ -1,3 +1,2 @@
benmiles@google.com
-gaillard@google.com
mohamadmahmoud@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index fbc058c..b0e38e2 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -122,18 +122,20 @@
private final Lock mBackgroundServiceLock = new ReentrantLock();
private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
- public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) {
+ public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups)
+ throws ServiceManager.ServiceNotFoundException {
this(null, null, null, () -> {}, groups);
}
- public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) {
+ public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups)
+ throws ServiceManager.ServiceNotFoundException {
this(null, null, null, cacheUpdater, groups);
}
public PerfettoProtoLogImpl(
@NonNull String viewerConfigFilePath,
@NonNull Runnable cacheUpdater,
- @NonNull IProtoLogGroup[] groups) {
+ @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
this(viewerConfigFilePath,
null,
new ProtoLogViewerConfigReader(() -> {
@@ -177,12 +179,14 @@
@Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
@Nullable ProtoLogViewerConfigReader viewerConfigReader,
@NonNull Runnable cacheUpdater,
- @NonNull IProtoLogGroup[] groups) {
+ @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
this(viewerConfigFilePath, viewerConfigInputStreamProvider, viewerConfigReader,
cacheUpdater, groups,
ProtoLogDataSource::new,
- IProtoLogConfigurationService.Stub
- .asInterface(ServiceManager.getService(PROTOLOG_CONFIGURATION_SERVICE))
+ android.tracing.Flags.clientSideProtoLogging() ?
+ IProtoLogConfigurationService.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(PROTOLOG_CONFIGURATION_SERVICE)
+ ) : null
);
}
@@ -222,7 +226,7 @@
if (android.tracing.Flags.clientSideProtoLogging()) {
mProtoLogConfigurationService = configurationService;
Objects.requireNonNull(mProtoLogConfigurationService,
- "ServiceManager returned a null ProtoLog Configuration Service");
+ "A null ProtoLog Configuration Service was provided!");
try {
var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs();
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index bf77db7..adf03fe 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -16,6 +16,8 @@
package com.android.internal.protolog;
+import android.os.ServiceManager;
+
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
@@ -76,7 +78,11 @@
groups = allGroups.toArray(new IProtoLogGroup[0]);
}
- sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ try {
+ sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new RuntimeException(e);
+ }
}
} else {
sProtoLogInstance = new LogcatOnlyProtoLogImpl();
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 7bdcf2d..5d67534 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -23,6 +23,7 @@
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
import android.annotation.Nullable;
+import android.os.ServiceManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -106,18 +107,23 @@
final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
if (android.tracing.Flags.perfettoProtologTracing()) {
- File f = new File(sViewerConfigPath);
- if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
- // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
- // In some tests the viewer config file might not exist in which we don't
- // want to provide config path to the user
- Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
- + ProtoLogImpl.class.getSimpleName() + ". "
- + "Setting up without a viewer config instead...");
- sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
- } else {
- sServiceInstance =
- new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+ try {
+ File f = new File(sViewerConfigPath);
+ if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
+ // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
+ // In some tests the viewer config file might not exist in which we don't
+ // want to provide config path to the user
+ Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
+ + ProtoLogImpl.class.getSimpleName() + ". "
+ + "Setting up without a viewer config instead...");
+
+ sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
+ } else {
+ sServiceInstance =
+ new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+ }
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new RuntimeException(e);
}
} else {
var protologImpl = new LegacyProtoLogImpl(
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index e65b4b6..c0a7383 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -16,14 +16,19 @@
package com.android.internal.widget;
+import static java.lang.Float.NaN;
+
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Path;
+import android.graphics.PorterDuff;
import android.graphics.RectF;
import android.graphics.Region;
import android.hardware.input.InputManager;
@@ -65,11 +70,14 @@
private static final PointerState EMPTY_POINTER_STATE = new PointerState();
public static class PointerState {
- // Trace of previous points.
- private float[] mTraceX = new float[32];
- private float[] mTraceY = new float[32];
- private boolean[] mTraceCurrent = new boolean[32];
- private int mTraceCount;
+ private float mCurrentX = NaN;
+ private float mCurrentY = NaN;
+ private float mPreviousX = NaN;
+ private float mPreviousY = NaN;
+ private float mFirstX = NaN;
+ private float mFirstY = NaN;
+ private boolean mPreviousPointIsHistorical;
+ private boolean mCurrentPointIsHistorical;
// True if the pointer is down.
@UnsupportedAppUsage
@@ -96,31 +104,20 @@
public PointerState() {
}
- public void clearTrace() {
- mTraceCount = 0;
- }
-
- public void addTrace(float x, float y, boolean current) {
- int traceCapacity = mTraceX.length;
- if (mTraceCount == traceCapacity) {
- traceCapacity *= 2;
- float[] newTraceX = new float[traceCapacity];
- System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount);
- mTraceX = newTraceX;
-
- float[] newTraceY = new float[traceCapacity];
- System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount);
- mTraceY = newTraceY;
-
- boolean[] newTraceCurrent = new boolean[traceCapacity];
- System.arraycopy(mTraceCurrent, 0, newTraceCurrent, 0, mTraceCount);
- mTraceCurrent= newTraceCurrent;
+ public void addTrace(float x, float y, boolean isHistorical) {
+ if (Float.isNaN(mFirstX)) {
+ mFirstX = x;
+ }
+ if (Float.isNaN(mFirstY)) {
+ mFirstY = y;
}
- mTraceX[mTraceCount] = x;
- mTraceY[mTraceCount] = y;
- mTraceCurrent[mTraceCount] = current;
- mTraceCount += 1;
+ mPreviousX = mCurrentX;
+ mPreviousY = mCurrentY;
+ mCurrentX = x;
+ mCurrentY = y;
+ mPreviousPointIsHistorical = mCurrentPointIsHistorical;
+ mCurrentPointIsHistorical = isHistorical;
}
}
@@ -149,6 +146,12 @@
private final SparseArray<PointerState> mPointers = new SparseArray<PointerState>();
private final PointerCoords mTempCoords = new PointerCoords();
+ // Draw the trace of all pointers in the current gesture in a separate layer
+ // that is not cleared on every frame so that we don't have to re-draw the
+ // entire trace on each frame.
+ private final Bitmap mTraceBitmap;
+ private final Canvas mTraceCanvas;
+
private final Region mSystemGestureExclusion = new Region();
private final Region mSystemGestureExclusionRejected = new Region();
private final Path mSystemGestureExclusionPath = new Path();
@@ -197,6 +200,10 @@
mPathPaint.setARGB(255, 0, 96, 255);
mPathPaint.setStyle(Paint.Style.STROKE);
+ mTraceBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics().widthPixels,
+ getResources().getDisplayMetrics().heightPixels, Bitmap.Config.ARGB_8888);
+ mTraceCanvas = new Canvas(mTraceBitmap);
+
configureDensityDependentFactors();
mSystemGestureExclusionPaint = new Paint();
@@ -256,7 +263,7 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mTextPaint.getFontMetricsInt(mTextMetrics);
- mHeaderBottom = mHeaderPaddingTop-mTextMetrics.ascent+mTextMetrics.descent+2;
+ mHeaderBottom = mHeaderPaddingTop - mTextMetrics.ascent + mTextMetrics.descent + 2;
if (false) {
Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent
+ " descent=" + mTextMetrics.descent
@@ -269,6 +276,7 @@
// Draw an oval. When angle is 0 radians, orients the major axis vertically,
// angles less than or greater than 0 radians rotate the major axis left or right.
private RectF mReusableOvalRect = new RectF();
+
private void drawOval(Canvas canvas, float x, float y, float major, float minor,
float angle, Paint paint) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
@@ -285,6 +293,8 @@
protected void onDraw(Canvas canvas) {
final int NP = mPointers.size();
+ canvas.drawBitmap(mTraceBitmap, 0, 0, null);
+
if (!mSystemGestureExclusion.isEmpty()) {
mSystemGestureExclusionPath.reset();
mSystemGestureExclusion.getBoundaryPath(mSystemGestureExclusionPath);
@@ -303,32 +313,9 @@
// Pointer trace.
for (int p = 0; p < NP; p++) {
final PointerState ps = mPointers.valueAt(p);
+ float lastX = ps.mCurrentX, lastY = ps.mCurrentY;
- // Draw path.
- final int N = ps.mTraceCount;
- float lastX = 0, lastY = 0;
- boolean haveLast = false;
- boolean drawn = false;
- mPaint.setARGB(255, 128, 255, 255);
- for (int i=0; i < N; i++) {
- float x = ps.mTraceX[i];
- float y = ps.mTraceY[i];
- if (Float.isNaN(x) || Float.isNaN(y)) {
- haveLast = false;
- continue;
- }
- if (haveLast) {
- canvas.drawLine(lastX, lastY, x, y, mPathPaint);
- final Paint paint = ps.mTraceCurrent[i - 1] ? mCurrentPointPaint : mPaint;
- canvas.drawPoint(lastX, lastY, paint);
- drawn = true;
- }
- lastX = x;
- lastY = y;
- haveLast = true;
- }
-
- if (drawn) {
+ if (!Float.isNaN(lastX) && !Float.isNaN(lastY)) {
// Draw velocity vector.
mPaint.setARGB(255, 255, 64, 128);
float xVel = ps.mXVelocity * (1000 / 60);
@@ -353,7 +340,7 @@
Math.max(getHeight(), getWidth()), mTargetPaint);
// Draw current point.
- int pressureLevel = (int)(ps.mCoords.pressure * 255);
+ int pressureLevel = (int) (ps.mCoords.pressure * 255);
mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel);
canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint);
@@ -424,8 +411,7 @@
.append(" / ").append(mMaxNumPointers)
.toString(), 1, base, mTextPaint);
- final int count = ps.mTraceCount;
- if ((mCurDown && ps.mCurDown) || count == 0) {
+ if ((mCurDown && ps.mCurDown) || Float.isNaN(ps.mCurrentX)) {
canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
mTextBackgroundPaint);
canvas.drawText(mText.clear()
@@ -437,8 +423,8 @@
.append("Y: ").append(ps.mCoords.y, 1)
.toString(), 1 + itemW * 2, base, mTextPaint);
} else {
- float dx = ps.mTraceX[count - 1] - ps.mTraceX[0];
- float dy = ps.mTraceY[count - 1] - ps.mTraceY[0];
+ float dx = ps.mCurrentX - ps.mFirstX;
+ float dy = ps.mCurrentY - ps.mFirstY;
canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom,
Math.abs(dx) < mVC.getScaledTouchSlop()
? mTextBackgroundPaint : mTextLevelPaint);
@@ -565,9 +551,9 @@
.append(" TouchMinor=").append(coords.touchMinor, 3)
.append(" ToolMajor=").append(coords.toolMajor, 3)
.append(" ToolMinor=").append(coords.toolMinor, 3)
- .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
+ .append(" Orientation=").append((float) (coords.orientation * 180 / Math.PI), 1)
.append("deg")
- .append(" Tilt=").append((float)(
+ .append(" Tilt=").append((float) (
coords.getAxisValue(MotionEvent.AXIS_TILT) * 180 / Math.PI), 1)
.append("deg")
.append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1)
@@ -598,6 +584,7 @@
mCurNumPointers = 0;
mMaxNumPointers = 0;
mVelocity.clear();
+ mTraceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
if (mAltVelocity != null) {
mAltVelocity.clear();
}
@@ -646,7 +633,8 @@
logCoords("Pointer", action, i, coords, id, event);
}
if (ps != null) {
- ps.addTrace(coords.x, coords.y, false);
+ ps.addTrace(coords.x, coords.y, true);
+ updateDrawTrace(ps);
}
}
}
@@ -659,7 +647,8 @@
logCoords("Pointer", action, i, coords, id, event);
}
if (ps != null) {
- ps.addTrace(coords.x, coords.y, true);
+ ps.addTrace(coords.x, coords.y, false);
+ updateDrawTrace(ps);
ps.mXVelocity = mVelocity.getXVelocity(id);
ps.mYVelocity = mVelocity.getYVelocity(id);
if (mAltVelocity != null) {
@@ -702,13 +691,26 @@
if (mActivePointerId == id) {
mActivePointerId = event.getPointerId(index == 0 ? 1 : 0);
}
- ps.addTrace(Float.NaN, Float.NaN, false);
+ ps.addTrace(Float.NaN, Float.NaN, true);
}
}
invalidate();
}
+ private void updateDrawTrace(PointerState ps) {
+ mPaint.setARGB(255, 128, 255, 255);
+ float x = ps.mCurrentX;
+ float y = ps.mCurrentY;
+ float lastX = ps.mPreviousX;
+ float lastY = ps.mPreviousY;
+ if (!Float.isNaN(x) && !Float.isNaN(y) && !Float.isNaN(lastX) && !Float.isNaN(lastY)) {
+ mTraceCanvas.drawLine(lastX, lastY, x, y, mPathPaint);
+ Paint paint = ps.mPreviousPointIsHistorical ? mPaint : mCurrentPointPaint;
+ mTraceCanvas.drawPoint(lastX, lastY, paint);
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
onPointerEvent(event);
@@ -767,7 +769,7 @@
return true;
default:
return KeyEvent.isGamepadButton(keyCode)
- || KeyEvent.isModifierKey(keyCode);
+ || KeyEvent.isModifierKey(keyCode);
}
}
@@ -887,7 +889,7 @@
public FasterStringBuilder append(int value, int zeroPadWidth) {
final boolean negative = value < 0;
if (negative) {
- value = - value;
+ value = -value;
if (value < 0) {
append("-2147483648");
return this;
@@ -973,26 +975,27 @@
private ISystemGestureExclusionListener mSystemGestureExclusionListener =
new ISystemGestureExclusionListener.Stub() {
- @Override
- public void onSystemGestureExclusionChanged(int displayId, Region systemGestureExclusion,
- Region systemGestureExclusionUnrestricted) {
- Region exclusion = Region.obtain(systemGestureExclusion);
- Region rejected = Region.obtain();
- if (systemGestureExclusionUnrestricted != null) {
- rejected.set(systemGestureExclusionUnrestricted);
- rejected.op(exclusion, Region.Op.DIFFERENCE);
- }
- Handler handler = getHandler();
- if (handler != null) {
- handler.post(() -> {
- mSystemGestureExclusion.set(exclusion);
- mSystemGestureExclusionRejected.set(rejected);
- exclusion.recycle();
- invalidate();
- });
- }
- }
- };
+ @Override
+ public void onSystemGestureExclusionChanged(int displayId,
+ Region systemGestureExclusion,
+ Region systemGestureExclusionUnrestricted) {
+ Region exclusion = Region.obtain(systemGestureExclusion);
+ Region rejected = Region.obtain();
+ if (systemGestureExclusionUnrestricted != null) {
+ rejected.set(systemGestureExclusionUnrestricted);
+ rejected.op(exclusion, Region.Op.DIFFERENCE);
+ }
+ Handler handler = getHandler();
+ if (handler != null) {
+ handler.post(() -> {
+ mSystemGestureExclusion.set(exclusion);
+ mSystemGestureExclusionRejected.set(rejected);
+ exclusion.recycle();
+ invalidate();
+ });
+ }
+ }
+ };
@Override
protected void onConfigurationChanged(Configuration newConfig) {
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/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 69437b4..9854030 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -287,6 +287,11 @@
<string name="config_satellite_demo_mode_sos_intent_action" translatable="false"></string>
<java-symbol type="string" name="config_satellite_demo_mode_sos_intent_action" />
+ <!-- The action of the intent that hidden menu sends to the app to launch esp loopback test mode
+ for sos emergency messaging via satellite. -->
+ <string name="config_satellite_test_with_esp_replies_intent_action" translatable="false"></string>
+ <java-symbol type="string" name="config_satellite_test_with_esp_replies_intent_action" />
+
<!-- Whether outgoing satellite datagrams should be sent to modem in demo mode. When satellite
is enabled for demo mode, if this config is enabled, outgoing datagrams will be sent to
modem; otherwise, success results will be returned. If demo mode is disabled, outgoing
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/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 0837b45..0f73df9 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -37,6 +37,7 @@
import static android.app.Notification.EXTRA_PICTURE_ICON;
import static android.app.Notification.EXTRA_SUMMARY_TEXT;
import static android.app.Notification.EXTRA_TITLE;
+import static android.app.Notification.FLAG_CAN_COLORIZE;
import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.GROUP_ALERT_SUMMARY;
import static android.app.Notification.GROUP_KEY_SILENT;
@@ -96,6 +97,7 @@
import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
import android.util.Pair;
+import android.util.Slog;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
@@ -126,6 +128,8 @@
private Context mContext;
+ private RemoteViews mRemoteViews;
+
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -133,23 +137,25 @@
@Before
public void setUp() {
mContext = InstrumentationRegistry.getContext();
+ mRemoteViews = new RemoteViews(
+ mContext.getPackageName(), R.layout.notification_template_header);
}
@Test
public void testColorizedByPermission() {
Notification n = new Notification.Builder(mContext, "test")
- .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+ .setFlag(FLAG_CAN_COLORIZE, true)
.setColorized(true).setColor(Color.WHITE)
.build();
assertTrue(n.isColorized());
n = new Notification.Builder(mContext, "test")
- .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+ .setFlag(FLAG_CAN_COLORIZE, true)
.build();
assertFalse(n.isColorized());
n = new Notification.Builder(mContext, "test")
- .setFlag(Notification.FLAG_CAN_COLORIZE, false)
+ .setFlag(FLAG_CAN_COLORIZE, false)
.setColorized(true).setColor(Color.WHITE)
.build();
assertFalse(n.isColorized());
@@ -215,6 +221,275 @@
}
@Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasTitle_noStyle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setContentTitle("TITLE")
+ .build();
+ assertThat(n.hasTitle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasTitle_bigText() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .build();
+ assertThat(n.hasTitle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasTitle_noTitle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setContentText("text not title")
+ .build();
+ assertThat(n.hasTitle()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_none() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_content() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomContentView(mRemoteViews)
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_big() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomBigContentView(mRemoteViews)
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_headsUp() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomHeadsUpContentView(mRemoteViews)
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_content_public() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("public")
+ .setCustomContentView(mRemoteViews)
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_big_public() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomBigContentView(mRemoteViews)
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_headsUp_public() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomHeadsUpContentView(mRemoteViews)
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_noStyle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .build();
+ assertThat(n.hasPromotableStyle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_bigPicture() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigPictureStyle())
+ .build();
+ assertThat(n.hasPromotableStyle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_bigText() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle())
+ .build();
+ assertThat(n.hasPromotableStyle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_no_messagingStyle() {
+ Notification.MessagingStyle style = new Notification.MessagingStyle("self name")
+ .setGroupConversation(true)
+ .setConversationTitle("test conversation title");
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(style)
+ .build();
+ assertThat(n.hasPromotableStyle()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_no_mediaStyle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.MediaStyle())
+ .build();
+ assertThat(n.hasPromotableStyle()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_no_inboxStyle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.InboxStyle())
+ .build();
+ assertThat(n.hasPromotableStyle()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_callText() {
+ PendingIntent answerIntent = createPendingIntent("answer");
+ PendingIntent declineIntent = createPendingIntent("decline");
+ Notification.CallStyle style = Notification.CallStyle.forIncomingCall(
+ new Person.Builder().setName("A Caller").build(),
+ declineIntent,
+ answerIntent
+ );
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(style)
+ .build();
+ assertThat(n.hasPromotableStyle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableCharacteristics() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true)
+ .build();
+ assertThat(n.hasPromotableCharacteristics()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableCharacteristics_wrongStyle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.InboxStyle())
+ .setContentTitle("TITLE")
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true)
+ .build();
+ assertThat(n.hasPromotableCharacteristics()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableCharacteristics_notColorized() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .build();
+ assertThat(n.hasPromotableCharacteristics()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableCharacteristics_noTitle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle())
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true)
+ .build();
+ assertThat(n.hasPromotableCharacteristics()).isFalse();
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void testGetShortCriticalText_noneSet() {
Notification n = new Notification.Builder(mContext, "test")
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 786f1e8..ba6f62c 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -38,7 +38,6 @@
import android.graphics.RectF;
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
-import android.view.SurfaceControl.Transaction;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
@@ -79,7 +78,6 @@
private SurfaceControl mNavLeash;
private InsetsState mInsetsState;
- @Mock Transaction mMockTransaction;
@Mock InsetsController mMockController;
@Mock WindowInsetsAnimationControlListener mMockListener;
@@ -98,16 +96,14 @@
mInsetsState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
.setFrame(new Rect(400, 0, 500, 500));
InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ID_STATUS_BAR,
- WindowInsets.Type.statusBars(), mInsetsState,
- () -> mMockTransaction, mMockController);
+ WindowInsets.Type.statusBars(), mInsetsState, mMockController);
topConsumer.setControl(
new InsetsSourceControl(ID_STATUS_BAR, WindowInsets.Type.statusBars(),
mStatusLeash, true, new Point(0, 0), Insets.of(0, 100, 0, 0)),
new int[1], new int[1]);
InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ID_NAVIGATION_BAR,
- WindowInsets.Type.navigationBars(), mInsetsState,
- () -> mMockTransaction, mMockController);
+ WindowInsets.Type.navigationBars(), mInsetsState, mMockController);
navConsumer.setControl(
new InsetsSourceControl(ID_NAVIGATION_BAR, WindowInsets.Type.navigationBars(),
mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)),
@@ -131,8 +127,9 @@
mController = new InsetsAnimationControlImpl(controls,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
- mMockController, spec /* insetsAnimationSpecCreator */, 0 /* animationType */,
- 0 /* layoutInsetsDuringAnimation */, null /* translator */, null /* statsToken */);
+ mMockController, mMockController, spec /* insetsAnimationSpecCreator */,
+ 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */,
+ null /* statsToken */);
mController.setReadyDispatched(true);
}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index bec8b1f..4516e9c 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -63,7 +63,6 @@
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.platform.test.annotations.Presubmit;
-import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
import android.view.WindowManager.BadTokenException;
@@ -138,8 +137,7 @@
mTestHost = spy(new TestHost(mViewRoot));
mController = new InsetsController(mTestHost, (controller, id, type) -> {
if (!Flags.refactorInsetsController() && type == ime()) {
- return new InsetsSourceConsumer(id, type, controller.getState(),
- Transaction::new, controller) {
+ return new InsetsSourceConsumer(id, type, controller.getState(), controller) {
private boolean mImeRequestedShow;
@@ -155,8 +153,7 @@
}
};
} else {
- return new InsetsSourceConsumer(id, type, controller.getState(),
- Transaction::new, controller);
+ return new InsetsSourceConsumer(id, type, controller.getState(), controller);
}
}, mTestHandler);
final Rect rect = new Rect(5, 5, 5, 5);
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 655cb45..d6d45e8 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -28,10 +28,7 @@
import static junit.framework.TestCase.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.app.Instrumentation;
import android.content.Context;
@@ -39,7 +36,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.view.SurfaceControl.Transaction;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.ImeTracker;
@@ -51,7 +47,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -75,9 +70,9 @@
private SurfaceSession mSession = new SurfaceSession();
private SurfaceControl mLeash;
- @Mock Transaction mMockTransaction;
private InsetsSource mSpyInsetsSource;
private boolean mRemoveSurfaceCalled = false;
+ private boolean mSurfaceParamsApplied = false;
private InsetsController mController;
private InsetsState mState;
private ViewRootImpl mViewRoot;
@@ -102,9 +97,14 @@
mSpyInsetsSource = Mockito.spy(new InsetsSource(ID_STATUS_BAR, statusBars()));
mState.addSource(mSpyInsetsSource);
- mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot));
- mConsumer = new InsetsSourceConsumer(ID_STATUS_BAR, statusBars(), mState,
- () -> mMockTransaction, mController) {
+ mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot)) {
+ @Override
+ public void applySurfaceParams(
+ final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+ mSurfaceParamsApplied = true;
+ }
+ };
+ mConsumer = new InsetsSourceConsumer(ID_STATUS_BAR, statusBars(), mState, mController) {
@Override
public void removeSurface() {
super.removeSurface();
@@ -148,8 +148,7 @@
InsetsState state = new InsetsState();
InsetsController controller = new InsetsController(new ViewRootInsetsControllerHost(
mViewRoot));
- InsetsSourceConsumer consumer = new InsetsSourceConsumer(
- ID_IME, ime(), state, null, controller);
+ InsetsSourceConsumer consumer = new InsetsSourceConsumer(ID_IME, ime(), state, controller);
InsetsSource source = new InsetsSource(ID_IME, ime());
source.setFrame(0, 1, 2, 3);
@@ -182,9 +181,9 @@
public void testRestore() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mConsumer.setControl(null, new int[1], new int[1]);
- reset(mMockTransaction);
+ mSurfaceParamsApplied = false;
mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars());
- verifyZeroInteractions(mMockTransaction);
+ assertFalse(mSurfaceParamsApplied);
int[] hideTypes = new int[1];
mConsumer.setControl(
new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
@@ -200,8 +199,9 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars());
mConsumer.setControl(null, new int[1], new int[1]);
- reset(mMockTransaction);
- verifyZeroInteractions(mMockTransaction);
+ mLeash = new SurfaceControl.Builder(mSession)
+ .setName("testSurface")
+ .build();
mRemoveSurfaceCalled = false;
int[] hideTypes = new int[1];
mConsumer.setControl(
@@ -221,8 +221,7 @@
ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
InsetsController insetsController = new InsetsController(host, (ic, id, type) -> {
if (type == ime()) {
- return new InsetsSourceConsumer(ID_IME, ime(), state,
- () -> mMockTransaction, ic) {
+ return new InsetsSourceConsumer(ID_IME, ime(), state, ic) {
@Override
public int requestShow(boolean fromController,
ImeTracker.Token statsToken) {
@@ -230,14 +229,14 @@
}
};
}
- return new InsetsSourceConsumer(id, type, ic.getState(), Transaction::new, ic);
+ return new InsetsSourceConsumer(id, type, ic.getState(), ic);
}, host.getHandler());
InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ID_IME, ime());
// Initial IME insets source control with its leash.
imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
- reset(mMockTransaction);
+ mSurfaceParamsApplied = false;
// Verify when the app requests controlling show IME animation, the IME leash
// visibility won't be updated when the consumer received the same leash in setControl.
@@ -246,7 +245,7 @@
assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime()));
imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
- verify(mMockTransaction, never()).show(mLeash);
+ assertFalse(mSurfaceParamsApplied);
});
}
}
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/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index e07471c..6d31578 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -128,22 +128,6 @@
private static final WeakHashMap<Bitmap, Void> sAllBitmaps = new WeakHashMap<>();
/**
- * @hide
- */
- private static NativeAllocationRegistry getRegistry(boolean malloc, long size) {
- final long free = nativeGetNativeFinalizer();
- if (com.android.libcore.Flags.nativeMetrics()) {
- Class cls = Bitmap.class;
- return malloc ? NativeAllocationRegistry.createMalloced(cls, free, size)
- : NativeAllocationRegistry.createNonmalloced(cls, free, size);
- } else {
- ClassLoader loader = Bitmap.class.getClassLoader();
- return malloc ? NativeAllocationRegistry.createMalloced(loader, free, size)
- : NativeAllocationRegistry.createNonmalloced(loader, free, size);
- }
- }
-
- /**
* Private constructor that must receive an already allocated native bitmap
* int (pointer).
*/
@@ -167,6 +151,7 @@
mWidth = width;
mHeight = height;
mRequestPremultiplied = requestPremultiplied;
+
mNinePatchChunk = ninePatchChunk;
mNinePatchInsets = ninePatchInsets;
if (density >= 0) {
@@ -174,9 +159,17 @@
}
mNativePtr = nativeBitmap;
- final int allocationByteCount = getAllocationByteCount();
- getRegistry(fromMalloc, allocationByteCount).registerNativeAllocation(this, mNativePtr);
+ final int allocationByteCount = getAllocationByteCount();
+ NativeAllocationRegistry registry;
+ if (fromMalloc) {
+ registry = NativeAllocationRegistry.createMalloced(
+ Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
+ } else {
+ registry = NativeAllocationRegistry.createNonmalloced(
+ Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
+ }
+ registry.registerNativeAllocation(this, nativeBitmap);
synchronized (Bitmap.class) {
sAllBitmaps.put(this, null);
}
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/Android.bp b/libs/WindowManager/Shell/Android.bp
index 94809f2..f857429 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -147,8 +147,10 @@
java_library {
name: "WindowManager-Shell-lite-proto",
- srcs: ["src/com/android/wm/shell/desktopmode/education/data/proto/**/*.proto"],
-
+ srcs: [
+ "src/com/android/wm/shell/desktopmode/education/data/proto/**/*.proto",
+ "src/com/android/wm/shell/desktopmode/persistence/*.proto",
+ ],
proto: {
type: "lite",
},
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
index c0ff192..1d1cdfa 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml
@@ -28,6 +28,8 @@
android:layout_height="@dimen/desktop_mode_fullscreen_decor_caption_height"
android:paddingVertical="16dp"
android:paddingHorizontal="10dp"
+ android:screenReaderFocusable="true"
+ android:importantForAccessibility="yes"
android:contentDescription="@string/handle_text"
android:src="@drawable/decor_handle_dark"
tools:tint="@color/desktop_mode_caption_handle_bar_dark"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
index 7dcb3c2..3dbf754 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml
@@ -31,14 +31,16 @@
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
+ android:contentDescription="@string/desktop_mode_app_header_chip_text"
android:layout_marginStart="12dp">
<ImageView
android:id="@+id/application_icon"
android:layout_width="@dimen/desktop_mode_caption_icon_radius"
android:layout_height="@dimen/desktop_mode_caption_icon_radius"
android:layout_gravity="center_vertical"
- android:contentDescription="@string/app_icon_text"
android:layout_marginStart="6dp"
+ android:clickable="false"
+ android:focusable="false"
android:scaleType="centerCrop"/>
<TextView
@@ -53,18 +55,22 @@
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:layout_marginStart="8dp"
+ android:clickable="false"
+ android:focusable="false"
tools:text="Gmail"/>
<ImageButton
android:id="@+id/expand_menu_button"
android:layout_width="16dp"
android:layout_height="16dp"
- android:contentDescription="@string/expand_menu_text"
android:src="@drawable/ic_baseline_expand_more_24"
android:background="@null"
android:scaleType="fitCenter"
android:clickable="false"
android:focusable="false"
+ android:screenReaderFocusable="false"
+ android:importantForAccessibility="no"
+ android:contentDescription="@null"
android:layout_marginHorizontal="8dp"
android:layout_gravity="center_vertical"/>
@@ -90,6 +96,7 @@
<com.android.wm.shell.windowdecor.MaximizeButtonView
android:id="@+id/maximize_button_view"
+ android:importantForAccessibility="no"
android:layout_width="44dp"
android:layout_height="40dp"
android:layout_gravity="end"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 64f71c7..6913e54 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -43,13 +43,15 @@
android:layout_height="@dimen/desktop_mode_caption_icon_radius"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
- android:contentDescription="@string/app_icon_text"/>
+ android:contentDescription="@string/app_icon_text"
+ android:importantForAccessibility="no"/>
<TextView
android:id="@+id/application_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
tools:text="Gmail"
+ android:importantForAccessibility="no"
android:textColor="?androidprv:attr/materialColorOnSurface"
android:textSize="14sp"
android:textFontWeight="500"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
index 5fe3f2a..35ef239 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -41,6 +41,8 @@
android:id="@+id/maximize_menu_maximize_button"
style="?android:attr/buttonBarButtonStyle"
android:stateListAnimator="@null"
+ android:importantForAccessibility="yes"
+ android:contentDescription="@string/desktop_mode_maximize_menu_maximize_button_text"
android:layout_marginRight="8dp"
android:layout_marginBottom="4dp"
android:alpha="0"/>
@@ -53,6 +55,7 @@
android:layout_marginBottom="76dp"
android:gravity="center"
android:fontFamily="google-sans-text"
+ android:importantForAccessibility="no"
android:text="@string/desktop_mode_maximize_menu_maximize_text"
android:textColor="?androidprv:attr/materialColorOnSurface"
android:alpha="0"/>
@@ -78,6 +81,8 @@
android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
android:layout_marginRight="4dp"
android:background="@drawable/desktop_mode_maximize_menu_button_background"
+ android:importantForAccessibility="yes"
+ android:contentDescription="@string/desktop_mode_maximize_menu_snap_left_button_text"
android:stateListAnimator="@null"/>
<Button
@@ -86,6 +91,8 @@
android:layout_width="41dp"
android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
android:background="@drawable/desktop_mode_maximize_menu_button_background"
+ android:importantForAccessibility="yes"
+ android:contentDescription="@string/desktop_mode_maximize_menu_snap_right_button_text"
android:stateListAnimator="@null"/>
</LinearLayout>
<TextView
@@ -96,6 +103,7 @@
android:layout_marginBottom="76dp"
android:layout_gravity="center"
android:gravity="center"
+ android:importantForAccessibility="no"
android:fontFamily="google-sans-text"
android:text="@string/desktop_mode_maximize_menu_snap_text"
android:textColor="?androidprv:attr/materialColorOnSurface"
diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
index cf1b894..b734d2d 100644
--- a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
+++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
@@ -19,7 +19,8 @@
<FrameLayout
android:layout_width="44dp"
- android:layout_height="40dp">
+ android:layout_height="40dp"
+ android:importantForAccessibility="noHideDescendants">
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index a6da421..bda5686 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -300,12 +300,19 @@
<string name="close_text">Close</string>
<!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
<string name="collapse_menu_text">Close Menu</string>
- <!-- Accessibility text for the handle menu open menu button [CHAR LIMIT=NONE] -->
- <string name="expand_menu_text">Open Menu</string>
+ <!-- Accessibility text for the App Header's App Chip [CHAR LIMIT=NONE] -->
+ <string name="desktop_mode_app_header_chip_text">Open Menu</string>
<!-- Maximize menu maximize button string. -->
<string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string>
<!-- Maximize menu snap buttons string. -->
<string name="desktop_mode_maximize_menu_snap_text">Snap Screen</string>
<!-- Snap resizing non-resizable string. -->
<string name="desktop_mode_non_resizable_snap_text">This app can\'t be resized</string>
+ <!-- Accessibility text for the Maximize Menu's maximize button [CHAR LIMIT=NONE] -->
+ <string name="desktop_mode_maximize_menu_maximize_button_text">Maximize</string>
+ <!-- Accessibility text for the Maximize Menu's snap left button [CHAR LIMIT=NONE] -->
+ <string name="desktop_mode_maximize_menu_snap_left_button_text">Snap left</string>
+ <!-- Accessibility text for the Maximize Menu's snap right button [CHAR LIMIT=NONE] -->
+ <string name="desktop_mode_maximize_menu_snap_right_button_text">Snap right</string>
+
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 7e6f434..4607a8e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -584,7 +584,8 @@
final boolean windowModeChanged =
data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode();
final boolean visibilityChanged = data.getTaskInfo().isVisible != taskInfo.isVisible;
- if (windowModeChanged || visibilityChanged) {
+ if (windowModeChanged || (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && visibilityChanged)) {
mRecentTasks.ifPresent(recentTasks ->
recentTasks.onTaskRunningInfoChanged(taskInfo));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 80a9b67..308bd0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -79,6 +79,7 @@
import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter;
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.GlobalDragListener;
import com.android.wm.shell.freeform.FreeformComponents;
@@ -712,8 +713,14 @@
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeTaskRepository provideDesktopModeTaskRepository() {
- return new DesktopModeTaskRepository();
+ static DesktopModeTaskRepository provideDesktopModeTaskRepository(
+ Context context,
+ ShellInit shellInit,
+ DesktopPersistentRepository desktopPersistentRepository,
+ @ShellMainThread CoroutineScope mainScope
+ ) {
+ return new DesktopModeTaskRepository(context, shellInit, desktopPersistentRepository,
+ mainScope);
}
@WMSingleton
@@ -798,6 +805,14 @@
shellTaskOrganizer, appHandleEducationDatastoreRepository, applicationScope);
}
+ @WMSingleton
+ @Provides
+ static DesktopPersistentRepository provideDesktopPersistentRepository(
+ Context context,
+ @ShellBackgroundThread CoroutineScope bgScope) {
+ return new DesktopPersistentRepository(context, bgScope);
+ }
+
//
// Drag and drop
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 9d04169..759ed03 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.desktopmode
+import android.content.Context
import android.graphics.Rect
import android.graphics.Region
import android.util.ArrayMap
@@ -27,13 +28,27 @@
import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.desktopmode.persistence.DesktopTask
+import com.android.wm.shell.desktopmode.persistence.DesktopTaskState
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/** Tracks task data for Desktop Mode. */
-class DesktopModeTaskRepository {
+class DesktopModeTaskRepository (
+ private val context: Context,
+ shellInit: ShellInit,
+ private val persistentRepository: DesktopPersistentRepository,
+ @ShellMainThread private val mainCoroutineScope: CoroutineScope,
+){
/**
* Task data tracked per desktop.
@@ -54,7 +69,15 @@
// TODO(b/332682201): Remove when the repository state is updated via TransitionObserver
val closingTasks: ArraySet<Int> = ArraySet(),
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
- )
+ ) {
+ fun deepCopy(): DesktopTaskData = DesktopTaskData(
+ activeTasks = ArraySet(activeTasks),
+ visibleTasks = ArraySet(visibleTasks),
+ minimizedTasks = ArraySet(minimizedTasks),
+ closingTasks = ArraySet(closingTasks),
+ freeformTasksInZOrder = ArrayList(freeformTasksInZOrder)
+ )
+ }
/* Current wallpaper activity token to remove wallpaper activity when last task is removed. */
var wallpaperActivityToken: WindowContainerToken? = null
@@ -77,6 +100,40 @@
this[displayId] ?: DesktopTaskData().also { this[displayId] = it }
}
+ init {
+ if (DesktopModeStatus.canEnterDesktopMode(context)) {
+ shellInit.addInitCallback(::initRepoFromPersistentStorage, this)
+ }
+ }
+
+ private fun initRepoFromPersistentStorage() {
+ if (!Flags.enableDesktopWindowingPersistence()) return
+ // TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
+ mainCoroutineScope.launch {
+ val desktop = persistentRepository.readDesktop()
+ val maxTasks =
+ DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
+ ?: desktop.zOrderedTasksCount
+
+ desktop.zOrderedTasksList
+ // Reverse it so we initialize the repo from bottom to top.
+ .reversed()
+ .map { taskId ->
+ desktop.tasksByTaskIdMap.getOrDefault(
+ taskId,
+ DesktopTask.getDefaultInstance()
+ )
+ }
+ .filter { task -> task.desktopTaskState == DesktopTaskState.VISIBLE }
+ .take(maxTasks)
+ .forEach { task ->
+ addOrMoveFreeformTaskToTop(desktop.displayId, task.taskId)
+ addActiveTask(desktop.displayId, task.taskId)
+ updateTaskVisibility(desktop.displayId, task.taskId, visible = false)
+ }
+ }
+ }
+
/** Adds [activeTasksListener] to be notified of updates to active tasks. */
fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) {
activeTasksListeners.add(activeTasksListener)
@@ -266,12 +323,18 @@
desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
// Unminimize the task if it is minimized.
unminimizeTask(displayId, taskId)
+ if (Flags.enableDesktopWindowingPersistence()) {
+ updatePersistentRepository(displayId)
+ }
}
/** Minimizes the task for [taskId] and [displayId] */
fun minimizeTask(displayId: Int, taskId: Int) {
logD("Minimize Task: display=%d, task=%d", displayId, taskId)
desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
+ if (Flags.enableDesktopWindowingPersistence()) {
+ updatePersistentRepository(displayId)
+ }
}
/** Unminimizes the task for [taskId] and [displayId] */
@@ -315,7 +378,10 @@
// Remove task from unminimized task if it is minimized.
unminimizeTask(displayId, taskId)
removeActiveTask(taskId)
- updateTaskVisibility(displayId, taskId, visible = false);
+ updateTaskVisibility(displayId, taskId, visible = false)
+ if (Flags.enableDesktopWindowingPersistence()) {
+ updatePersistentRepository(displayId)
+ }
}
/**
@@ -352,6 +418,27 @@
fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) =
boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
+ private fun updatePersistentRepository(displayId: Int) {
+ // Create a deep copy of the data
+ desktopTaskDataByDisplayId[displayId]?.deepCopy()?.let { desktopTaskDataByDisplayIdCopy ->
+ mainCoroutineScope.launch {
+ try {
+ persistentRepository.addOrUpdateDesktop(
+ visibleTasks = desktopTaskDataByDisplayIdCopy.visibleTasks,
+ minimizedTasks = desktopTaskDataByDisplayIdCopy.minimizedTasks,
+ freeformTasksInZOrder = desktopTaskDataByDisplayIdCopy.freeformTasksInZOrder
+ )
+ } catch (exception: Exception) {
+ logE(
+ "An exception occurred while updating the persistent repository \n%s",
+ exception.stackTrace
+ )
+ }
+ }
+ }
+ }
+
+
internal fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopModeTaskRepository")
@@ -390,6 +477,10 @@
ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
+ private fun logE(msg: String, vararg arguments: Any?) {
+ ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
companion object {
private const val TAG = "DesktopModeTaskRepository"
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index f3ae3ed..968f40c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -58,6 +58,7 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.policy.ScreenDecorationsUtils
import com.android.internal.protolog.ProtoLog
+import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
@@ -80,8 +81,8 @@
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
-import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.ShellSharedConstants
+import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
import com.android.wm.shell.shared.annotations.ShellMainThread
import android.window.flags.DesktopModeFlags
@@ -728,7 +729,7 @@
// exclude current task since maximize/restore transition has not taken place yet.
.filterNot { taskId -> taskId == excludeTaskId }
.any { taskId ->
- val taskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId)!!
+ val taskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return false
val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
val stableBounds = Rect().apply { displayLayout?.getStableBounds(this) }
logD("taskInfo = %s", taskInfo)
@@ -896,6 +897,7 @@
val nonMinimizedTasksOrderedFrontToBack =
taskRepository.getActiveNonMinimizedOrderedTasks(displayId)
// If we're adding a new Task we might need to minimize an old one
+ // TODO(b/365725441): Handle non running task minimization
val taskToMinimize: RunningTaskInfo? =
if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
desktopTasksLimiter
@@ -907,12 +909,26 @@
} else {
null
}
+
nonMinimizedTasksOrderedFrontToBack
// If there is a Task to minimize, let it stay behind the Home Task
.filter { taskId -> taskId != taskToMinimize?.taskId }
- .mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
.reversed() // Start from the back so the front task is brought forward last
- .forEach { task -> wct.reorder(task.token, /* onTop= */ true) }
+ .forEach { taskId ->
+ val runningTaskInfo = shellTaskOrganizer.getRunningTaskInfo(taskId)
+ if (runningTaskInfo != null) {
+ // Task is already running, reorder it to the front
+ wct.reorder(runningTaskInfo.token, /* onTop= */ true)
+ } else if (Flags.enableDesktopWindowingPersistence()) {
+ // Task is not running, start it
+ wct.startTask(
+ taskId,
+ ActivityOptions.makeBasic().apply {
+ launchWindowingMode = WINDOWING_MODE_FREEFORM
+ }.toBundle(),
+ )
+ }
+ }
taskbarDesktopTaskListener?.
onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding(displayId))
@@ -1211,6 +1227,7 @@
wct.reorder(task.token, true)
return wct
}
+ // TODO(b/365723620): Handle non running tasks that were launched after reboot.
// If task is already visible, it must have been handled already and added to desktop mode.
// Cascade task only if it's not visible yet.
if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
new file mode 100644
index 0000000..3f41d7c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepository.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode.persistence
+
+import android.content.Context
+import android.util.ArraySet
+import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.datastore.core.CorruptionException
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.DataStoreFactory
+import androidx.datastore.core.Serializer
+import androidx.datastore.dataStoreFile
+import com.android.framework.protobuf.InvalidProtocolBufferException
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
+
+/**
+ * Persistent repository for storing desktop mode related data.
+ *
+ * The main constructor is public only for testing purposes.
+ */
+class DesktopPersistentRepository(
+ private val dataStore: DataStore<DesktopPersistentRepositories>,
+) {
+ constructor(
+ context: Context,
+ @ShellBackgroundThread bgCoroutineScope: CoroutineScope,
+ ) : this(
+ DataStoreFactory.create(
+ serializer = DesktopPersistentRepositoriesSerializer,
+ produceFile = { context.dataStoreFile(DESKTOP_REPOSITORIES_DATASTORE_FILE) },
+ scope = bgCoroutineScope))
+
+ /** Provides `dataStore.data` flow and handles exceptions thrown during collection */
+ private val dataStoreFlow: Flow<DesktopPersistentRepositories> =
+ dataStore.data.catch { exception ->
+ // dataStore.data throws an IOException when an error is encountered when reading data
+ if (exception is IOException) {
+ Log.e(
+ TAG,
+ "Error in reading desktop mode related data from datastore, data is " +
+ "stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE",
+ exception)
+ } else {
+ throw exception
+ }
+ }
+
+ /**
+ * Reads and returns the [DesktopRepositoryState] proto object from the DataStore for a user. If
+ * the DataStore is empty or there's an error reading, it returns the default value of Proto.
+ */
+ private suspend fun getDesktopRepositoryState(
+ userId: Int = DEFAULT_USER_ID
+ ): DesktopRepositoryState =
+ try {
+ dataStoreFlow
+ .first()
+ .desktopRepoByUserMap
+ .getOrDefault(userId, DesktopRepositoryState.getDefaultInstance())
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to read from datastore", e)
+ DesktopRepositoryState.getDefaultInstance()
+ }
+
+ /**
+ * Reads the [Desktop] of a desktop filtering by the [userId] and [desktopId]. Executes the
+ * [callback] using the [mainCoroutineScope].
+ */
+ suspend fun readDesktop(
+ userId: Int = DEFAULT_USER_ID,
+ desktopId: Int = DEFAULT_DESKTOP_ID,
+ ): Desktop =
+ try {
+ val repository = getDesktopRepositoryState(userId)
+ repository.getDesktopOrThrow(desktopId)
+ } catch (e: Exception) {
+ Log.e(TAG, "Unable to get desktop info from persistent repository", e)
+ Desktop.getDefaultInstance()
+ }
+
+ /** Adds or updates a desktop stored in the datastore */
+ suspend fun addOrUpdateDesktop(
+ userId: Int = DEFAULT_USER_ID,
+ desktopId: Int = 0,
+ visibleTasks: ArraySet<Int> = ArraySet(),
+ minimizedTasks: ArraySet<Int> = ArraySet(),
+ freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
+ ) {
+ // TODO: b/367609270 - Improve the API to support multi-user
+ try {
+ dataStore.updateData { desktopPersistentRepositories: DesktopPersistentRepositories ->
+ val currentRepository =
+ desktopPersistentRepositories.getDesktopRepoByUserOrDefault(
+ userId, DesktopRepositoryState.getDefaultInstance())
+ val desktop =
+ getDesktop(currentRepository, desktopId)
+ .toBuilder()
+ .updateTaskStates(visibleTasks, minimizedTasks)
+ .updateZOrder(freeformTasksInZOrder)
+
+ desktopPersistentRepositories
+ .toBuilder()
+ .putDesktopRepoByUser(
+ userId,
+ currentRepository
+ .toBuilder()
+ .putDesktop(desktopId, desktop.build())
+ .build())
+ .build()
+ }
+ } catch (exception: IOException) {
+ Log.e(
+ TAG,
+ "Error in updating desktop mode related data, data is " +
+ "stored in a file named $DESKTOP_REPOSITORIES_DATASTORE_FILE",
+ exception)
+ }
+ }
+
+ private fun getDesktop(currentRepository: DesktopRepositoryState, desktopId: Int): Desktop =
+ // If there are no desktops set up, create one on the default display
+ currentRepository.getDesktopOrDefault(
+ desktopId,
+ Desktop.newBuilder().setDesktopId(desktopId).setDisplayId(DEFAULT_DISPLAY).build())
+
+ companion object {
+ private const val TAG = "DesktopPersistenceRepo"
+ private const val DESKTOP_REPOSITORIES_DATASTORE_FILE = "desktop_persistent_repositories.pb"
+
+ private const val DEFAULT_USER_ID = 1000
+ private const val DEFAULT_DESKTOP_ID = 0
+
+ object DesktopPersistentRepositoriesSerializer : Serializer<DesktopPersistentRepositories> {
+
+ override val defaultValue: DesktopPersistentRepositories =
+ DesktopPersistentRepositories.getDefaultInstance()
+
+ override suspend fun readFrom(input: InputStream): DesktopPersistentRepositories =
+ try {
+ DesktopPersistentRepositories.parseFrom(input)
+ } catch (exception: InvalidProtocolBufferException) {
+ throw CorruptionException("Cannot read proto.", exception)
+ }
+
+ override suspend fun writeTo(t: DesktopPersistentRepositories, output: OutputStream) =
+ t.writeTo(output)
+ }
+
+ private fun Desktop.Builder.updateTaskStates(
+ visibleTasks: ArraySet<Int>,
+ minimizedTasks: ArraySet<Int>
+ ): Desktop.Builder {
+ clearTasksByTaskId()
+ putAllTasksByTaskId(
+ visibleTasks.associateWith {
+ createDesktopTask(it, state = DesktopTaskState.VISIBLE)
+ })
+ putAllTasksByTaskId(
+ minimizedTasks.associateWith {
+ createDesktopTask(it, state = DesktopTaskState.MINIMIZED)
+ })
+ return this
+ }
+
+ private fun Desktop.Builder.updateZOrder(
+ freeformTasksInZOrder: ArrayList<Int>
+ ): Desktop.Builder {
+ clearZOrderedTasks()
+ addAllZOrderedTasks(freeformTasksInZOrder)
+ return this
+ }
+
+ private fun createDesktopTask(
+ taskId: Int,
+ state: DesktopTaskState = DesktopTaskState.VISIBLE
+ ): DesktopTask =
+ DesktopTask.newBuilder().setTaskId(taskId).setDesktopTaskState(state).build()
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/persistent_desktop_repositories.proto b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/persistent_desktop_repositories.proto
new file mode 100644
index 0000000..0105231
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/persistent_desktop_repositories.proto
@@ -0,0 +1,33 @@
+syntax = "proto2";
+
+option java_package = "com.android.wm.shell.desktopmode.persistence";
+option java_multiple_files = true;
+
+// Represents the state of a task in desktop.
+enum DesktopTaskState {
+ VISIBLE = 0;
+ MINIMIZED = 1;
+}
+
+message DesktopTask {
+ optional int32 task_id = 1;
+ optional DesktopTaskState desktop_task_state= 2;
+}
+
+message Desktop {
+ optional int32 display_id = 1;
+ optional int32 desktop_id = 2;
+ // Stores a mapping between task id and the tasks. The key is the task id.
+ map<int32, DesktopTask> tasks_by_task_id = 3;
+ repeated int32 z_ordered_tasks = 4;
+}
+
+message DesktopRepositoryState {
+ // Stores a mapping between a repository and the desktops in it. The key is the desktop id.
+ map<int32, Desktop> desktop = 1;
+}
+
+message DesktopPersistentRepositories {
+ // Stores a mapping between a user and their desktop repository. The key is the user id.
+ map<int32, DesktopRepositoryState> desktop_repo_by_user = 1;
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 1573291..3a2820ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -358,12 +358,9 @@
if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
if (info.getType() == TRANSIT_CHANGE) {
- int anim = getRotationAnimationHint(change, info, mDisplayController);
+ final int anim = getRotationAnimationHint(change, info, mDisplayController);
isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
- if (wallpaperTransit != WALLPAPER_TRANSITION_NONE) {
- anim |= ScreenRotationAnimation.ANIMATION_HINT_HAS_WALLPAPER;
- }
startRotationAnimation(startTransaction, change, info, anim, animations,
onAnimFinish);
isDisplayRotationAnimationStarted = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index b9d11a3..5802e2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -25,9 +25,12 @@
import static com.android.wm.shell.transition.Transitions.TAG;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -35,7 +38,6 @@
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
-import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.window.ScreenCapture;
@@ -72,9 +74,6 @@
*/
class ScreenRotationAnimation {
static final int MAX_ANIMATION_DURATION = 10 * 1000;
- static final int ANIMATION_HINT_HAS_WALLPAPER = 1 << 8;
- /** It must cover all WindowManager#ROTATION_ANIMATION_*. */
- private static final int ANIMATION_TYPE_MASK = 0xff;
private final Context mContext;
private final TransactionPool mTransactionPool;
@@ -82,7 +81,7 @@
/** The leash of the changing window container. */
private final SurfaceControl mSurfaceControl;
- private final int mAnimType;
+ private final int mAnimHint;
private final int mStartWidth;
private final int mStartHeight;
private final int mEndWidth;
@@ -99,12 +98,6 @@
private SurfaceControl mBackColorSurface;
/** The leash using to animate screenshot layer. */
private final SurfaceControl mAnimLeash;
- /**
- * The container with background color for {@link #mSurfaceControl}. It is only created if
- * {@link #mSurfaceControl} may be translucent. E.g. visible wallpaper with alpha < 1 (dimmed).
- * That prevents flickering of alpha blending.
- */
- private SurfaceControl mBackEffectSurface;
// The current active animation to move from the old to the new rotated
// state. Which animation is run here will depend on the old and new
@@ -122,7 +115,7 @@
Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
mContext = context;
mTransactionPool = pool;
- mAnimType = animHint & ANIMATION_TYPE_MASK;
+ mAnimHint = animHint;
mSurfaceControl = change.getLeash();
mStartWidth = change.getStartAbsBounds().width();
@@ -177,20 +170,11 @@
}
hardwareBuffer.close();
}
- if ((animHint & ANIMATION_HINT_HAS_WALLPAPER) != 0) {
- mBackEffectSurface = new SurfaceControl.Builder()
- .setCallsite("ShellRotationAnimation").setParent(rootLeash)
- .setEffectLayer().setOpaque(true).setName("BackEffect").build();
- t.reparent(mSurfaceControl, mBackEffectSurface)
- .setColor(mBackEffectSurface,
- new float[] {mStartLuma, mStartLuma, mStartLuma})
- .show(mBackEffectSurface);
- }
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
t.show(mAnimLeash);
// Crop the real content in case it contains a larger child layer, e.g. wallpaper.
- t.setCrop(getEnterSurface(), new Rect(0, 0, mEndWidth, mEndHeight));
+ t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
if (!isCustomRotate()) {
mBackColorSurface = new SurfaceControl.Builder()
@@ -215,12 +199,7 @@
}
private boolean isCustomRotate() {
- return mAnimType == ROTATION_ANIMATION_CROSSFADE || mAnimType == ROTATION_ANIMATION_JUMPCUT;
- }
-
- /** Returns the surface which contains the real content to animate enter. */
- private SurfaceControl getEnterSurface() {
- return mBackEffectSurface != null ? mBackEffectSurface : mSurfaceControl;
+ return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
}
private void setScreenshotTransform(SurfaceControl.Transaction t) {
@@ -281,7 +260,7 @@
final boolean customRotate = isCustomRotate();
if (customRotate) {
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- mAnimType == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
+ mAnimHint == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
: R.anim.rotation_animation_xfade_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.rotation_animation_enter);
@@ -335,11 +314,7 @@
} else {
startDisplayRotation(animations, finishCallback, mainExecutor);
startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
- if (mBackEffectSurface != null && mStartLuma > 0.1f) {
- // Animate from the color of background to black for smooth alpha blending.
- buildLumaAnimation(animations, mStartLuma, 0f /* endLuma */, mBackEffectSurface,
- animationScale, finishCallback, mainExecutor);
- }
+ //startColorAnimation(mTransaction, animationScale);
}
return true;
@@ -347,7 +322,7 @@
private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
- buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback,
+ buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
null /* clipRect */, false /* isActivity */);
}
@@ -366,17 +341,40 @@
null /* clipRect */, false /* isActivity */);
}
- private void buildLumaAnimation(@NonNull ArrayList<Animator> animations,
- float startLuma, float endLuma, SurfaceControl surface, float animationScale,
- @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
- final long durationMillis = (long) (mContext.getResources().getInteger(
- R.integer.config_screen_rotation_color_transition) * animationScale);
- final LumaAnimation animation = new LumaAnimation(durationMillis);
- // Align the end with the enter animation.
- animation.setStartOffset(mRotateEnterAnimation.getDuration() - durationMillis);
- final LumaAnimationAdapter adapter = new LumaAnimationAdapter(surface, startLuma, endLuma);
- buildSurfaceAnimation(animations, animation, finishCallback, mTransactionPool,
- mainExecutor, adapter);
+ private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
+ int colorTransitionMs = mContext.getResources().getInteger(
+ R.integer.config_screen_rotation_color_transition);
+ final float[] rgbTmpFloat = new float[3];
+ final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
+ final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
+ final long duration = colorTransitionMs * (long) animationScale;
+ final Transaction t = mTransactionPool.acquire();
+
+ final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
+ // Animation length is already expected to be scaled.
+ va.overrideDurationScale(1.0f);
+ va.setDuration(duration);
+ va.addUpdateListener(animation -> {
+ final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
+ final float fraction = currentPlayTime / va.getDuration();
+ applyColor(startColor, endColor, rgbTmpFloat, fraction, mBackColorSurface, t);
+ });
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
+ t);
+ mTransactionPool.release(t);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
+ t);
+ mTransactionPool.release(t);
+ }
+ });
+ animExecutor.execute(va::start);
}
public void kill() {
@@ -391,47 +389,21 @@
if (mBackColorSurface != null && mBackColorSurface.isValid()) {
t.remove(mBackColorSurface);
}
- if (mBackEffectSurface != null && mBackEffectSurface.isValid()) {
- t.remove(mBackEffectSurface);
- }
t.apply();
mTransactionPool.release(t);
}
- /** A no-op wrapper to provide animation duration. */
- private static class LumaAnimation extends Animation {
- LumaAnimation(long durationMillis) {
- setDuration(durationMillis);
+ private static void applyColor(int startColor, int endColor, float[] rgbFloat,
+ float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
+ final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
+ endColor);
+ Color middleColor = Color.valueOf(color);
+ rgbFloat[0] = middleColor.red();
+ rgbFloat[1] = middleColor.green();
+ rgbFloat[2] = middleColor.blue();
+ if (surface.isValid()) {
+ t.setColor(surface, rgbFloat);
}
- }
-
- private static class LumaAnimationAdapter extends DefaultTransitionHandler.AnimationAdapter {
- final float[] mColorArray = new float[3];
- final float mStartLuma;
- final float mEndLuma;
- final AccelerateInterpolator mInterpolation;
-
- LumaAnimationAdapter(@NonNull SurfaceControl leash, float startLuma, float endLuma) {
- super(leash);
- mStartLuma = startLuma;
- mEndLuma = endLuma;
- // Make the initial progress color lighter if the background is light. That avoids
- // darker content when fading into the entering surface.
- final float factor = Math.min(3f, (Math.max(0.5f, mStartLuma) - 0.5f) * 10);
- Slog.d(TAG, "Luma=" + mStartLuma + " factor=" + factor);
- mInterpolation = factor > 0.5f ? new AccelerateInterpolator(factor) : null;
- }
-
- @Override
- void applyTransformation(ValueAnimator animator) {
- final float fraction = mInterpolation != null
- ? mInterpolation.getInterpolation(animator.getAnimatedFraction())
- : animator.getAnimatedFraction();
- final float luma = mStartLuma + fraction * (mEndLuma - mStartLuma);
- mColorArray[0] = luma;
- mColorArray[1] = luma;
- mColorArray[2] = luma;
- mTransaction.setColor(mLeash, mColorArray);
- }
+ t.apply();
}
}
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..23f8e6e 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
@@ -610,7 +610,7 @@
|| !Flags.enableHandleInputFix()) {
return;
}
- ((AppHandleViewHolder) mWindowDecorViewHolder).disposeStatusBarInputLayer();
+ asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
}
private WindowDecorationViewHolder createViewHolder() {
@@ -647,6 +647,22 @@
return viewHolder instanceof AppHandleViewHolder;
}
+ @Nullable
+ private AppHandleViewHolder asAppHandle(WindowDecorationViewHolder viewHolder) {
+ if (viewHolder instanceof AppHandleViewHolder) {
+ return (AppHandleViewHolder) viewHolder;
+ }
+ return null;
+ }
+
+ @Nullable
+ private AppHeaderViewHolder asAppHeader(WindowDecorationViewHolder viewHolder) {
+ if (viewHolder instanceof AppHeaderViewHolder) {
+ return (AppHeaderViewHolder) viewHolder;
+ }
+ return null;
+ }
+
@VisibleForTesting
static void updateRelayoutParams(
RelayoutParams relayoutParams,
@@ -1025,7 +1041,15 @@
*/
void closeMaximizeMenu() {
if (!isMaximizeMenuActive()) return;
- mMaximizeMenu.close();
+ mMaximizeMenu.close(() -> {
+ // Request the accessibility service to refocus on the maximize button after closing
+ // the menu.
+ final AppHeaderViewHolder appHeader = asAppHeader(mWindowDecorViewHolder);
+ if (appHeader != null) {
+ appHeader.requestAccessibilityFocus();
+ }
+ return Unit.INSTANCE;
+ });
mMaximizeMenu = null;
}
@@ -1053,7 +1077,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 +1395,7 @@
*/
private Region getGlobalExclusionRegion() {
Region exclusionRegion;
- if (mDragResizeListener != null && mTaskInfo.isResizeable) {
+ if (mDragResizeListener != null && isDragResizable(mTaskInfo)) {
exclusionRegion = mDragResizeListener.getCornersRegion();
} else {
exclusionRegion = new Region();
@@ -1407,7 +1432,7 @@
void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) {
if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return;
- ((AppHeaderViewHolder) mWindowDecorViewHolder)
+ asAppHeader(mWindowDecorViewHolder)
.setAnimatingTaskResizeOrReposition(animatingTaskResizeOrReposition);
}
@@ -1415,16 +1440,14 @@
* Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button.
*/
void onMaximizeButtonHoverExit() {
- ((AppHeaderViewHolder) mWindowDecorViewHolder)
- .onMaximizeWindowHoverExit();
+ asAppHeader(mWindowDecorViewHolder).onMaximizeWindowHoverExit();
}
/**
* Called when there is a {@link MotionEvent#ACTION_HOVER_ENTER} on the maximize window button.
*/
void onMaximizeButtonHoverEnter() {
- ((AppHeaderViewHolder) mWindowDecorViewHolder)
- .onMaximizeWindowHoverEnter();
+ asAppHeader(mWindowDecorViewHolder).onMaximizeWindowHoverEnter();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 5b3f263..9a5b4f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -23,8 +23,6 @@
import android.content.res.ColorStateList
import android.content.res.Resources
import android.graphics.Bitmap
-import android.graphics.BlendMode
-import android.graphics.BlendModeColorFilter
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
@@ -568,9 +566,7 @@
appIconBitmap: Bitmap?,
appName: CharSequence?
) {
- appInfoPill.background.colorFilter = BlendModeColorFilter(
- style.backgroundColor, BlendMode.MULTIPLY
- )
+ appInfoPill.background.setTint(style.backgroundColor)
collapseMenuButton.apply {
imageTintList = ColorStateList.valueOf(style.textColor)
@@ -584,9 +580,7 @@
}
private fun bindWindowingPill(style: MenuStyle) {
- windowingPill.background.colorFilter = BlendModeColorFilter(
- style.backgroundColor, BlendMode.MULTIPLY
- )
+ windowingPill.background.setTint(style.backgroundColor)
// TODO: Remove once implemented.
floatingBtn.visibility = View.GONE
@@ -612,23 +606,19 @@
}
screenshotBtn.apply {
isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
- background.colorFilter =
- BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY
- )
+ background.setTint(style.backgroundColor)
setTextColor(style.textColor)
compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
}
newWindowBtn.apply {
isGone = !shouldShowNewWindowButton
- background.colorFilter =
- BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
+ background.setTint(style.backgroundColor)
setTextColor(style.textColor)
compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
}
manageWindowBtn.apply {
isGone = !shouldShowManageWindowsButton
- background.colorFilter =
- BlendModeColorFilter(style.backgroundColor, BlendMode.MULTIPLY)
+ background.setTint(style.backgroundColor)
setTextColor(style.textColor)
compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
}
@@ -637,9 +627,7 @@
private fun bindOpenInBrowserPill(style: MenuStyle) {
openInBrowserPill.apply {
isGone = !shouldShowBrowserPill
- background.colorFilter = BlendModeColorFilter(
- style.backgroundColor, BlendMode.MULTIPLY
- )
+ background.setTint(style.backgroundColor)
}
browserBtn.apply {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
index 9590ccd..0c475f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -26,6 +26,7 @@
import android.view.View.TRANSLATION_Y
import android.view.View.TRANSLATION_Z
import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
import android.widget.Button
import androidx.core.animation.doOnEnd
import androidx.core.view.children
@@ -83,7 +84,12 @@
animateWindowingPillOpen()
animateMoreActionsPillOpen()
animateOpenInBrowserPill()
- runAnimations()
+ runAnimations {
+ appInfoPill.post {
+ appInfoPill.requireViewById<View>(R.id.collapse_menu_button).sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_VIEW_FOCUSED)
+ }
+ }
}
/**
@@ -98,7 +104,12 @@
animateWindowingPillOpen()
animateMoreActionsPillOpen()
animateOpenInBrowserPill()
- runAnimations()
+ runAnimations {
+ appInfoPill.post {
+ appInfoPill.requireViewById<View>(R.id.collapse_menu_button).sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_VIEW_FOCUSED)
+ }
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 9c73e4a..0cb219a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -51,6 +51,7 @@
import android.view.ViewGroup
import android.view.WindowManager
import android.view.WindowlessWindowManager
+import android.view.accessibility.AccessibilityEvent
import android.widget.Button
import android.widget.TextView
import android.window.TaskConstants
@@ -116,19 +117,24 @@
onHoverListener = onHoverListener,
onOutsideTouchListener = onOutsideTouchListener
)
- maximizeMenuView?.animateOpenMenu()
+ maximizeMenuView?.let { view ->
+ view.animateOpenMenu(onEnd = {
+ view.requestAccessibilityFocus()
+ })
+ }
}
/** Closes the maximize window and releases its view. */
- fun close() {
+ fun close(onEnd: () -> Unit) {
val view = maximizeMenuView
val menu = maximizeMenu
if (view == null) {
menu?.releaseView()
} else {
- view.animateCloseMenu {
+ view.animateCloseMenu(onEnd = {
menu?.releaseView()
- }
+ onEnd.invoke()
+ })
}
maximizeMenu = null
maximizeMenuView = null
@@ -351,7 +357,7 @@
}
/** Animate the opening of the menu */
- fun animateOpenMenu() {
+ fun animateOpenMenu(onEnd: () -> Unit) {
maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null)
maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null)
menuAnimatorSet = AnimatorSet()
@@ -419,6 +425,7 @@
onEnd = {
maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
+ onEnd.invoke()
}
)
menuAnimatorSet?.start()
@@ -499,6 +506,14 @@
menuAnimatorSet?.start()
}
+ /** Request that the accessibility service focus on the menu. */
+ fun requestAccessibilityFocus() {
+ // Focus the first button in the menu by default.
+ maximizeButton.post {
+ maximizeButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+ }
+ }
+
/** Cancel the menu animation. */
private fun cancelAnimation() {
menuAnimatorSet?.cancel()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index af6a819..e996165 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -30,6 +30,7 @@
import android.view.View
import android.view.View.OnLongClickListener
import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import android.view.accessibility.AccessibilityEvent
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
@@ -263,7 +264,11 @@
override fun onHandleMenuOpened() {}
- override fun onHandleMenuClosed() {}
+ override fun onHandleMenuClosed() {
+ openMenuButton.post {
+ openMenuButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+ }
+ }
fun setAnimatingTaskResizeOrReposition(animatingTaskResizeOrReposition: Boolean) {
// If animating a task resize or reposition, cancel any running hover animations
@@ -309,6 +314,12 @@
)
}
+ fun requestAccessibilityFocus() {
+ maximizeWindowButton.post {
+ maximizeWindowButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
+ }
+ }
+
private fun getHeaderStyle(header: Header): HeaderStyle {
return HeaderStyle(
background = getHeaderBackground(header),
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
index 717ea30..ce235d4 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
@@ -72,7 +72,6 @@
@Test
open fun startMediaProjection() {
- // TODO(b/366455106) - handle max task Limit
mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
mailApp.launchViaIntent(wmHelper)
simpleApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
index 1573b58..f5fb4ce 100644
--- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
@@ -20,13 +20,12 @@
import android.platform.test.annotations.Postsubmit
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
import com.android.wm.shell.Utils
import org.junit.After
@@ -47,8 +46,7 @@
private val initialRotation = Rotation.ROTATION_0
private val targetApp = CalculatorAppHelper(instrumentation)
- private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
- private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+ private val testApp = StartMediaProjectionAppHelper(instrumentation)
@Rule
@JvmField
@@ -63,7 +61,7 @@
@Test
open fun startMediaProjectionAndRotate() {
- mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
+ testApp.startSingleAppMediaProjection(wmHelper, targetApp)
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_90)
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
index e80a895..28f3cc7 100644
--- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
@@ -25,7 +25,6 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
import com.android.wm.shell.Utils
import org.junit.After
@@ -45,8 +44,7 @@
val device = UiDevice.getInstance(instrumentation)
private val initialRotation = Rotation.ROTATION_0
- private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
- private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+ private val testApp = StartMediaProjectionAppHelper(instrumentation)
@Rule
@JvmField
@@ -60,7 +58,7 @@
@Test
open fun startMediaProjectionAndRotate() {
- mediaProjectionAppHelper.startEntireScreenMediaProjection(wmHelper)
+ testApp.startEntireScreenMediaProjection(wmHelper)
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_90)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index e514dc3..f01ed84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -39,6 +39,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.never;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -599,6 +600,18 @@
}
@Test
+ public void testRecentTasks_visibilityChanges_notFreeForm_shouldNotNotifyTaskController() {
+ RunningTaskInfo task1_visible = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(task1_visible, /* leash= */ null);
+ RunningTaskInfo task1_hidden = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ task1_hidden.isVisible = false;
+
+ mOrganizer.onTaskInfoChanged(task1_hidden);
+
+ verify(mRecentTasksController, never()).onTaskRunningInfoChanged(task1_hidden);
+ }
+
+ @Test
public void testRecentTasks_windowingModeChanges_shouldNotifyTaskController() {
RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
mOrganizer.onTaskAppeared(task1, /* leash= */ null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index b14f163..628c9cdd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -41,12 +41,22 @@
import com.android.wm.shell.common.TaskStackListenerImpl
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
+import com.android.wm.shell.desktopmode.persistence.Desktop
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
import kotlin.test.assertNotNull
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -73,6 +83,7 @@
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE)
class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
@@ -82,16 +93,19 @@
@Mock lateinit var transitions: Transitions
@Mock lateinit var resizeTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var taskStackListener: TaskStackListenerImpl
+ @Mock lateinit var persistentRepository: DesktopPersistentRepository
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var handler: DesktopActivityOrientationChangeHandler
private lateinit var shellInit: ShellInit
private lateinit var taskRepository: DesktopModeTaskRepository
+ private lateinit var testScope: CoroutineScope
// Mock running tasks are registered here so we can get the list from mock shell task organizer.
private val runningTasks = mutableListOf<RunningTaskInfo>()
@Before
fun setUp() {
+ Dispatchers.setMain(StandardTestDispatcher())
mockitoSession =
mockitoSession()
.strictness(Strictness.LENIENT)
@@ -99,10 +113,15 @@
.startMocking()
doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- taskRepository = DesktopModeTaskRepository()
+ taskRepository =
+ DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+ whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
+ Desktop.getDefaultInstance()
+ )
handler = DesktopActivityOrientationChangeHandler(context, shellInit, shellTaskOrganizer,
taskStackListener, resizeTransitionHandler, taskRepository)
@@ -115,6 +134,7 @@
mockitoSession.finishMocking()
runningTasks.clear()
+ testScope.cancel()
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index d3404f7..bc40d89 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -17,27 +17,70 @@
package com.android.wm.shell.desktopmode
import android.graphics.Rect
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
+import android.util.ArraySet
import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.INVALID_DISPLAY
import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.desktopmode.persistence.Desktop
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
+import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.fail
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.spy
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
class DesktopModeTaskRepositoryTest : ShellTestCase() {
private lateinit var repo: DesktopModeTaskRepository
+ private lateinit var shellInit: ShellInit
+ private lateinit var datastoreScope: CoroutineScope
+
+ @Mock private lateinit var testExecutor: ShellExecutor
+ @Mock private lateinit var persistentRepository: DesktopPersistentRepository
@Before
fun setUp() {
- repo = DesktopModeTaskRepository()
+ Dispatchers.setMain(StandardTestDispatcher())
+ datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+ shellInit = spy(ShellInit(testExecutor))
+
+ repo = DesktopModeTaskRepository(context, shellInit, persistentRepository, datastoreScope)
+ whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
+ Desktop.getDefaultInstance()
+ )
+ shellInit.init()
+ }
+
+ @After
+ fun tearDown() {
+ datastoreScope.cancel()
}
@Test
@@ -455,6 +498,44 @@
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ fun addOrMoveFreeformTaskToTop_noTaskExists_persistenceEnabled_addsToTop() =
+ runTest(StandardTestDispatcher()) {
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+
+ val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
+ assertThat(tasks).containsExactly(7, 6, 5).inOrder()
+ inOrder(persistentRepository).run {
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf(5)
+ )
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf(6, 5)
+ )
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf(7, 6, 5)
+ )
+ }
+ }
+
+ @Test
fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() {
repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
@@ -480,6 +561,55 @@
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ fun minimizeTask_persistenceEnabled_taskIsPersistedAsMinimized() =
+ runTest(StandardTestDispatcher()) {
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 7)
+
+ repo.minimizeTask(displayId = 0, taskId = 6)
+
+ val tasks = repo.getFreeformTasksInZOrder(DEFAULT_DISPLAY)
+ assertThat(tasks).containsExactly(7, 6, 5).inOrder()
+ assertThat(repo.isMinimizedTask(taskId = 6)).isTrue()
+ inOrder(persistentRepository).run {
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf(5)
+ )
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf(6, 5)
+ )
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf(7, 6, 5)
+ )
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(arrayOf(6)),
+ freeformTasksInZOrder = arrayListOf(7, 6, 5)
+ )
+ }
+ }
+
+ @Test
fun addOrMoveFreeformTaskToTop_taskIsUnminimized_noop() {
repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 5)
repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, 6)
@@ -503,6 +633,33 @@
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ fun removeFreeformTask_invalidDisplay_persistenceEnabled_removesTaskFromFreeformTasks() {
+ runTest(StandardTestDispatcher()) {
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+
+ repo.removeFreeformTask(INVALID_DISPLAY, taskId = 1)
+
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf(1)
+ )
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = ArrayList()
+ )
+ }
+ }
+
+ @Test
fun removeFreeformTask_validDisplay_removesTaskFromFreeformTasks() {
repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
@@ -513,6 +670,33 @@
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ fun removeFreeformTask_validDisplay_persistenceEnabled_removesTaskFromFreeformTasks() {
+ runTest(StandardTestDispatcher()) {
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+
+ repo.removeFreeformTask(DEFAULT_DISPLAY, taskId = 1)
+
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf(1)
+ )
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = ArrayList()
+ )
+ }
+ }
+
+ @Test
fun removeFreeformTask_validDisplay_differentDisplay_doesNotRemovesTask() {
repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
@@ -523,6 +707,33 @@
}
@Test
+ @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+ fun removeFreeformTask_validDisplayButDifferentDisplay_persistenceEnabled_doesNotRemoveTask() {
+ runTest(StandardTestDispatcher()) {
+ repo.addOrMoveFreeformTaskToTop(DEFAULT_DISPLAY, taskId = 1)
+
+ repo.removeFreeformTask(SECOND_DISPLAY, taskId = 1)
+
+ verify(persistentRepository)
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = arrayListOf(1)
+ )
+ verify(persistentRepository, never())
+ .addOrUpdateDesktop(
+ DEFAULT_USER_ID,
+ DEFAULT_DESKTOP_ID,
+ visibleTasks = ArraySet(),
+ minimizedTasks = ArraySet(),
+ freeformTasksInZOrder = ArrayList()
+ )
+ }
+ }
+
+ @Test
fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
val taskId = 1
repo.addActiveTask(THIRD_DISPLAY, taskId)
@@ -709,5 +920,7 @@
companion object {
const val SECOND_DISPLAY = 1
const val THIRD_DISPLAY = 345
+ private const val DEFAULT_USER_ID = 1000
+ private const val DEFAULT_DESKTOP_ID = 0
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 8f20841..ee54520 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -93,6 +93,8 @@
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
+import com.android.wm.shell.desktopmode.persistence.Desktop
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
@@ -117,6 +119,14 @@
import junit.framework.Assert.assertTrue
import kotlin.test.assertNotNull
import kotlin.test.assertNull
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
@@ -148,6 +158,7 @@
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
class DesktopTasksControllerTest : ShellTestCase() {
@@ -183,6 +194,7 @@
@Mock private lateinit var mockSurface: SurfaceControl
@Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener
@Mock private lateinit var mockHandler: Handler
+ @Mock lateinit var persistentRepository: DesktopPersistentRepository
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -190,6 +202,7 @@
private lateinit var taskRepository: DesktopModeTaskRepository
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
private lateinit var recentsTransitionStateListener: RecentsTransitionStateListener
+ private lateinit var testScope: CoroutineScope
private val shellExecutor = TestShellExecutor()
@@ -207,6 +220,7 @@
@Before
fun setUp() {
+ Dispatchers.setMain(StandardTestDispatcher())
mockitoSession =
mockitoSession()
.strictness(Strictness.LENIENT)
@@ -214,8 +228,9 @@
.startMocking()
doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
- taskRepository = DesktopModeTaskRepository()
+ taskRepository = DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
desktopTasksLimiter =
DesktopTasksLimiter(
transitions,
@@ -233,6 +248,9 @@
whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
(i.arguments.first() as Rect).set(STABLE_BOUNDS)
}
+ whenever(runBlocking { persistentRepository.readDesktop(any(), any()) }).thenReturn(
+ Desktop.getDefaultInstance()
+ )
val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
@@ -287,6 +305,7 @@
mockitoSession.finishMocking()
runningTasks.clear()
+ testScope.cancel()
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 61d03ca..045e077 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -35,13 +35,23 @@
import com.android.internal.jank.InteractionJankMonitor
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
+import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.TransitionInfoBuilder
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -49,6 +59,7 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
import org.mockito.kotlin.eq
import org.mockito.kotlin.verify
@@ -62,6 +73,7 @@
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
class DesktopTasksLimiterTest : ShellTestCase() {
@JvmField
@@ -72,19 +84,26 @@
@Mock lateinit var transitions: Transitions
@Mock lateinit var interactionJankMonitor: InteractionJankMonitor
@Mock lateinit var handler: Handler
+ @Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var persistentRepository: DesktopPersistentRepository
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var desktopTasksLimiter: DesktopTasksLimiter
private lateinit var desktopTaskRepo: DesktopModeTaskRepository
+ private lateinit var shellInit: ShellInit
+ private lateinit var testScope: CoroutineScope
@Before
fun setUp() {
mockitoSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus::class.java).startMocking()
doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(any()) }
+ shellInit = spy(ShellInit(testExecutor))
+ Dispatchers.setMain(StandardTestDispatcher())
+ testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
- desktopTaskRepo = DesktopModeTaskRepository()
-
+ desktopTaskRepo =
+ DesktopModeTaskRepository(context, shellInit, persistentRepository, testScope)
desktopTasksLimiter =
DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT,
interactionJankMonitor, mContext, handler)
@@ -93,6 +112,7 @@
@After
fun tearDown() {
mockitoSession.finishMocking()
+ testScope.cancel()
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
new file mode 100644
index 0000000..9b9703f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/persistence/DesktopPersistentRepositoryTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode.persistence
+
+import android.content.Context
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.util.ArraySet
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.DataStoreFactory
+import androidx.datastore.dataStoreFile
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
+import com.android.wm.shell.ShellTestCase
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@ExperimentalCoroutinesApi
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE, FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
+class DesktopPersistentRepositoryTest : ShellTestCase() {
+ private val testContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
+ private lateinit var testDatastore: DataStore<DesktopPersistentRepositories>
+ private lateinit var datastoreRepository: DesktopPersistentRepository
+ private lateinit var datastoreScope: CoroutineScope
+
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(StandardTestDispatcher())
+ datastoreScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
+ testDatastore =
+ DataStoreFactory.create(
+ serializer =
+ DesktopPersistentRepository.Companion.DesktopPersistentRepositoriesSerializer,
+ scope = datastoreScope) {
+ testContext.dataStoreFile(DESKTOP_REPOSITORY_STATES_DATASTORE_TEST_FILE)
+ }
+ datastoreRepository = DesktopPersistentRepository(testDatastore)
+ }
+
+ @After
+ fun tearDown() {
+ File(ApplicationProvider.getApplicationContext<Context>().filesDir, "datastore")
+ .deleteRecursively()
+
+ datastoreScope.cancel()
+ }
+
+ @Test
+ fun readRepository_returnsCorrectDesktop() {
+ runTest(StandardTestDispatcher()) {
+ val task = createDesktopTask(1)
+ val desk = createDesktop(task)
+ val repositoryState =
+ DesktopRepositoryState.newBuilder().putDesktop(DEFAULT_DESKTOP_ID, desk)
+ val DesktopPersistentRepositories =
+ DesktopPersistentRepositories.newBuilder()
+ .putDesktopRepoByUser(DEFAULT_USER_ID, repositoryState.build())
+ .build()
+ testDatastore.updateData { DesktopPersistentRepositories }
+
+ val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
+
+ assertThat(actualDesktop).isEqualTo(desk)
+ }
+ }
+
+ @Test
+ fun addOrUpdateTask_addNewTaskToDesktop() {
+ runTest(StandardTestDispatcher()) {
+ // Create a basic repository state
+ val task = createDesktopTask(1)
+ val DesktopPersistentRepositories = createRepositoryWithOneDesk(task)
+ testDatastore.updateData { DesktopPersistentRepositories }
+ // Create a new state to be initialized
+ val visibleTasks = ArraySet(listOf(1, 2))
+ val minimizedTasks = ArraySet<Int>()
+ val freeformTasksInZOrder = ArrayList(listOf(2, 1))
+
+ // Update with new state
+ datastoreRepository.addOrUpdateDesktop(
+ visibleTasks = visibleTasks,
+ minimizedTasks = minimizedTasks,
+ freeformTasksInZOrder = freeformTasksInZOrder)
+
+ val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
+ assertThat(actualDesktop.tasksByTaskIdMap).hasSize(2)
+ assertThat(actualDesktop.getZOrderedTasks(0)).isEqualTo(2)
+ }
+ }
+
+ @Test
+ fun addOrUpdateTask_changeTaskStateToMinimize_taskStateIsMinimized() {
+ runTest(StandardTestDispatcher()) {
+ val task = createDesktopTask(1)
+ val DesktopPersistentRepositories = createRepositoryWithOneDesk(task)
+ testDatastore.updateData { DesktopPersistentRepositories }
+ // Create a new state to be initialized
+ val visibleTasks = ArraySet(listOf(1))
+ val minimizedTasks = ArraySet(listOf(1))
+ val freeformTasksInZOrder = ArrayList(listOf(1))
+
+ // Update with new state
+ datastoreRepository.addOrUpdateDesktop(
+ visibleTasks = visibleTasks,
+ minimizedTasks = minimizedTasks,
+ freeformTasksInZOrder = freeformTasksInZOrder)
+
+ val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
+ assertThat(actualDesktop.tasksByTaskIdMap[task.taskId]?.desktopTaskState)
+ .isEqualTo(DesktopTaskState.MINIMIZED)
+ }
+ }
+
+ @Test
+ fun removeTask_previouslyAddedTaskIsRemoved() {
+ runTest(StandardTestDispatcher()) {
+ val task = createDesktopTask(1)
+ val DesktopPersistentRepositories = createRepositoryWithOneDesk(task)
+ testDatastore.updateData { DesktopPersistentRepositories }
+ // Create a new state to be initialized
+ val visibleTasks = ArraySet<Int>()
+ val minimizedTasks = ArraySet<Int>()
+ val freeformTasksInZOrder = ArrayList<Int>()
+
+ // Update with new state
+ datastoreRepository.addOrUpdateDesktop(
+ visibleTasks = visibleTasks,
+ minimizedTasks = minimizedTasks,
+ freeformTasksInZOrder = freeformTasksInZOrder)
+
+ val actualDesktop = datastoreRepository.readDesktop(DEFAULT_USER_ID, DEFAULT_DESKTOP_ID)
+ assertThat(actualDesktop.tasksByTaskIdMap).isEmpty()
+ assertThat(actualDesktop.zOrderedTasksList).isEmpty()
+ }
+ }
+
+ private companion object {
+ const val DESKTOP_REPOSITORY_STATES_DATASTORE_TEST_FILE = "desktop_repo_test.pb"
+ const val DEFAULT_USER_ID = 1000
+ const val DEFAULT_DESKTOP_ID = 0
+
+ fun createRepositoryWithOneDesk(task: DesktopTask): DesktopPersistentRepositories {
+ val desk = createDesktop(task)
+ val repositoryState =
+ DesktopRepositoryState.newBuilder().putDesktop(DEFAULT_DESKTOP_ID, desk)
+ val DesktopPersistentRepositories =
+ DesktopPersistentRepositories.newBuilder()
+ .putDesktopRepoByUser(DEFAULT_USER_ID, repositoryState.build())
+ .build()
+ return DesktopPersistentRepositories
+ }
+
+ fun createDesktop(task: DesktopTask): Desktop? =
+ Desktop.newBuilder()
+ .setDisplayId(DEFAULT_DISPLAY)
+ .addZOrderedTasks(task.taskId)
+ .putTasksByTaskId(task.taskId, task)
+ .build()
+
+ fun createDesktopTask(
+ taskId: Int,
+ state: DesktopTaskState = DesktopTaskState.VISIBLE
+ ): DesktopTask =
+ DesktopTask.newBuilder().setTaskId(taskId).setDesktopTaskState(state).build()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index a1867f3..9c11ec3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -623,7 +623,7 @@
.postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS));
mCloseMaxMenuRunnable.getValue().run();
- verify(menu).close();
+ verify(menu).close(any());
assertFalse(decoration.isMaximizeMenuActive());
}
@@ -642,7 +642,7 @@
.postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS));
mCloseMaxMenuRunnable.getValue().run();
- verify(menu).close();
+ verify(menu).close(any());
assertFalse(decoration.isMaximizeMenuActive());
}
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/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index bf6ec96..185f579 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -8,3 +8,10 @@
description: "Add media metrics for transcoding/editing events."
bug: "297487694"
}
+
+flag {
+ name: "stagefrightrecorder_enable_b_frames"
+ namespace: "media_solutions"
+ description: "Enable B frames for Stagefright recorder."
+ bug: "341121900"
+}
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index 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/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 9603c0a..d17a9b6 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -60,7 +60,7 @@
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
- method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void setControllerAlwaysOn(int);
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void setControllerAlwaysOnMode(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull android.nfc.NfcOemExtension.Callback);
field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int DISABLE = 0; // 0x0
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 2804546..951702c 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -560,13 +560,13 @@
public @interface TagIntentAppPreferenceResult {}
/**
- * Mode Type for {@link NfcOemExtension#setControllerAlwaysOn(int)}.
+ * Mode Type for {@link NfcOemExtension#setControllerAlwaysOnMode(int)}.
* @hide
*/
public static final int CONTROLLER_ALWAYS_ON_MODE_DEFAULT = 1;
/**
- * Mode Type for {@link NfcOemExtension#setControllerAlwaysOn(int)}.
+ * Mode Type for {@link NfcOemExtension#setControllerAlwaysOnMode(int)}.
* @hide
*/
public static final int CONTROLLER_ALWAYS_ON_DISABLE = 0;
@@ -2323,7 +2323,7 @@
* <p>This API is for the NFCC internal state management. It allows to discriminate
* the controller function from the NFC function by keeping the NFC controller on without
* any NFC RF enabled if necessary.
- * <p>This call is asynchronous. Register a listener {@link #ControllerAlwaysOnListener}
+ * <p>This call is asynchronous. Register a listener {@link ControllerAlwaysOnListener}
* by {@link #registerControllerAlwaysOnListener} to find out when the operation is
* complete.
* <p>If this returns true, then either NFCC always on state has been set based on the value,
@@ -2337,7 +2337,7 @@
* FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
* FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
* are unavailable
- * @return true if feature is supported by the device and operation has bee initiated,
+ * @return true if feature is supported by the device and operation has been initiated,
* false if the feature is not supported by the device.
* @hide
*/
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 45038d4..011c60b 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -70,7 +70,7 @@
private boolean mRfDiscoveryStarted = false;
/**
- * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Mode Type for {@link #setControllerAlwaysOnMode(int)}.
* Enables the controller in default mode when NFC is disabled (existing API behavior).
* works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}.
* @hide
@@ -80,7 +80,7 @@
public static final int ENABLE_DEFAULT = NfcAdapter.CONTROLLER_ALWAYS_ON_MODE_DEFAULT;
/**
- * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Mode Type for {@link #setControllerAlwaysOnMode(int)}.
* Enables the controller in transparent mode when NFC is disabled.
* @hide
*/
@@ -89,7 +89,7 @@
public static final int ENABLE_TRANSPARENT = 2;
/**
- * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Mode Type for {@link #setControllerAlwaysOnMode(int)}.
* Enables the controller and initializes and enables the EE subsystem when NFC is disabled.
* @hide
*/
@@ -98,7 +98,7 @@
public static final int ENABLE_EE = 3;
/**
- * Mode Type for {@link #setControllerAlwaysOn(int)}.
+ * Mode Type for {@link #setControllerAlwaysOnMode(int)}.
* Disable the Controller Always On Mode.
* works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}.
* @hide
@@ -108,7 +108,7 @@
public static final int DISABLE = NfcAdapter.CONTROLLER_ALWAYS_ON_DISABLE;
/**
- * Possible controller modes for {@link #setControllerAlwaysOn(int)}.
+ * Possible controller modes for {@link #setControllerAlwaysOnMode(int)}.
*
* @hide
*/
@@ -449,6 +449,9 @@
* <p>This call is asynchronous, register listener {@link NfcAdapter.ControllerAlwaysOnListener}
* by {@link NfcAdapter#registerControllerAlwaysOnListener} to find out when the operation is
* complete.
+ * <p> Note: This adds more always on modes on top of existing
+ * {@link NfcAdapter#setControllerAlwaysOn(boolean)} API which can be used to set the NFCC in
+ * only {@link #ENABLE_DEFAULT} and {@link #DISABLE} modes.
* @param mode one of {@link ControllerMode} modes
* @throws UnsupportedOperationException if
* <li> if FEATURE_NFC, FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
@@ -456,11 +459,12 @@
* are unavailable </li>
* <li> if the feature is unavailable @see NfcAdapter#isNfcControllerAlwaysOnSupported() </li>
* @hide
+ * @see NfcAdapter#setControllerAlwaysOn(boolean)
*/
@SystemApi
@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
@RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
- public void setControllerAlwaysOn(@ControllerMode int mode) {
+ public void setControllerAlwaysOnMode(@ControllerMode int mode) {
if (!NfcAdapter.sHasNfcFeature && !NfcAdapter.sHasCeFeature) {
throw new UnsupportedOperationException();
}
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_chevron.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
new file mode 100644
index 0000000..16ca18a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="18dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="@color/settingslib_materialColorOnSurfaceVariant"
+ android:autoMirrored="true">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M321,880L250,809L579,480L250,151L321,80L721,480L321,880Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_close.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_close.xml
new file mode 100644
index 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/layout-v35/settingslib_expressive_two_target_divider.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
new file mode 100644
index 0000000..3f75181
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/two_target_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical"
+ android:orientation="horizontal"
+ android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall7">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="@dimen/settingslib_expressive_space_extrasmall6"
+ android:src="@drawable/settingslib_expressive_icon_chevron"/>
+
+ <View
+ android:layout_width="1dp"
+ android:layout_height="40dp"
+ android:background="?android:attr/listDivider" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml
new file mode 100644
index 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/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 3011ce0..b69912a 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.0-rc01"
+ extra["jetpackComposeVersion"] = "1.7.0"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 18a6db0..f942fd0 100644
--- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -26,6 +26,8 @@
<string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string>
<!-- Footer text with two links. [DO NOT TRANSLATE] -->
<string name="footer_with_two_links" translatable="false">Annotated string with <a href="https://www.android.com/">link 1</a> and <a href="https://source.android.com/">link 2</a>.</string>
+ <!-- TopIntroPreference preview text. [DO NOT TRANSLATE] -->
+ <string name="label_with_two_links" translatable="false"><a href="https://www.android.com/">Label</a></string>
<!-- Sample title -->
<string name="sample_title" translatable="false">Lorem ipsum</string>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 83d657e..7139f5b4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -42,12 +42,15 @@
import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
import com.android.settingslib.spa.gallery.scaffold.NonScrollablePagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
+import com.android.settingslib.spa.gallery.preference.IntroPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.TopIntroPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.ZeroStatePreferencePageProvider
import com.android.settingslib.spa.gallery.scaffold.PagerMainPageProvider
import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
@@ -82,6 +85,7 @@
MainSwitchPreferencePageProvider,
ListPreferencePageProvider,
TwoTargetSwitchPreferencePageProvider,
+ ZeroStatePreferencePageProvider,
ArgumentPageProvider,
SliderPageProvider,
SpinnerPageProvider,
@@ -109,6 +113,8 @@
SuwScaffoldPageProvider,
BannerPageProvider,
CopyablePageProvider,
+ IntroPreferencePageProvider,
+ TopIntroPreferencePageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
new file mode 100644
index 0000000..603fcee
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.IntroPreference
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+private const val TITLE = "Sample IntroPreference"
+
+object IntroPreferencePageProvider : SettingsPageProvider {
+ override val name = "IntroPreference"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("IntroPreference", owner)
+ .setUiLayoutFn { SampleIntroPreference() }
+ .build()
+ )
+
+ return entryList
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
+ }
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+}
+
+@Composable
+private fun SampleIntroPreference() {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ IntroPreference(
+ title = "Preferred network type",
+ descriptions = listOf("Description"),
+ imageVector = Icons.Outlined.AirplanemodeActive,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index ce9678b..1626b02 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -39,6 +39,9 @@
ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
.setLink(fromPage = owner).build(),
+ ZeroStatePreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ IntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ TopIntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
new file mode 100644
index 0000000..b251266
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.gallery.R
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.TopIntroPreference
+import com.android.settingslib.spa.widget.preference.TopIntroPreferenceModel
+
+private const val TITLE = "Sample TopIntroPreference"
+
+object TopIntroPreferencePageProvider : SettingsPageProvider {
+ override val name = "TopIntroPreference"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("TopIntroPreference", owner)
+ .setUiLayoutFn { SampleTopIntroPreference() }
+ .build()
+ )
+
+ return entryList
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
+ }
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+}
+
+@Composable
+private fun SampleTopIntroPreference() {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text =
+ "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" +
+ "Example collapsed text area that you will not see until you expand this block."
+ override val expandText = "Expand"
+ override val collapseText = "Collapse"
+ override val labelText = R.string.label_with_two_links
+ }
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
new file mode 100644
index 0000000..4a9c5c8
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.ZeroStatePreference
+
+private const val TITLE = "Sample ZeroStatePreference"
+
+object ZeroStatePreferencePageProvider : SettingsPageProvider {
+ override val name = "ZeroStatePreference"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("ZeroStatePreference", owner)
+ .setUiLayoutFn {
+ SampleZeroStatePreference()
+ }.build()
+ )
+
+ return entryList
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+}
+
+@Composable
+private fun SampleZeroStatePreference() {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ ZeroStatePreference(
+ Icons.Filled.History,
+ "No recent search history",
+ "Description"
+ )
+ }
+}
+
+
+@Preview(showBackground = true)
+@Composable
+private fun SwitchPreferencePagePreview() {
+ SettingsTheme {
+ ZeroStatePreferencePageProvider.Page(null)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index f0c2ea6..790aa9f 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -54,15 +54,16 @@
dependencies {
api(project(":SettingsLibColor"))
api("androidx.appcompat:appcompat:1.7.0")
- api("androidx.compose.material3:material3:1.3.0-rc01")
+ api("androidx.compose.material3:material3:1.3.0")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.8.0-rc01")
+ api("androidx.navigation:navigation-compose:2.8.1")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.11.0")
+ api("androidx.graphics:graphics-shapes-android:1.0.1")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
implementation("com.airbnb.android:lottie-compose:6.4.0")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 1f3e2425..f8c791a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -21,7 +21,9 @@
object SettingsDimension {
val paddingTiny = 2.dp
- val paddingSmall = 4.dp
+ val paddingExtraSmall = 4.dp
+ val paddingSmall = if (isSpaExpressiveEnabled) 8.dp else 4.dp
+ val paddingExtraSmall5 = 10.dp
val paddingLarge = 16.dp
val paddingExtraLarge = 24.dp
@@ -56,6 +58,7 @@
val itemDividerHeight = 32.dp
val iconLarge = 48.dp
+ val introIconSize = 40.dp
/** The size when app icon is displayed in list. */
val appIconItemSize = 32.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index 15def72..f948d51 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -21,6 +21,7 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import com.android.settingslib.spa.framework.util.SystemProperties
/**
* The Material 3 Theme for Settings.
@@ -41,4 +42,5 @@
}
}
-const val isSpaExpressiveEnabled = false
\ No newline at end of file
+val isSpaExpressiveEnabled
+ by lazy { SystemProperties.getBoolean("is_expressive_design_enabled", false) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt
new file mode 100644
index 0000000..ed4936b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.annotation.SuppressLint
+import android.util.Log
+
+@SuppressLint("PrivateApi")
+object SystemProperties {
+ private const val TAG = "SystemProperties"
+
+ fun getBoolean(key: String, default: Boolean): Boolean = try {
+ val systemProperties = Class.forName("android.os.SystemProperties")
+ systemProperties
+ .getMethod("getBoolean", String::class.java, Boolean::class.java)
+ .invoke(systemProperties, key, default) as Boolean
+ } catch (e: Exception) {
+ Log.e(TAG, "getBoolean: $key", e)
+ default
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
new file mode 100644
index 0000000..22a5755
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun IntroPreference(
+ title: String,
+ descriptions: List<String>? = null,
+ imageVector: ImageVector? = null,
+) {
+ IntroPreference(title = title, descriptions = descriptions, icon = { IntroIcon(imageVector) })
+}
+
+@Composable
+fun IntroAppPreference(
+ title: String,
+ descriptions: List<String>? = null,
+ appIcon: @Composable (() -> Unit),
+) {
+ IntroPreference(title = title, descriptions = descriptions, icon = { IntroAppIcon(appIcon) })
+}
+
+@Composable
+internal fun IntroPreference(
+ title: String,
+ descriptions: List<String>?,
+ icon: @Composable (() -> Unit),
+) {
+ Column(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ horizontal = SettingsDimension.paddingExtraLarge,
+ vertical = SettingsDimension.paddingLarge,
+ ),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ icon()
+ IntroTitle(title)
+ IntroDescription(descriptions)
+ }
+}
+
+@Composable
+private fun IntroIcon(imageVector: ImageVector?) {
+ if (imageVector != null) {
+ Box(
+ modifier =
+ Modifier.size(SettingsDimension.itemIconContainerSize)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.secondaryContainer),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ imageVector = imageVector,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.introIconSize),
+ tint = MaterialTheme.colorScheme.onSecondary,
+ )
+ }
+ }
+}
+
+@Composable
+private fun IntroAppIcon(appIcon: @Composable () -> Unit) {
+ Box(
+ modifier = Modifier.size(SettingsDimension.itemIconContainerSize).clip(CircleShape),
+ contentAlignment = Alignment.Center,
+ ) {
+ appIcon()
+ }
+}
+
+@Composable
+private fun IntroTitle(title: String) {
+ Box(modifier = Modifier.padding(top = SettingsDimension.paddingLarge)) {
+ Text(
+ text = title,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+ }
+}
+
+@Composable
+private fun IntroDescription(descriptions: List<String>?) {
+ if (descriptions != null) {
+ for (description in descriptions) {
+ if (description.isEmpty()) continue
+ Text(
+ text = description,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(top = SettingsDimension.paddingExtraSmall),
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun IntroPreferencePreview() {
+ IntroPreference(
+ title = "Preferred network type",
+ descriptions = listOf("Description", "Version"),
+ imageVector = Icons.Outlined.AirplanemodeActive,
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt
new file mode 100644
index 0000000..7e61959
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.annotation.StringRes
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material.icons.filled.KeyboardArrowUp
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.toMediumWeight
+import com.android.settingslib.spa.framework.util.annotatedStringResource
+
+/** The widget model for [TopIntroPreference] widget. */
+interface TopIntroPreferenceModel {
+ /** The content of this [TopIntroPreference]. */
+ val text: String
+
+ /** The text clicked to expand this [TopIntroPreference]. */
+ val expandText: String
+
+ /** The text clicked to collapse this [TopIntroPreference]. */
+ val collapseText: String
+
+ /** The text clicked to open other resources. Should be a resource Id. */
+ val labelText: Int?
+}
+
+@Composable
+fun TopIntroPreference(model: TopIntroPreferenceModel) {
+ var expanded by remember { mutableStateOf(false) }
+ Column(Modifier.background(MaterialTheme.colorScheme.surfaceContainer)) {
+ // TopIntroPreference content.
+ Column(
+ modifier =
+ Modifier.padding(
+ horizontal = SettingsDimension.paddingExtraLarge,
+ vertical = SettingsDimension.paddingSmall,
+ )
+ .animateContentSize()
+ ) {
+ Text(
+ text = model.text,
+ style = MaterialTheme.typography.bodyLarge,
+ maxLines = if (expanded) MAX_LINE else MIN_LINE,
+ )
+ if (expanded) TopIntroAnnotatedText(model.labelText)
+ }
+
+ // TopIntroPreference collapse bar.
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.fillMaxWidth()
+ .clickable(onClick = { expanded = !expanded })
+ .padding(
+ top = SettingsDimension.paddingSmall,
+ bottom = SettingsDimension.paddingLarge,
+ start = SettingsDimension.paddingExtraLarge,
+ end = SettingsDimension.paddingExtraLarge,
+ ),
+ ) {
+ Icon(
+ imageVector =
+ if (expanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
+ contentDescription = null,
+ modifier =
+ Modifier.size(SettingsDimension.itemIconSize)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.surfaceContainerHighest),
+ )
+ Text(
+ text = if (expanded) model.collapseText else model.expandText,
+ modifier = Modifier.padding(start = SettingsDimension.paddingSmall),
+ style = MaterialTheme.typography.bodyLarge.toMediumWeight(),
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+ }
+ }
+}
+
+@Composable
+private fun TopIntroAnnotatedText(@StringRes id: Int?) {
+ if (id != null) {
+ Box(
+ Modifier.padding(
+ top = SettingsDimension.paddingExtraSmall5,
+ bottom = SettingsDimension.paddingExtraSmall5,
+ end = SettingsDimension.paddingLarge,
+ )
+ ) {
+ Text(
+ text = annotatedStringResource(id),
+ style = MaterialTheme.typography.bodyLarge.toMediumWeight(),
+ color = MaterialTheme.colorScheme.primary,
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun TopIntroPreferencePreview() {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text =
+ "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" +
+ "Example collapsed text area that you will not see until you expand this block."
+ override val expandText = "Expand"
+ override val collapseText = "Collapse"
+ override val labelText = androidx.appcompat.R.string.abc_prepend_shortcut_label
+ }
+ )
+}
+
+const val MIN_LINE = 2
+const val MAX_LINE = 10
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
new file mode 100644
index 0000000..3f2e772
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.asComposePath
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.graphics.shapes.CornerRounding
+import androidx.graphics.shapes.RoundedPolygon
+import androidx.graphics.shapes.star
+import androidx.graphics.shapes.toPath
+
+@Composable
+fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: String? = null) {
+ val zeroStateShape = remember {
+ RoundedPolygon.star(
+ numVerticesPerRadius = 6,
+ innerRadius = 0.75f,
+ rounding = CornerRounding(0.3f)
+ )
+ }
+ val clip = remember(zeroStateShape) {
+ RoundedPolygonShape(polygon = zeroStateShape)
+ }
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Box(
+ modifier = Modifier
+ .clip(clip)
+ .background(MaterialTheme.colorScheme.primary)
+ .size(160.dp)
+ ) {
+ Icon(
+ imageVector = icon,
+ modifier = Modifier
+ .align(Alignment.Center)
+ .size(72.dp),
+ tint = MaterialTheme.colorScheme.onPrimary,
+ contentDescription = null,
+ )
+ }
+ if (text != null) {
+ Text(
+ text = text,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(top = 24.dp),
+ )
+ }
+ if (description != null) {
+ Box {
+ Text(
+ text = description,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun ZeroStatePreferencePreview() {
+ ZeroStatePreference(
+ Icons.Filled.History,
+ "No recent search history",
+ "Description"
+ )
+}
+
+class RoundedPolygonShape(
+ private val polygon: RoundedPolygon,
+ private var matrix: Matrix = Matrix()
+) : Shape {
+ private var path = Path()
+ override fun createOutline(
+ size: Size,
+ layoutDirection: LayoutDirection,
+ density: Density
+ ): Outline {
+ path.rewind()
+ path = polygon.toPath().asComposePath()
+
+ matrix.reset()
+ matrix.scale(size.width / 2f, size.height / 2f)
+ matrix.translate(1f, 1f)
+ matrix.rotateZ(30.0f)
+
+ path.transform(matrix)
+ return Outline.Generic(path)
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
index fb8f878..346f69b 100644
--- a/packages/SettingsLib/Spa/tests/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -28,5 +28,7 @@
<string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.android.com/">link</a>.</string>
+ <string name="test_top_intro_preference_label"><a href="https://www.android.com/">Label</a></string>
+
<string name="test_link"><a href="https://www.android.com/">link</a></string>
</resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt
new file mode 100644
index 0000000..0827fa9
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SystemPropertiesTest {
+
+ @Test
+ fun getBoolean_noCrash() {
+ SystemProperties.getBoolean("is_expressive_design_enabled", false)
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
new file mode 100644
index 0000000..5d80145
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class IntroPreferenceTest {
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent { IntroPreference(title = TITLE) }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun description_displayed() {
+ composeTestRule.setContent { IntroPreference(title = TITLE, descriptions = DESCRIPTION) }
+
+ composeTestRule.onNodeWithText(DESCRIPTION.component1()).assertIsDisplayed()
+ composeTestRule.onNodeWithText(DESCRIPTION.component2()).assertIsNotDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ val DESCRIPTION = listOf("Description", "")
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt
new file mode 100644
index 0000000..62a71d4
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TopIntroPreferenceTest {
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun content_collapsed_displayed() {
+ composeTestRule.setContent {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text = TEXT
+ override val expandText = EXPAND_TEXT
+ override val collapseText = COLLAPSE_TEXT
+ override val labelText = R.string.test_top_intro_preference_label
+ }
+ )
+ }
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+ composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed()
+ }
+
+ @Test
+ fun content_expended_displayed() {
+ composeTestRule.setContent {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text = TEXT
+ override val expandText = EXPAND_TEXT
+ override val collapseText = COLLAPSE_TEXT
+ override val labelText = R.string.test_top_intro_preference_label
+ }
+ )
+ }
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+ composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed().performClick()
+ composeTestRule.onNodeWithText(COLLAPSE_TEXT).assertIsDisplayed()
+ composeTestRule.onNodeWithText(LABEL_TEXT).assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TEXT = "Text"
+ const val EXPAND_TEXT = "Expand"
+ const val COLLAPSE_TEXT = "Collapse"
+ const val LABEL_TEXT = "Label"
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt
new file mode 100644
index 0000000..99ac27c
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ZeroStatePreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ ZeroStatePreference(Icons.Filled.History, TITLE)
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun description_displayed() {
+ composeTestRule.setContent {
+ ZeroStatePreference(Icons.Filled.History, TITLE, DESCRIPTION)
+ }
+
+ composeTestRule.onNodeWithText(DESCRIPTION).assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val DESCRIPTION = "Description"
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml
new file mode 100644
index 0000000..4347ef2
--- /dev/null
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:clipToPadding="false">
+
+ <include layout="@layout/settingslib_expressive_preference_icon_frame"/>
+
+ <include layout="@layout/settingslib_expressive_preference_text_frame" />
+
+ <include layout="@layout/settingslib_expressive_two_target_divider" />
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
index b125f71..58ff0ce 100644
--- a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
+++ b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
@@ -72,7 +72,10 @@
}
private void init(Context context) {
- setLayoutResource(R.layout.preference_two_target);
+ int resID = SettingsThemeHelper.isExpressiveTheme(context)
+ ? R.layout.settingslib_expressive_preference_two_target
+ : R.layout.preference_two_target;
+ setLayoutResource(resID);
mSmallIconSize = context.getResources().getDimensionPixelSize(
R.dimen.two_target_pref_small_icon_size);
mMediumIconSize = context.getResources().getDimensionPixelSize(
diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
index e41126f..2475c8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -31,6 +31,8 @@
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.core.instrumentation.SettingsJankMonitor;
+import com.android.settingslib.widget.SettingsThemeHelper;
+import com.android.settingslib.widget.theme.R;
/**
* A custom preference that provides inline switch toggle. It has a mandatory field for title, and
@@ -62,7 +64,9 @@
@Override
protected int getSecondTargetResId() {
- return androidx.preference.R.layout.preference_widget_switch_compat;
+ return SettingsThemeHelper.isExpressiveTheme(getContext())
+ ? R.layout.settingslib_expressive_preference_switch
+ : androidx.preference.R.layout.preference_widget_switch_compat;
}
@Override
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index f64c305..749ad0a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -967,7 +967,7 @@
for (int i = 0; i < nameCount; i++) {
String name = names.get(i);
Setting setting = settingsState.getSettingLocked(name);
- pw.print("_id:"); pw.print(toDumpString(setting.getId()));
+ pw.print("_id:"); pw.print(toDumpString(String.valueOf(setting.getId())));
pw.print(" name:"); pw.print(toDumpString(name));
if (setting.getPackageName() != null) {
pw.print(" pkg:"); pw.print(setting.getPackageName());
@@ -2785,7 +2785,7 @@
switch (column) {
case Settings.NameValueTable._ID -> {
- values[i] = setting.getId();
+ values[i] = String.valueOf(setting.getId());
}
case Settings.NameValueTable.NAME -> {
values[i] = setting.getName();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 452edd9..3c634f0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -517,6 +517,7 @@
}
String namespace = name.substring(0, slashIdx);
+ namespace = namespace.intern(); // Many configs have the same namespace.
String fullFlagName = name.substring(slashIdx + 1);
boolean isLocal = false;
@@ -566,7 +567,7 @@
}
try {
localCounter = Integer.parseInt(markerSetting.value);
- } catch(NumberFormatException e) {
+ } catch (NumberFormatException e) {
// reset local counter
markerSetting.value = "0";
}
@@ -1363,7 +1364,10 @@
}
try {
- if (writeSingleSetting(mVersion, serializer, setting.getId(),
+ if (writeSingleSetting(
+ mVersion,
+ serializer,
+ Long.toString(setting.getId()),
setting.getName(),
setting.getValue(), setting.getDefaultValue(),
setting.getPackageName(),
@@ -1632,7 +1636,7 @@
TypedXmlPullParser parser = Xml.resolvePullParser(in);
parseStateLocked(parser);
return true;
- } catch (XmlPullParserException | IOException e) {
+ } catch (XmlPullParserException | IOException | NumberFormatException e) {
Slog.e(LOG_TAG, "parse settings xml failed", e);
return false;
} finally {
@@ -1652,7 +1656,7 @@
}
private void parseStateLocked(TypedXmlPullParser parser)
- throws IOException, XmlPullParserException {
+ throws IOException, XmlPullParserException, NumberFormatException {
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1708,7 +1712,7 @@
@GuardedBy("mLock")
private void parseSettingsLocked(TypedXmlPullParser parser)
- throws IOException, XmlPullParserException {
+ throws IOException, XmlPullParserException, NumberFormatException {
mVersion = parser.getAttributeInt(null, ATTR_VERSION);
@@ -1776,7 +1780,7 @@
}
}
mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
- fromSystem, id, isPreservedInRestore));
+ fromSystem, Long.valueOf(id), isPreservedInRestore));
if (DEBUG_PERSISTENCE) {
Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value);
@@ -1866,7 +1870,7 @@
private String value;
private String defaultValue;
private String packageName;
- private String id;
+ private long id;
private String tag;
// Whether the default is set by the system
private boolean defaultFromSystem;
@@ -1898,30 +1902,27 @@
}
public Setting(String name, String value, String defaultValue,
- String packageName, String tag, boolean fromSystem, String id) {
+ String packageName, String tag, boolean fromSystem, long id) {
this(name, value, defaultValue, packageName, tag, fromSystem, id,
/* isOverrideableByRestore */ false);
}
Setting(String name, String value, String defaultValue,
- String packageName, String tag, boolean fromSystem, String id,
+ String packageName, String tag, boolean fromSystem, long id,
boolean isValuePreservedInRestore) {
- mNextId = Math.max(mNextId, Long.parseLong(id) + 1);
- if (NULL_VALUE.equals(value)) {
- value = null;
- }
+ mNextId = Math.max(mNextId, id + 1);
init(name, value, tag, defaultValue, packageName, fromSystem, id,
isValuePreservedInRestore);
}
private void init(String name, String value, String tag, String defaultValue,
- String packageName, boolean fromSystem, String id,
+ String packageName, boolean fromSystem, long id,
boolean isValuePreservedInRestore) {
this.name = name;
- this.value = value;
+ this.value = internValue(value);
this.tag = tag;
- this.defaultValue = defaultValue;
- this.packageName = packageName;
+ this.defaultValue = internValue(defaultValue);
+ this.packageName = TextUtils.safeIntern(packageName);
this.id = id;
this.defaultFromSystem = fromSystem;
this.isValuePreservedInRestore = isValuePreservedInRestore;
@@ -1959,7 +1960,7 @@
return isValuePreservedInRestore;
}
- public String getId() {
+ public long getId() {
return id;
}
@@ -1992,9 +1993,6 @@
private boolean update(String value, boolean setDefault, String packageName, String tag,
boolean forceNonSystemPackage, boolean overrideableByRestore,
boolean resetToDefault) {
- if (NULL_VALUE.equals(value)) {
- value = null;
- }
final boolean callerSystem = !forceNonSystemPackage &&
!isNull() && (isCalledFromSystem(packageName)
|| isSystemPackage(mContext, packageName));
@@ -2039,7 +2037,7 @@
}
init(name, value, tag, defaultValue, packageName, defaultFromSystem,
- String.valueOf(mNextId++), isPreserved);
+ mNextId++, isPreserved);
return true;
}
@@ -2051,6 +2049,32 @@
+ " defaultFromSystem=" + defaultFromSystem + "}";
}
+ /**
+ * Interns a string if it's a common setting value.
+ * Otherwise returns the given string.
+ */
+ static String internValue(String str) {
+ if (str == null) {
+ return null;
+ }
+ switch (str) {
+ case "true":
+ return "true";
+ case "false":
+ return "false";
+ case "0":
+ return "0";
+ case "1":
+ return "1";
+ case "":
+ return "";
+ case "null":
+ return null; // explicit null has special handling
+ default:
+ return str;
+ }
+ }
+
private boolean shouldPreserveSetting(boolean overrideableByRestore,
boolean resetToDefault, String packageName, String value) {
if (resetToDefault) {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c49ffb4..f8383d9 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -149,16 +149,6 @@
}
flag {
- name: "modes_dialog_single_rows"
- namespace: "systemui"
- description: "[Experiment] Display one entry per grid row in the Modes Dialog."
- bug: "366034002"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "pss_app_selector_recents_split_screen"
namespace: "systemui"
description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
@@ -599,6 +589,16 @@
}
flag {
+ name: "clipboard_use_description_mimetype"
+ namespace: "systemui"
+ description: "Read item mimetype from description rather than checking URI"
+ bug: "357197236"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "screenshot_action_dismiss_system_windows"
namespace: "systemui"
description: "Dismiss existing system windows when starting action from screenshot UI"
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/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 1b99a96..fe4a65b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -130,8 +130,8 @@
fun SceneScope.HeadsUpNotificationSpace(
stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
+ useHunBounds: () -> Boolean = { true },
modifier: Modifier = Modifier,
- isPeekFromBottom: Boolean = false,
) {
Box(
modifier =
@@ -141,17 +141,25 @@
.notificationHeadsUpHeight(stackScrollView)
.debugBackground(viewModel, DEBUG_HUN_COLOR)
.onGloballyPositioned { coordinates: LayoutCoordinates ->
- val positionInWindow = coordinates.positionInWindow()
- val boundsInWindow = coordinates.boundsInWindow()
- debugLog(viewModel) {
- "HUNS onGloballyPositioned:" +
- " size=${coordinates.size}" +
- " bounds=$boundsInWindow"
+ // This element is sometimes opted out of the shared element system, so there
+ // can be multiple instances of it during a transition. Thus we need to
+ // determine which instance should feed its bounds to NSSL to avoid providing
+ // conflicting values
+ val useBounds = useHunBounds()
+ if (useBounds) {
+ val positionInWindow = coordinates.positionInWindow()
+ val boundsInWindow = coordinates.boundsInWindow()
+ debugLog(viewModel) {
+ "HUNS onGloballyPositioned:" +
+ " size=${coordinates.size}" +
+ " bounds=$boundsInWindow"
+ }
+ // Note: boundsInWindow doesn't scroll off the screen, so use
+ // positionInWindow
+ // for top bound, which can scroll off screen while snoozing
+ stackScrollView.setHeadsUpTop(positionInWindow.y)
+ stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
}
- // Note: boundsInWindow doesn't scroll off the screen, so use positionInWindow
- // for top bound, which can scroll off screen while snoozing
- stackScrollView.setHeadsUpTop(positionInWindow.y)
- stackScrollView.setHeadsUpBottom(boundsInWindow.bottom)
}
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index d91958a..0c69dbd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -416,11 +416,11 @@
HeadsUpNotificationSpace(
stackScrollView = notificationStackScrollView,
viewModel = notificationsPlaceholderViewModel,
+ useHunBounds = { shouldUseQuickSettingsHunBounds(layoutState.transitionState) },
modifier =
Modifier.align(Alignment.BottomCenter)
.navigationBarsPadding()
.padding(horizontal = shadeHorizontalPadding),
- isPeekFromBottom = true,
)
NotificationScrollingStack(
shadeSession = shadeSession,
@@ -446,3 +446,7 @@
)
}
}
+
+private fun shouldUseQuickSettingsHunBounds(state: TransitionState): Boolean {
+ return state is TransitionState.Idle && state.currentScene == Scenes.QuickSettings
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index f64d0ed..58fbf43 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -77,6 +77,10 @@
}
from(Scenes.Lockscreen, to = Scenes.QuickSettings) { lockscreenToQuickSettingsTransition() }
from(Scenes.Lockscreen, to = Scenes.Gone) { lockscreenToGoneTransition() }
+ from(Scenes.QuickSettings, to = Scenes.Shade) {
+ reversed { shadeToQuickSettingsTransition() }
+ sharedElement(Notifications.Elements.HeadsUpNotificationPlaceholder, enabled = false)
+ }
from(Scenes.Shade, to = Scenes.QuickSettings) { shadeToQuickSettingsTransition() }
// Overlay transitions
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/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/NumPadAnimatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/NumPadAnimatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeHintingViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeHintingViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/TestScopeProvider.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/TestScopeProvider.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
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/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index 2ba4bf9..e25c1a7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -34,8 +34,6 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.DozeStateModel
-import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
@@ -50,8 +48,6 @@
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
@@ -220,15 +216,11 @@
@Test
fun transition_from_hub_end_in_dream() =
testScope.runTest {
- // Device is dreaming and not dozing.
- kosmos.powerInteractor.setAwakeForTest()
- kosmos.fakeKeyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
- )
+ // Device is dreaming and occluded.
kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
kosmos.fakeKeyguardRepository.setDreaming(true)
kosmos.fakeKeyguardRepository.setDreamingWithOverlay(true)
- advanceTimeBy(600L)
+ runCurrent()
sceneTransitions.value = hubToBlank
@@ -663,7 +655,7 @@
from = LOCKSCREEN,
to = OCCLUDED,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -750,7 +742,7 @@
from = LOCKSCREEN,
to = OCCLUDED,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -852,8 +844,8 @@
to = ALTERNATE_BOUNCER,
animator = null,
ownerName = "external",
- modeOnCanceled = TransitionModeOnCanceled.RESET
- ),
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ )
)
val allSteps by collectValues(keyguardTransitionRepository.transitions)
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/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
index 9da6885..52ed231 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
@@ -19,11 +19,13 @@
import android.content.Context
import android.content.Intent
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
+import com.android.systemui.shared.Flags
import com.android.systemui.testKosmos
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,16 +45,17 @@
KeyboardTouchpadTutorialCoreStartable(
{ mock<TutorialNotificationCoordinator>() },
broadcastDispatcher,
- context
+ context,
)
@Test
+ @EnableFlags(Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL)
fun registersBroadcastReceiverStartingActivityAsSystemUser() {
underTest.start()
broadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
- Intent("com.android.systemui.action.KEYBOARD_TOUCHPAD_TUTORIAL")
+ Intent("com.android.systemui.action.KEYBOARD_TOUCHPAD_TUTORIAL"),
)
verify(context).startActivityAsUser(any(), eq(UserHandle.SYSTEM))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index bd6cfff..93754fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -288,6 +289,7 @@
}
@Test
+ @DisableSceneContainer
fun dozeAmount() =
testScope.runTest {
val values = mutableListOf<Float>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index fac9312..ff0a4a1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -26,8 +26,10 @@
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
+import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -50,10 +52,12 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -219,6 +223,28 @@
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun testTransitionToGlanceableHub_onWakeup_ifAvailable() =
+ testScope.runTest {
+ // Hub is available.
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ // Device turns on.
+ powerInteractor.setAwakeForTest()
+ advanceTimeBy(50L)
+ runCurrent()
+
+ // We transition to the hub when waking up.
+ Truth.assertThat(kosmos.communalSceneRepository.currentScene.value)
+ .isEqualTo(CommunalScenes.Communal)
+ // No transitions are directly started by this interactor.
+ assertThat(transitionRepository).noTransitionsStarted()
+ }
+
+ @Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 638c957..a08fbbf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -16,12 +16,19 @@
package com.android.systemui.keyguard.domain.interactor
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
+import android.service.dream.dreamManager
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -36,6 +43,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -43,26 +51,52 @@
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class FromDreamingTransitionInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class FromDreamingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ .andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
private val kosmos =
testKosmos().apply {
this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
}
private val testScope = kosmos.testScope
- private val underTest = kosmos.fromDreamingTransitionInteractor
+ private val underTest by lazy { kosmos.fromDreamingTransitionInteractor }
private val powerInteractor = kosmos.powerInteractor
private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
@Before
fun setup() {
+ runBlocking {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ reset(transitionRepository)
+ kosmos.setCommunalAvailable(true)
+ }
underTest.start()
}
@@ -86,10 +120,7 @@
runCurrent()
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DREAMING,
- to = KeyguardState.OCCLUDED,
- )
+ .startedTransition(from = KeyguardState.DREAMING, to = KeyguardState.OCCLUDED)
}
@Test
@@ -126,7 +157,7 @@
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.DREAMING,
- testScope
+ testScope,
)
kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
@@ -139,10 +170,7 @@
advanceTimeBy(60L)
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DREAMING,
- to = KeyguardState.LOCKSCREEN,
- )
+ .startedTransition(from = KeyguardState.DREAMING, to = KeyguardState.LOCKSCREEN)
}
@Test
@@ -164,4 +192,25 @@
to = KeyguardState.ALTERNATE_BOUNCER,
)
}
+
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+ fun testTransitionToGlanceableHubOnWake() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ // Device wakes up.
+ powerInteractor.setAwakeForTest()
+ advanceTimeBy(150L)
+ runCurrent()
+
+ // We transition to the hub when waking up.
+ assertThat(kosmos.communalSceneRepository.currentScene.value)
+ .isEqualTo(CommunalScenes.Communal)
+ // No transitions are directly started by this interactor.
+ assertThat(transitionRepository).noTransitionsStarted()
+ }
}
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/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index b843fd5..3fb3eea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -29,15 +29,21 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchType
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -67,12 +73,11 @@
private val testScope = kosmos.testScope
private val repository by lazy { kosmos.fakeKeyguardRepository }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
- private val commandQueue by lazy { kosmos.fakeCommandQueue }
private val configRepository by lazy { kosmos.fakeConfigurationRepository }
private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository }
private val shadeRepository by lazy { kosmos.shadeRepository }
private val powerInteractor by lazy { kosmos.powerInteractor }
+ private val keyguardRepository by lazy { kosmos.keyguardRepository }
private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private val transitionState: MutableStateFlow<ObservableTransitionState> =
@@ -178,8 +183,8 @@
assertThat(dismissAlpha).isEqualTo(1f)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ from = AOD,
+ to = LOCKSCREEN,
testScope,
)
@@ -204,8 +209,8 @@
assertThat(dismissAlpha.size).isEqualTo(1)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ from = AOD,
+ to = LOCKSCREEN,
testScope,
)
@@ -266,13 +271,13 @@
keyguardTransitionRepository.sendTransitionSteps(
listOf(
TransitionStep(
- from = KeyguardState.AOD,
+ from = AOD,
to = KeyguardState.GONE,
value = 0f,
- transitionState = TransitionState.STARTED,
+ transitionState = STARTED,
),
TransitionStep(
- from = KeyguardState.AOD,
+ from = AOD,
to = KeyguardState.GONE,
value = 0.1f,
transitionState = TransitionState.RUNNING,
@@ -302,7 +307,7 @@
shadeRepository.setLegacyShadeExpansion(0f)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
+ from = AOD,
to = KeyguardState.GONE,
testScope,
)
@@ -324,8 +329,8 @@
shadeRepository.setLegacyShadeExpansion(0f)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ from = AOD,
+ to = LOCKSCREEN,
testScope,
)
@@ -346,8 +351,8 @@
shadeRepository.setLegacyShadeExpansion(1f)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ from = AOD,
+ to = LOCKSCREEN,
testScope,
)
@@ -370,13 +375,13 @@
keyguardTransitionRepository.sendTransitionSteps(
listOf(
TransitionStep(
- from = KeyguardState.AOD,
+ from = AOD,
to = KeyguardState.GONE,
value = 0f,
- transitionState = TransitionState.STARTED,
+ transitionState = STARTED,
),
TransitionStep(
- from = KeyguardState.AOD,
+ from = AOD,
to = KeyguardState.GONE,
value = 0.1f,
transitionState = TransitionState.RUNNING,
@@ -468,4 +473,63 @@
runCurrent()
assertThat(isAnimate).isFalse()
}
+
+ @Test
+ @EnableSceneContainer
+ fun dozeAmount_updatedByAodTransitionWhenAodEnabled() =
+ testScope.runTest {
+ val dozeAmount by collectLastValue(underTest.dozeAmount)
+
+ keyguardRepository.setAodAvailable(true)
+
+ sendTransitionStep(TransitionStep(to = AOD, value = 0f, transitionState = STARTED))
+ assertThat(dozeAmount).isEqualTo(0f)
+
+ sendTransitionStep(TransitionStep(to = AOD, value = 0.5f, transitionState = RUNNING))
+ assertThat(dozeAmount).isEqualTo(0.5f)
+
+ sendTransitionStep(TransitionStep(to = AOD, value = 1f, transitionState = FINISHED))
+ assertThat(dozeAmount).isEqualTo(1f)
+
+ sendTransitionStep(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ assertThat(dozeAmount).isEqualTo(1f)
+
+ sendTransitionStep(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ assertThat(dozeAmount).isEqualTo(0.5f)
+
+ sendTransitionStep(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ assertThat(dozeAmount).isEqualTo(0f)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun dozeAmount_updatedByDozeTransitionWhenAodDisabled() =
+ testScope.runTest {
+ val dozeAmount by collectLastValue(underTest.dozeAmount)
+
+ keyguardRepository.setAodAvailable(false)
+
+ sendTransitionStep(TransitionStep(to = DOZING, value = 0f, transitionState = STARTED))
+ assertThat(dozeAmount).isEqualTo(0f)
+
+ sendTransitionStep(TransitionStep(to = DOZING, value = 0.5f, transitionState = RUNNING))
+ assertThat(dozeAmount).isEqualTo(0.5f)
+
+ sendTransitionStep(TransitionStep(to = DOZING, value = 1f, transitionState = FINISHED))
+ assertThat(dozeAmount).isEqualTo(1f)
+
+ sendTransitionStep(TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED))
+ assertThat(dozeAmount).isEqualTo(1f)
+
+ sendTransitionStep(TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING))
+ assertThat(dozeAmount).isEqualTo(0.5f)
+
+ sendTransitionStep(TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED))
+ assertThat(dozeAmount).isEqualTo(0f)
+ }
+
+ private suspend fun sendTransitionStep(step: TransitionStep) {
+ keyguardTransitionRepository.sendTransitionStep(step)
+ testScope.runCurrent()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt
new file mode 100644
index 0000000..2558d58
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.policy.IKeyguardDismissCallback
+import com.android.internal.policy.IKeyguardStateCallback
+import com.android.keyguard.trustManager
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.dismissCallbackRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.domain.interactor.keyguardStateCallbackInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.fakeSystemClock
+import kotlin.test.Test
+import kotlinx.coroutines.test.currentTime
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.kotlin.atLeast
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardStateCallbackInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: KeyguardStateCallbackInteractor
+ private lateinit var callback: IKeyguardStateCallback
+ private lateinit var systemClock: FakeSystemClock
+
+ @Before
+ fun setUp() {
+ systemClock = kosmos.fakeSystemClock
+ systemClock.setCurrentTimeMillis(testScope.currentTime)
+
+ underTest = kosmos.keyguardStateCallbackInteractor
+ underTest.start()
+
+ callback = mock<IKeyguardStateCallback>()
+ }
+
+ @Test
+ fun test_addCallback_passesInitialValues() =
+ testScope.runTest {
+ underTest.addCallback(callback)
+
+ verify(callback).onShowingStateChanged(anyBoolean(), anyInt())
+ verify(callback).onInputRestrictedStateChanged(anyBoolean())
+ verify(callback).onTrustedChanged(anyBoolean())
+ verify(callback).onSimSecureStateChanged(anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun test_lockscreenVisibility_notifyDismissSucceeded_ifNotVisible() =
+ testScope.runTest {
+ underTest.addCallback(callback)
+
+ val dismissCallback = mock<IKeyguardDismissCallback>()
+ kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
+ runCurrent()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+
+ systemClock.advanceTime(1) // Required for DismissCallbackRegistry's bgExecutor
+ verify(dismissCallback).onDismissSucceeded()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+
+ Mockito.verifyNoMoreInteractions(dismissCallback)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun test_lockscreenVisibility_reportsKeyguardShowingChanged() =
+ testScope.runTest {
+ underTest.addCallback(callback)
+
+ Mockito.clearInvocations(callback)
+ Mockito.clearInvocations(kosmos.trustManager)
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+ runCurrent()
+
+ verify(callback, atLeastOnce()).onShowingStateChanged(eq(false), anyInt())
+ verify(kosmos.trustManager, atLeastOnce()).reportKeyguardShowingChanged()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+
+ verify(callback, atLeastOnce()).onShowingStateChanged(eq(true), anyInt())
+ verify(kosmos.trustManager, atLeast(2)).reportKeyguardShowingChanged()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 77106ae..a617484 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1465,10 +1465,8 @@
// WHEN the keyguard is occluded and device wakes up and is no longer dreaming
keyguardRepository.setDreaming(false)
- testScheduler.advanceTimeBy(150) // The dreaming signal is debounced.
- runCurrent()
keyguardRepository.setKeyguardOccluded(true)
- powerInteractor.setAwakeForTest()
+ testScheduler.advanceTimeBy(150) // The dreaming and occluded signals are debounced.
runCurrent()
// THEN a transition to OCCLUDED should occur
@@ -2059,12 +2057,7 @@
fun glanceableHubToOccluded_communalKtfRefactor() =
testScope.runTest {
// GIVEN device is not dreaming
- powerInteractor.setAwakeForTest()
keyguardRepository.setDreaming(false)
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
- )
- advanceTimeBy(600.milliseconds)
// GIVEN a prior transition has run to GLANCEABLE_HUB
communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
@@ -2073,6 +2066,7 @@
// WHEN the keyguard is occluded
keyguardRepository.setKeyguardOccluded(true)
+ advanceTimeBy(200.milliseconds)
runCurrent()
assertThat(transitionRepository)
@@ -2218,6 +2212,7 @@
advanceTimeBy(10.milliseconds)
keyguardRepository.setKeyguardOccluded(true)
advanceTimeBy(200.milliseconds)
+ runCurrent()
assertThat(transitionRepository)
.startedTransition(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index f31eb7f..309e3a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -27,11 +27,18 @@
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeLockscreenInteractor
@@ -62,6 +69,7 @@
val kosmos = testKosmos()
val testScope = kosmos.testScope
val keyguardRepository = kosmos.fakeKeyguardRepository
+ val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
val shadeRepository = kosmos.fakeShadeRepository
val shadeTestUtil by lazy { kosmos.shadeTestUtil }
@@ -132,8 +140,15 @@
assertThat(burnInOffsets?.x).isEqualTo(0)
// WHEN we're in the middle of the doze amount change
- keyguardRepository.setDozeAmount(.50f)
- runCurrent()
+ if (SceneContainerFlag.isEnabled) {
+ sendTransitionSteps(
+ TransitionStep(to = DOZING, value = 0.0f, transitionState = STARTED),
+ TransitionStep(to = DOZING, value = 0.5f, transitionState = RUNNING),
+ )
+ } else {
+ keyguardRepository.setDozeAmount(.50f)
+ runCurrent()
+ }
// THEN burn in is updated (between 0 and the full offset)
assertThat(burnInOffsets?.progress).isGreaterThan(0f)
@@ -144,8 +159,14 @@
assertThat(burnInOffsets?.x).isLessThan(burnInXOffset)
// WHEN we're fully dozing
- keyguardRepository.setDozeAmount(1f)
- runCurrent()
+ if (SceneContainerFlag.isEnabled) {
+ sendTransitionSteps(
+ TransitionStep(to = DOZING, value = 1.0f, transitionState = FINISHED)
+ )
+ } else {
+ keyguardRepository.setDozeAmount(1f)
+ runCurrent()
+ }
// THEN burn in offsets are updated to final current values (for the given time)
assertThat(burnInOffsets?.progress).isEqualTo(burnInProgress)
@@ -217,7 +238,9 @@
}
private fun setAwake() {
- keyguardRepository.setDozeAmount(0f)
+ if (!SceneContainerFlag.isEnabled) {
+ keyguardRepository.setDozeAmount(0f)
+ }
keyguardRepository.dozeTimeTick()
bouncerRepository.setAlternateVisible(false)
@@ -225,4 +248,11 @@
bouncerRepository.setPrimaryShow(false)
powerInteractor.setAwakeForTest()
}
+
+ private suspend fun sendTransitionSteps(vararg steps: TransitionStep) {
+ steps.forEach { step ->
+ keyguardTransitionRepository.sendTransitionStep(step)
+ testScope.runCurrent()
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
index e58cf15..79a303d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractorTest.kt
@@ -85,12 +85,12 @@
runCurrent()
// Assert that the tile is removed from the large tiles after resizing
- underTest.resize(largeTile)
+ underTest.resize(largeTile, toIcon = true)
runCurrent()
assertThat(latest).doesNotContain(largeTile)
// Assert that the tile is added to the large tiles after resizing
- underTest.resize(largeTile)
+ underTest.resize(largeTile, toIcon = false)
runCurrent()
assertThat(latest).contains(largeTile)
}
@@ -122,7 +122,7 @@
val newTile = TileSpec.create("newTile")
// Remove the large tile from the current tiles
- underTest.resize(newTile)
+ underTest.resize(newTile, toIcon = false)
runCurrent()
// Assert that it's still small
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index 484a8ff..3910903 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -24,7 +24,6 @@
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.model.GridCell
-import com.android.systemui.qs.panels.ui.model.SpacerGridCell
import com.android.systemui.qs.panels.ui.model.TileGridCell
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -39,13 +38,6 @@
private val underTest = EditTileListState(TestEditTiles, 4)
@Test
- fun noDrag_listUnchanged() {
- underTest.tiles.forEach { assertThat(it).isNotInstanceOf(SpacerGridCell::class.java) }
- assertThat(underTest.tiles.map { (it as TileGridCell).tile.tileSpec })
- .containsExactly(*TestEditTiles.map { it.tile.tileSpec }.toTypedArray())
- }
-
- @Test
fun startDrag_listHasSpacers() {
underTest.onStarted(TestEditTiles[0])
@@ -109,16 +101,6 @@
}
@Test
- fun droppedNewTile_spacersDisappear() {
- underTest.onStarted(TestEditTiles[0])
- underTest.onDrop()
-
- assertThat(underTest.tiles.toStrings()).isEqualTo(listOf("a", "b", "c", "d", "e"))
- assertThat(underTest.isMoving(TestEditTiles[0].tile.tileSpec)).isFalse()
- assertThat(underTest.dragInProgress).isFalse()
- }
-
- @Test
fun movedTileOutOfBounds_tileDisappears() {
underTest.onStarted(TestEditTiles[0])
underTest.movedOutOfBounds()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
index fa72d74..4acf3ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
@@ -27,23 +27,25 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class MutableSelectionStateTest : SysuiTestCase() {
- private val underTest = MutableSelectionState()
+ private val underTest = MutableSelectionState({}, {})
@Test
fun selectTile_isCorrectlySelected() {
- assertThat(underTest.isSelected(TEST_SPEC)).isFalse()
+ assertThat(underTest.selection?.tileSpec).isNotEqualTo(TEST_SPEC)
- underTest.select(TEST_SPEC)
- assertThat(underTest.isSelected(TEST_SPEC)).isTrue()
+ underTest.select(TEST_SPEC, manual = true)
+ assertThat(underTest.selection?.tileSpec).isEqualTo(TEST_SPEC)
+ assertThat(underTest.selection?.manual).isTrue()
underTest.unSelect()
- assertThat(underTest.isSelected(TEST_SPEC)).isFalse()
+ assertThat(underTest.selection).isNull()
val newSpec = TileSpec.create("newSpec")
- underTest.select(TEST_SPEC)
- underTest.select(newSpec)
- assertThat(underTest.isSelected(TEST_SPEC)).isFalse()
- assertThat(underTest.isSelected(newSpec)).isTrue()
+ underTest.select(TEST_SPEC, manual = true)
+ underTest.select(newSpec, manual = false)
+ assertThat(underTest.selection?.tileSpec).isNotEqualTo(TEST_SPEC)
+ assertThat(underTest.selection?.tileSpec).isEqualTo(newSpec)
+ assertThat(underTest.selection?.manual).isFalse()
}
@Test
@@ -51,12 +53,12 @@
assertThat(underTest.resizingState).isNull()
// Resizing starts but no tile is selected
- underTest.onResizingDragStart(TileWidths(0, 0, 1)) {}
+ underTest.onResizingDragStart(TileWidths(0, 0, 1))
assertThat(underTest.resizingState).isNull()
// Resizing starts with a selected tile
- underTest.select(TEST_SPEC)
- underTest.onResizingDragStart(TileWidths(0, 0, 1)) {}
+ underTest.select(TEST_SPEC, manual = true)
+ underTest.onResizingDragStart(TileWidths(0, 0, 1))
assertThat(underTest.resizingState).isNotNull()
}
@@ -66,8 +68,8 @@
val spec = TileSpec.create("testSpec")
// Resizing starts with a selected tile
- underTest.select(spec)
- underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {}
+ underTest.select(spec, manual = true)
+ underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
assertThat(underTest.resizingState).isNotNull()
underTest.onResizingDragEnd()
@@ -77,8 +79,8 @@
@Test
fun unselect_clearsResizingState() {
// Resizing starts with a selected tile
- underTest.select(TEST_SPEC)
- underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {}
+ underTest.select(TEST_SPEC, manual = true)
+ underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
assertThat(underTest.resizingState).isNotNull()
underTest.unSelect()
@@ -88,8 +90,8 @@
@Test
fun onResizingDrag_updatesResizingState() {
// Resizing starts with a selected tile
- underTest.select(TEST_SPEC)
- underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10)) {}
+ underTest.select(TEST_SPEC, manual = true)
+ underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
assertThat(underTest.resizingState).isNotNull()
underTest.onResizingDrag(5f)
@@ -105,11 +107,15 @@
@Test
fun onResizingDrag_receivesResizeCallback() {
var resized = false
- val onResize: () -> Unit = { resized = !resized }
+ val onResize: (TileSpec) -> Unit = {
+ assertThat(it).isEqualTo(TEST_SPEC)
+ resized = !resized
+ }
+ val underTest = MutableSelectionState(onResize = onResize, {})
// Resizing starts with a selected tile
- underTest.select(TEST_SPEC)
- underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10), onResize)
+ underTest.select(TEST_SPEC, true)
+ underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
assertThat(underTest.resizingState).isNotNull()
// Drag under the threshold
@@ -125,6 +131,37 @@
assertThat(resized).isFalse()
}
+ @Test
+ fun onResizingEnded_receivesResizeEndCallback() {
+ var resizeEnded = false
+ val onResizeEnd: (TileSpec) -> Unit = { resizeEnded = true }
+ val underTest = MutableSelectionState({}, onResizeEnd = onResizeEnd)
+
+ // Resizing starts with a selected tile
+ underTest.select(TEST_SPEC, true)
+ underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
+
+ underTest.onResizingDragEnd()
+ assertThat(resizeEnded).isTrue()
+ }
+
+ @Test
+ fun onResizingEnded_setsSelectionAutomatically() {
+ val underTest = MutableSelectionState({}, {})
+
+ // Resizing starts with a selected tile
+ underTest.select(TEST_SPEC, manual = true)
+ underTest.onResizingDragStart(TileWidths(base = 0, min = 0, max = 10))
+
+ // Assert the selection was manual
+ assertThat(underTest.selection?.manual).isTrue()
+
+ underTest.onResizingDragEnd()
+
+ // Assert the selection is no longer manual due to the resizing
+ assertThat(underTest.selection?.manual).isFalse()
+ }
+
companion object {
private val TEST_SPEC = TileSpec.create("testSpec")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index f72a2e8..aefbc6b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
@@ -107,6 +108,7 @@
uiEventLogger,
{ kosmos.interactionJankMonitor },
JavaAdapter(testScope.backgroundScope),
+ { kosmos.keyguardInteractor },
{ kosmos.keyguardTransitionInteractor },
{ kosmos.shadeInteractor },
{ kosmos.deviceUnlockedInteractor },
@@ -139,6 +141,7 @@
}
@Test
+ @DisableSceneContainer
fun testSetDozeAmountInternal_onlySetsOnce() {
val listener = mock(StatusBarStateController.StateListener::class.java)
underTest.addCallback(listener)
@@ -190,6 +193,7 @@
}
@Test
+ @DisableSceneContainer
fun testSetDozeAmount_immediatelyChangesDozeAmount_lockscreenTransitionFromAod() {
// Put controller in AOD state
underTest.setAndInstrumentDozeAmount(null, 1f, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 425f16e..4adf693 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -44,6 +44,7 @@
import com.android.systemui.keyguard.shared.model.BurnInModel
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -363,6 +364,69 @@
showLockscreen()
assertThat(alpha).isEqualTo(1f)
+ // Go to dozing
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = LOCKSCREEN,
+ to = DOZING,
+ testScope,
+ )
+ assertThat(alpha).isEqualTo(1f)
+
+ // Start transitioning to glanceable hub
+ val progress = 0.6f
+ kosmos.setTransition(
+ sceneTransition = Transition(from = Scenes.Lockscreen, to = Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = DOZING,
+ to = GLANCEABLE_HUB,
+ value = 0f,
+ ),
+ )
+ runCurrent()
+ kosmos.setTransition(
+ sceneTransition =
+ Transition(
+ from = Scenes.Lockscreen,
+ to = Scenes.Communal,
+ progress = flowOf(progress),
+ ),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = DOZING,
+ to = GLANCEABLE_HUB,
+ value = progress,
+ ),
+ )
+ runCurrent()
+ // Keep notifications hidden during the transition from dream to hub
+ assertThat(alpha).isEqualTo(0)
+
+ // Finish transition to glanceable hub
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = DOZING,
+ to = GLANCEABLE_HUB,
+ value = 1f,
+ ),
+ )
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
+ fun glanceableHubAlpha_dozingToHub() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.glanceableHubAlpha)
+
+ // Start on lockscreen, notifications should be unhidden.
+ showLockscreen()
+ assertThat(alpha).isEqualTo(1f)
+
// Transition to dream, notifications should be hidden so that transition
// from dream->hub doesn't cause notification flicker.
showDream()
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a3db776..24b6579 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1482,7 +1482,7 @@
<!-- Text which is shown in the expanded notification shade when there are currently no notifications visible that the user hasn't already seen. [CHAR LIMIT=30] -->
<string name="no_unseen_notif_text">No new notifications</string>
- <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=50] -->
+ <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=60] -->
<string name="adaptive_notification_edu_hun_title">Notification cooldown is on</string>
<!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] -->
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/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/clipboardoverlay/ClipboardModel.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
index 12597a7..99c026c 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
@@ -24,6 +24,7 @@
import android.util.Log
import android.util.Size
import android.view.textclassifier.TextLinks
+import com.android.systemui.Flags.clipboardUseDescriptionMimetype
import com.android.systemui.res.R
import java.io.IOException
@@ -70,11 +71,11 @@
context: Context,
utils: ClipboardOverlayUtils,
clipData: ClipData,
- source: String
+ source: String,
): ClipboardModel {
val sensitive = clipData.description?.extras?.getBoolean(EXTRA_IS_SENSITIVE) ?: false
val item = clipData.getItemAt(0)!!
- val type = getType(context, item)
+ val type = getType(context, item, clipData.description.getMimeType(0))
val remote = utils.isRemoteCopy(context, clipData, source)
return ClipboardModel(
clipData,
@@ -84,18 +85,26 @@
item.textLinks,
item.uri,
sensitive,
- remote
+ remote,
)
}
- private fun getType(context: Context, item: ClipData.Item): Type {
+ private fun getType(context: Context, item: ClipData.Item, mimeType: String): Type {
return if (!TextUtils.isEmpty(item.text)) {
Type.TEXT
} else if (item.uri != null) {
- if (context.contentResolver.getType(item.uri)?.startsWith("image") == true) {
- Type.IMAGE
+ if (clipboardUseDescriptionMimetype()) {
+ if (mimeType.startsWith("image")) {
+ Type.IMAGE
+ } else {
+ Type.URI
+ }
} else {
- Type.URI
+ if (context.contentResolver.getType(item.uri)?.startsWith("image") == true) {
+ Type.IMAGE
+ } else {
+ Type.URI
+ }
}
} else {
Type.OTHER
@@ -107,6 +116,6 @@
TEXT,
IMAGE,
URI,
- OTHER
+ OTHER,
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 04393fe..1bd541e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -65,7 +65,9 @@
keyguardTransitionInteractor
.transitionValue(Scenes.Communal, KeyguardState.GLANCEABLE_HUB)
.map { it == 1f },
- not(keyguardInteractor.isDreaming),
+ // Use isDreamingAny because isDreaming is false in doze and doesn't change again
+ // when the screen turns on, which causes the dream to not start underneath the hub.
+ not(keyguardInteractor.isDreamingAny),
// TODO(b/362830856): Remove this workaround.
keyguardInteractor.isKeyguardShowing,
not(communalSceneInteractor.isLaunchingWidget),
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/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index c7538bb4..905eda1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -87,7 +87,9 @@
*/
private val nextKeyguardStateInternal =
combine(
- keyguardInteractor.isAbleToDream,
+ // Don't use delayed dreaming signal as otherwise we might go to occluded or lock
+ // screen when closing hub if dream just started under the hub.
+ keyguardInteractor.isDreamingWithOverlay,
keyguardInteractor.isKeyguardOccluded,
keyguardInteractor.isKeyguardGoingAway,
keyguardInteractor.isKeyguardShowing,
@@ -156,7 +158,7 @@
private suspend fun handleIdle(
prevTransition: ObservableTransitionState,
- idle: ObservableTransitionState.Idle
+ idle: ObservableTransitionState.Idle,
) {
if (
prevTransition is ObservableTransitionState.Transition &&
@@ -186,7 +188,7 @@
internalTransitionInteractor.updateTransition(
currentTransitionId!!,
1f,
- TransitionState.FINISHED
+ TransitionState.FINISHED,
)
resetTransitionData()
}
@@ -204,7 +206,7 @@
internalTransitionInteractor.updateTransition(
currentTransitionId!!,
1f,
- TransitionState.FINISHED
+ TransitionState.FINISHED,
)
resetTransitionData()
}
@@ -217,7 +219,7 @@
private suspend fun handleTransition(
prevTransition: ObservableTransitionState,
- transition: ObservableTransitionState.Transition
+ transition: ObservableTransitionState.Transition,
) {
if (
prevTransition.isTransitioning(from = transition.fromContent, to = transition.toContent)
@@ -295,7 +297,7 @@
internalTransitionInteractor.updateTransition(
currentTransitionId!!,
progress.coerceIn(0f, 1f),
- TransitionState.RUNNING
+ TransitionState.RUNNING,
)
}
}
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/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 28db3b8..f90f02a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -70,7 +70,14 @@
) {
private val keyguardOccludedByApp: Flow<Boolean> =
if (KeyguardWmStateRefactor.isEnabled) {
- keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED }
+ combine(
+ keyguardTransitionInteractor.currentKeyguardState,
+ communalSceneInteractor.isIdleOnCommunal,
+ ::Pair,
+ )
+ .map { (currentState, isIdleOnCommunal) ->
+ currentState == KeyguardState.OCCLUDED && !isIdleOnCommunal
+ }
} else {
combine(
keyguardInteractor.isKeyguardOccluded,
@@ -120,7 +127,7 @@
// On fingerprint success when the screen is on and not dreaming, go to the home screen
fingerprintUnlockSuccessEvents
.sample(
- combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair),
+ combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair)
)
.collect { (interactive, dreaming) ->
if (interactive && !dreaming) {
@@ -148,7 +155,7 @@
}
},
/* 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/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/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ba23eb3..0a38ce0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3314,7 +3314,11 @@
setShowingLocked(false, "onKeyguardExitFinished: " + reason);
mWakeAndUnlocking = false;
- mDismissCallbackRegistry.notifyDismissSucceeded();
+
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ mDismissCallbackRegistry.notifyDismissSucceeded();
+ }
+
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
adjustStatusBarLocked();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 49303e0..130242f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -58,6 +58,7 @@
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapLatest
@@ -486,19 +487,34 @@
override val isDreaming: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
- val callback =
- object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- trySendWithFailureLogging(linear, TAG, "updated dozeAmount")
- }
+ private val _preSceneLinearDozeAmount: Flow<Float> =
+ if (SceneContainerFlag.isEnabled) {
+ emptyFlow()
+ } else {
+ conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ trySendWithFailureLogging(linear, TAG, "updated dozeAmount")
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ trySendWithFailureLogging(
+ statusBarStateController.dozeAmount,
+ TAG,
+ "initial dozeAmount"
+ )
+
+ awaitClose { statusBarStateController.removeCallback(callback) }
}
+ }
- statusBarStateController.addCallback(callback)
- trySendWithFailureLogging(statusBarStateController.dozeAmount, TAG, "initial dozeAmount")
-
- awaitClose { statusBarStateController.removeCallback(callback) }
- }
+ override val linearDozeAmount: Flow<Float>
+ get() {
+ SceneContainerFlag.assertInLegacyMode()
+ return _preSceneLinearDozeAmount
+ }
override val dozeTransitionModel: Flow<DozeTransitionModel> = conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index b0820a7..8c7fe5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -150,7 +150,9 @@
if (!SceneContainerFlag.isEnabled) {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
}
- } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
+ } else if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
+ // Using false for isScreenOn as canStartDreaming returns false if any
+ // dream, including doze, is active.
// This case handles tapping the power button to transition through
// dream -> off -> hub.
if (!SceneContainerFlag.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 2434b29..9a0a858 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -17,9 +17,12 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.app.DreamManager
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launch
import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -60,10 +63,12 @@
@Main mainDispatcher: CoroutineDispatcher,
keyguardInteractor: KeyguardInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
+ private val communalInteractor: CommunalInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ private val dreamManager: DreamManager,
private val deviceEntryInteractor: DeviceEntryInteractor,
) :
TransitionInteractor(
@@ -76,6 +81,7 @@
keyguardInteractor = keyguardInteractor,
) {
+ @SuppressLint("MissingPermission")
override fun start() {
listenForDreamingToAlternateBouncer()
listenForDreamingToOccluded()
@@ -86,6 +92,8 @@
listenForTransitionToCamera(scope, keyguardInteractor)
if (!communalSceneKtfRefactor()) {
listenForDreamingToGlanceableHub()
+ } else {
+ listenForDreamingToGlanceableHubFromPowerButton()
}
listenForDreamingToPrimaryBouncer()
}
@@ -112,6 +120,34 @@
}
}
+ /**
+ * Normally when pressing power button from the dream, the devices goes from DREAMING to DOZING,
+ * then [FromDozingTransitionInteractor] handles the transition to GLANCEABLE_HUB. However if
+ * the power button is pressed quickly, we may need to go directly from DREAMING to
+ * GLANCEABLE_HUB as the transition to DOZING has not occurred yet.
+ */
+ @SuppressLint("MissingPermission")
+ private fun listenForDreamingToGlanceableHubFromPowerButton() {
+ if (!communalSettingsInteractor.isCommunalFlagEnabled()) return
+ if (SceneContainerFlag.isEnabled) return
+ scope.launch {
+ powerInteractor.isAwake
+ .debounce(50L)
+ .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+ .sample(communalInteractor.isCommunalAvailable)
+ .collect { isCommunalAvailable ->
+ if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "from dreaming to hub",
+ )
+ }
+ }
+ }
+ }
+
private fun listenForDreamingToPrimaryBouncer() {
// TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
@@ -144,7 +180,7 @@
} else {
startTransitionTo(
KeyguardState.LOCKSCREEN,
- ownerReason = "Dream has ended and device is awake"
+ ownerReason = "Dream has ended and device is awake",
)
}
}
@@ -158,15 +194,14 @@
scope.launch {
combine(
keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isAbleToDream
- // Debounce the dreaming signal since there is a race condition between
- // the occluded and dreaming signals. We therefore add a small delay
- // to give enough time for occluded to flip to false when the dream
- // ends, to avoid transitioning to OCCLUDED erroneously when exiting
- // the dream.
- .debounce(100.milliseconds),
- ::Pair
+ keyguardInteractor.isDreaming,
+ ::Pair,
)
+ // Debounce signals since there is a race condition between the occluded and
+ // dreaming signals when starting or stopping dreaming. We therefore add a small
+ // delay to give enough time for occluded to flip to false when the dream
+ // ends, to avoid transitioning to OCCLUDED erroneously when exiting the dream.
+ .debounce(100.milliseconds)
.filterRelevantKeyguardStateAnd { (isOccluded, isDreaming) ->
isOccluded && !isDreaming
}
@@ -194,12 +229,12 @@
if (dismissable) {
startTransitionTo(
KeyguardState.GONE,
- ownerReason = "No longer dreaming; dismissable"
+ ownerReason = "No longer dreaming; dismissable",
)
} else {
startTransitionTo(
KeyguardState.LOCKSCREEN,
- ownerReason = "No longer dreaming"
+ ownerReason = "No longer dreaming",
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 7759298..6b6a3dce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -202,15 +202,15 @@
scope.launch {
combine(
keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isAbleToDream
- // Debounce the dreaming signal since there is a race condition between
- // the occluded and dreaming signals. We therefore add a small delay
- // to give enough time for occluded to flip to false when the dream
- // ends, to avoid transitioning to OCCLUDED erroneously when exiting
- // the dream.
- .debounce(100.milliseconds),
+ keyguardInteractor.isDreaming,
::Pair,
)
+ // Debounce signals since there is a race condition between the occluded and
+ // dreaming signals when starting or stopping dreaming. We therefore add a small
+ // delay to give enough time for occluded to flip to false when the dream
+ // ends, to avoid transitioning to OCCLUDED erroneously when exiting the dream
+ // or when the dream starts underneath the hub.
+ .debounce(200.milliseconds)
.sampleFilter(
// When launching activities from widgets on the hub, we have a
// custom occlusion animation.
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/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
index 4457f1d..9b9bdd1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -23,12 +23,10 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
@@ -37,7 +35,6 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -59,7 +56,6 @@
trustRepository: TrustRepository,
alternateBouncerInteractor: AlternateBouncerInteractor,
powerInteractor: PowerInteractor,
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
/*
* Updates when a biometric has authenticated the device and is requesting to dismiss
@@ -165,14 +161,4 @@
}
}
}
-
- init {
- if (KeyguardWmStateRefactor.isEnabled) {
- scope.launch {
- keyguardTransitionInteractor.currentKeyguardState
- .filter { it == KeyguardState.GONE }
- .collect { dismissCallbackRegistry.notifyDismissSucceeded() }
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index e444092..e6ee112 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -140,12 +140,6 @@
_notificationPlaceholderBounds.value = position
}
- /**
- * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
- * all.
- */
- val dozeAmount: Flow<Float> = repository.linearDozeAmount
-
/** Whether the system is in doze mode. */
val isDozing: StateFlow<Boolean> = repository.isDozing
@@ -155,6 +149,23 @@
/** Whether Always-on Display mode is available. */
val isAodAvailable: StateFlow<Boolean> = repository.isAodAvailable
+ /**
+ * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
+ * all.
+ */
+ val dozeAmount: Flow<Float> =
+ if (SceneContainerFlag.isEnabled) {
+ isAodAvailable.flatMapLatest { isAodAvailable ->
+ if (isAodAvailable) {
+ keyguardTransitionInteractor.transitionValue(AOD)
+ } else {
+ keyguardTransitionInteractor.transitionValue(DOZING)
+ }
+ }
+ } else {
+ repository.linearDozeAmount
+ }
+
/** Doze transition information. */
val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
@@ -164,8 +175,8 @@
val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
/**
- * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
- * but not vice-versa. Also accounts for [isDreamingWithOverlay]
+ * Whether the system is dreaming. [KeyguardRepository.isDreaming] will be always be true when
+ * [isDozing] is true, but not vice-versa. Also accounts for [isDreamingWithOverlay].
*/
val isDreaming: StateFlow<Boolean> =
merge(repository.isDreaming, repository.isDreamingWithOverlay)
@@ -175,6 +186,9 @@
initialValue = false,
)
+ /** Whether any dreaming is running, including the doze dream. */
+ val isDreamingAny: Flow<Boolean> = repository.isDreaming
+
/** Whether the system is dreaming and the active dream is hosted in lockscreen */
val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
index 7fd348b..6fe4ff5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
+import android.app.trust.TrustManager
import android.os.DeadObjectException
import android.os.RemoteException
import com.android.internal.policy.IKeyguardStateCallback
@@ -24,6 +25,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -32,7 +34,6 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -53,6 +54,9 @@
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val trustInteractor: TrustInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
+ private val dismissCallbackRegistry: DismissCallbackRegistry,
+ private val wmLockscreenVisibilityInteractor: WindowManagerLockscreenVisibilityInteractor,
+ private val trustManager: TrustManager,
) : CoreStartable {
private val callbacks = mutableListOf<IKeyguardStateCallback>()
@@ -62,28 +66,31 @@
}
applicationScope.launch {
- combine(
- selectedUserInteractor.selectedUser,
- keyguardTransitionInteractor.currentKeyguardState,
- keyguardTransitionInteractor.startedKeyguardTransitionStep,
- ::Triple,
- )
- .collectLatest { (selectedUser, _, _) ->
- val iterator = callbacks.iterator()
- withContext(backgroundDispatcher) {
- while (iterator.hasNext()) {
- val callback = iterator.next()
- try {
- callback.onShowingStateChanged(!isIdleInGone(), selectedUser)
- callback.onInputRestrictedStateChanged(!isIdleInGone())
- } catch (e: RemoteException) {
- if (e is DeadObjectException) {
- iterator.remove()
- }
+ wmLockscreenVisibilityInteractor.lockscreenVisibility.collectLatest { visible ->
+ val iterator = callbacks.iterator()
+ withContext(backgroundDispatcher) {
+ while (iterator.hasNext()) {
+ val callback = iterator.next()
+ try {
+ callback.onShowingStateChanged(
+ visible,
+ selectedUserInteractor.getSelectedUserId(),
+ )
+ callback.onInputRestrictedStateChanged(visible)
+
+ trustManager.reportKeyguardShowingChanged()
+
+ if (!visible) {
+ dismissCallbackRegistry.notifyDismissSucceeded()
+ }
+ } catch (e: RemoteException) {
+ if (e is DeadObjectException) {
+ iterator.remove()
}
}
}
}
+ }
}
applicationScope.launch {
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/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
index aee34e1..1e42e19 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
@@ -26,6 +26,7 @@
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
@SysUISingleton
class DozingToGlanceableHubTransitionViewModel
@@ -35,10 +36,16 @@
animationFlow
.setup(
duration = TO_GLANCEABLE_HUB_DURATION,
- edge = Edge.create(DOZING, Scenes.Communal)
+ edge = Edge.create(DOZING, Scenes.Communal),
)
.setupWithoutSceneContainer(edge = Edge.create(DOZING, GLANCEABLE_HUB))
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(1f)
+
+ /**
+ * Hide notifications when transitioning directly from dozing to hub, such as when pressing
+ * power button when dozing and docked.
+ */
+ val notificationAlpha: Flow<Float> = flowOf(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 4b62eab..0d55709 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -179,14 +179,6 @@
} else {
button(KeyguardQuickAffordancePosition.BOTTOM_START)
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue =
- KeyguardQuickAffordanceViewModel(
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
- ),
- )
/** An observable for the view-model of the "end button" quick affordance. */
val endButton: Flow<KeyguardQuickAffordanceViewModel> =
@@ -200,14 +192,6 @@
} else {
button(KeyguardQuickAffordancePosition.BOTTOM_END)
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue =
- KeyguardQuickAffordanceViewModel(
- slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
- ),
- )
/**
* Notifies that a slot with the given ID has been selected in the preview experience that is
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/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index b3c697e..1216a88 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -551,6 +551,12 @@
}
@Override
+ public void appTransitionStarting(int displayId, long startTime, long duration,
+ boolean forced) {
+ appTransitionPending(false);
+ }
+
+ @Override
public void appTransitionCancelled(int displayId) {
appTransitionPending(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index 02a607d..fc59a50 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -40,7 +40,7 @@
private val currentTilesInteractor: CurrentTilesInteractor,
private val preferencesInteractor: QSPreferencesInteractor,
@PanelsLog private val logBuffer: LogBuffer,
- @Application private val applicationScope: CoroutineScope
+ @Application private val applicationScope: CoroutineScope,
) {
val largeTilesSpecs =
@@ -64,14 +64,15 @@
fun isIconTile(spec: TileSpec): Boolean = !largeTilesSpecs.value.contains(spec)
- fun resize(spec: TileSpec) {
+ fun resize(spec: TileSpec, toIcon: Boolean) {
if (!isCurrent(spec)) {
return
}
- if (largeTilesSpecs.value.contains(spec)) {
+ val isIcon = !largeTilesSpecs.value.contains(spec)
+ if (toIcon && !isIcon) {
preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value - spec)
- } else {
+ } else if (!toIcon && isIcon) {
preferencesInteractor.setLargeTilesSpecs(largeTilesSpecs.value + spec)
}
}
@@ -85,7 +86,7 @@
LOG_BUFFER_LARGE_TILES_SPECS_CHANGE_TAG,
LogLevel.DEBUG,
{ str1 = specs.toString() },
- { "Large tiles change: $str1" }
+ { "Large tiles change: $str1" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index a4f977b..770fd78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -60,10 +60,37 @@
return _tiles.filterIsInstance<TileGridCell>().map { it.tile.tileSpec }
}
- fun indexOf(tileSpec: TileSpec): Int {
+ private fun indexOf(tileSpec: TileSpec): Int {
return _tiles.indexOfFirst { it is TileGridCell && it.tile.tileSpec == tileSpec }
}
+ /**
+ * Whether the tile with this [TileSpec] is currently an icon in the [EditTileListState]
+ *
+ * @return true if the tile is an icon, false if it's large, null if the tile isn't in the list
+ */
+ fun isIcon(tileSpec: TileSpec): Boolean? {
+ val index = indexOf(tileSpec)
+ return if (index != -1) {
+ val cell = _tiles[index]
+ cell as TileGridCell
+ return cell.isIcon
+ } else {
+ null
+ }
+ }
+
+ /** Toggle the size of the tile corresponding to the [TileSpec] */
+ fun toggleSize(tileSpec: TileSpec) {
+ val fromIndex = indexOf(tileSpec)
+ if (fromIndex != -1) {
+ val cell = _tiles.removeAt(fromIndex)
+ cell as TileGridCell
+ _tiles.add(fromIndex, cell.copy(width = if (cell.isIcon) 2 else 1))
+ regenerateGrid(fromIndex)
+ }
+ }
+
override fun isMoving(tileSpec: TileSpec): Boolean {
return _draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false
}
@@ -71,8 +98,8 @@
override fun onStarted(cell: SizedTile<EditTileViewModel>) {
_draggedCell.value = cell
- // Add visible spacers to the grid to indicate where the user can move a tile
- regenerateGrid(includeSpacers = true)
+ // Add spacers to the grid to indicate where the user can move a tile
+ regenerateGrid()
}
override fun onMoved(target: Int, insertAfter: Boolean) {
@@ -86,7 +113,7 @@
val insertionIndex = if (insertAfter) target + 1 else target
if (fromIndex != -1) {
val cell = _tiles.removeAt(fromIndex)
- regenerateGrid(includeSpacers = true)
+ regenerateGrid()
_tiles.add(insertionIndex.coerceIn(0, _tiles.size), cell)
} else {
// Add the tile with a temporary row which will get reassigned when
@@ -94,7 +121,7 @@
_tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0))
}
- regenerateGrid(includeSpacers = true)
+ regenerateGrid()
}
override fun movedOutOfBounds() {
@@ -109,13 +136,28 @@
_draggedCell.value = null
// Remove the spacers
- regenerateGrid(includeSpacers = false)
+ regenerateGrid()
}
- private fun regenerateGrid(includeSpacers: Boolean) {
- _tiles.filterIsInstance<TileGridCell>().toGridCells(columns, includeSpacers).let {
+ /** Regenerate the list of [GridCell] with their new potential rows */
+ private fun regenerateGrid() {
+ _tiles.filterIsInstance<TileGridCell>().toGridCells(columns).let {
_tiles.clear()
_tiles.addAll(it)
}
}
+
+ /**
+ * Regenerate the list of [GridCell] with their new potential rows from [fromIndex], leaving
+ * cells before that untouched.
+ */
+ private fun regenerateGrid(fromIndex: Int) {
+ val fromRow = _tiles[fromIndex].row
+ val (pre, post) = _tiles.partition { it.row < fromRow }
+ post.filterIsInstance<TileGridCell>().toGridCells(columns, startingRow = fromRow).let {
+ _tiles.clear()
+ _tiles.addAll(pre)
+ _tiles.addAll(it)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index 0e76e18..30bafae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -132,15 +132,23 @@
@Composable
fun DefaultEditTileGrid(
- currentListState: EditTileListState,
+ listState: EditTileListState,
otherTiles: List<SizedTile<EditTileViewModel>>,
columns: Int,
modifier: Modifier,
onRemoveTile: (TileSpec) -> Unit,
onSetTiles: (List<TileSpec>) -> Unit,
- onResize: (TileSpec) -> Unit,
+ onResize: (TileSpec, toIcon: Boolean) -> Unit,
) {
- val selectionState = rememberSelectionState()
+ val currentListState by rememberUpdatedState(listState)
+ val selectionState =
+ rememberSelectionState(
+ onResize = { currentListState.toggleSize(it) },
+ onResizeEnd = { spec ->
+ // Commit the size currently in the list
+ currentListState.isIcon(spec)?.let { onResize(spec, it) }
+ },
+ )
CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
Column(
@@ -149,11 +157,11 @@
modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()),
) {
AnimatedContent(
- targetState = currentListState.dragInProgress,
+ targetState = listState.dragInProgress,
modifier = Modifier.wrapContentSize(),
label = "",
) { dragIsInProgress ->
- EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) {
+ EditGridHeader(Modifier.dragAndDropRemoveZone(listState, onRemoveTile)) {
if (dragIsInProgress) {
RemoveTileTarget()
} else {
@@ -162,11 +170,11 @@
}
}
- CurrentTilesGrid(currentListState, selectionState, columns, onResize, onSetTiles)
+ CurrentTilesGrid(listState, selectionState, columns, onResize, onSetTiles)
// Hide available tiles when dragging
AnimatedVisibility(
- visible = !currentListState.dragInProgress,
+ visible = !listState.dragInProgress,
enter = fadeIn(),
exit = fadeOut(),
) {
@@ -177,7 +185,7 @@
) {
EditGridHeader { Text(text = "Hold and drag to add tiles.") }
- AvailableTileGrid(otherTiles, selectionState, columns, currentListState)
+ AvailableTileGrid(otherTiles, selectionState, columns, listState)
}
}
@@ -186,7 +194,7 @@
modifier =
Modifier.fillMaxWidth()
.weight(1f)
- .dragAndDropRemoveZone(currentListState, onRemoveTile)
+ .dragAndDropRemoveZone(listState, onRemoveTile)
)
}
}
@@ -229,7 +237,7 @@
listState: EditTileListState,
selectionState: MutableSelectionState,
columns: Int,
- onResize: (TileSpec) -> Unit,
+ onResize: (TileSpec, toIcon: Boolean) -> Unit,
onSetTiles: (List<TileSpec>) -> Unit,
) {
val currentListState by rememberUpdatedState(listState)
@@ -242,19 +250,6 @@
)
val gridState = rememberLazyGridState()
var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) }
- var droppedSpec by remember { mutableStateOf<TileSpec?>(null) }
-
- // Select the tile that was dropped. A delay is introduced to avoid clipping issues on the
- // selected border and resizing handle, as well as letting the selection animation play.
- LaunchedEffect(droppedSpec) {
- droppedSpec?.let {
- delay(200)
- selectionState.select(it)
-
- // Reset droppedSpec in case a tile is dropped twice in a row
- droppedSpec = null
- }
- }
TileLazyGrid(
state = gridState,
@@ -270,14 +265,17 @@
)
.dragAndDropTileList(gridState, { gridContentOffset }, listState) { spec ->
onSetTiles(currentListState.tileSpecs())
- droppedSpec = spec
+ selectionState.select(spec, manual = false)
}
.onGloballyPositioned { coordinates ->
gridContentOffset = coordinates.positionInRoot()
}
.testTag(CURRENT_TILES_GRID_TEST_TAG),
) {
- EditTiles(listState.tiles, listState, selectionState, onResize)
+ EditTiles(listState.tiles, listState, selectionState) { spec ->
+ // Toggle the current size of the tile
+ currentListState.isIcon(spec)?.let { onResize(spec, !it) }
+ }
}
}
@@ -348,11 +346,19 @@
}
}
+/**
+ * Adds a list of [GridCell] to the lazy grid
+ *
+ * @param cells the list of [GridCell]
+ * @param dragAndDropState the [DragAndDropState] for this grid
+ * @param selectionState the [MutableSelectionState] for this grid
+ * @param onToggleSize the callback when a tile's size is toggled
+ */
fun LazyGridScope.EditTiles(
cells: List<GridCell>,
dragAndDropState: DragAndDropState,
selectionState: MutableSelectionState,
- onResize: (TileSpec) -> Unit,
+ onToggleSize: (spec: TileSpec) -> Unit,
) {
items(
count = cells.size,
@@ -378,7 +384,7 @@
index = index,
dragAndDropState = dragAndDropState,
selectionState = selectionState,
- onResize = onResize,
+ onToggleSize = onToggleSize,
)
}
is SpacerGridCell -> SpacerGridCell()
@@ -392,16 +398,28 @@
index: Int,
dragAndDropState: DragAndDropState,
selectionState: MutableSelectionState,
- onResize: (TileSpec) -> Unit,
+ onToggleSize: (spec: TileSpec) -> Unit,
) {
- val selected = selectionState.isSelected(cell.tile.tileSpec)
val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+ var selected by remember { mutableStateOf(false) }
val selectionAlpha by
animateFloatAsState(
targetValue = if (selected) 1f else 0f,
label = "QSEditTileSelectionAlpha",
)
+ LaunchedEffect(selectionState.selection?.tileSpec) {
+ selectionState.selection?.let {
+ // A delay is introduced on automatic selections such as dragged tiles or reflow caused
+ // by resizing. This avoids clipping issues on the border and resizing handle, as well
+ // as letting the selection animation play correctly.
+ if (!it.manual) {
+ delay(250)
+ }
+ }
+ selected = selectionState.selection?.tileSpec == cell.tile.tileSpec
+ }
+
val modifier =
Modifier.animateItem()
.semantics(mergeDescendants = true) {
@@ -411,7 +429,7 @@
listOf(
// TODO(b/367748260): Add final accessibility actions
CustomAccessibilityAction("Toggle size") {
- onResize(cell.tile.tileSpec)
+ onToggleSize(cell.tile.tileSpec)
true
}
)
@@ -438,11 +456,9 @@
if (selected) {
SelectedTile(
- tileSpec = cell.tile.tileSpec,
isIcon = cell.isIcon,
selectionAlpha = { selectionAlpha },
selectionState = selectionState,
- onResize = onResize,
modifier = modifier.zIndex(2f), // 2f to display this tile over neighbors when dragged
content = content,
)
@@ -458,11 +474,9 @@
@Composable
private fun SelectedTile(
- tileSpec: TileSpec,
isIcon: Boolean,
selectionAlpha: () -> Float,
selectionState: MutableSelectionState,
- onResize: (TileSpec) -> Unit,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
@@ -492,9 +506,7 @@
selectionState = selectionState,
transition = selectionAlpha,
tileWidths = { tileWidths },
- ) {
- onResize(tileSpec)
- }
+ )
}
Layout(contents = listOf(content, handle)) {
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 8a96065..e6edba5 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
@@ -101,7 +101,7 @@
val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent }
val currentListState = rememberEditListState(currentTiles, columns)
DefaultEditTileGrid(
- currentListState = currentListState,
+ listState = currentListState,
otherTiles = otherTiles,
columns = columns,
modifier = modifier,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
index 2ea32e6..441d962 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
@@ -27,28 +27,39 @@
/** Creates the state of the current selected tile that is remembered across compositions. */
@Composable
-fun rememberSelectionState(): MutableSelectionState {
- return remember { MutableSelectionState() }
+fun rememberSelectionState(
+ onResize: (TileSpec) -> Unit,
+ onResizeEnd: (TileSpec) -> Unit,
+): MutableSelectionState {
+ return remember { MutableSelectionState(onResize, onResizeEnd) }
}
+/**
+ * Holds the selected [TileSpec] and whether the selection was manual, i.e. caused by a tap from the
+ * user.
+ */
+data class Selection(val tileSpec: TileSpec, val manual: Boolean)
+
/** Holds the state of the current selection. */
-class MutableSelectionState {
- private var _selectedTile = mutableStateOf<TileSpec?>(null)
+class MutableSelectionState(
+ val onResize: (TileSpec) -> Unit,
+ private val onResizeEnd: (TileSpec) -> Unit,
+) {
+ private var _selection = mutableStateOf<Selection?>(null)
private var _resizingState = mutableStateOf<ResizingState?>(null)
+ /** The [Selection] if a tile is selected, null if not. */
+ val selection by _selection
+
/** The [ResizingState] of the selected tile is currently being resized, null if not. */
val resizingState by _resizingState
- fun isSelected(tileSpec: TileSpec): Boolean {
- return _selectedTile.value?.let { it == tileSpec } ?: false
- }
-
- fun select(tileSpec: TileSpec) {
- _selectedTile.value = tileSpec
+ fun select(tileSpec: TileSpec, manual: Boolean) {
+ _selection.value = Selection(tileSpec, manual)
}
fun unSelect() {
- _selectedTile.value = null
+ _selection.value = null
onResizingDragEnd()
}
@@ -56,14 +67,21 @@
_resizingState.value?.onDrag(offset)
}
- fun onResizingDragStart(tileWidths: TileWidths, onResize: () -> Unit) {
- if (_selectedTile.value == null) return
-
- _resizingState.value = ResizingState(tileWidths, onResize)
+ fun onResizingDragStart(tileWidths: TileWidths) {
+ _selection.value?.let {
+ _resizingState.value = ResizingState(tileWidths) { onResize(it.tileSpec) }
+ }
}
fun onResizingDragEnd() {
_resizingState.value = null
+ _selection.value?.let {
+ onResizeEnd(it.tileSpec)
+
+ // Mark the selection as automatic in case the tile ends up moving to a different
+ // row with its new size.
+ _selection.value = it.copy(manual = false)
+ }
}
}
@@ -76,10 +94,10 @@
return pointerInput(Unit) {
detectTapGestures(
onTap = {
- if (selectionState.isSelected(tileSpec)) {
+ if (selectionState.selection?.tileSpec == tileSpec) {
selectionState.unSelect()
} else {
- selectionState.select(tileSpec)
+ selectionState.select(tileSpec, manual = true)
}
}
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
index e3acf38..7c62e59 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
@@ -45,7 +45,6 @@
selectionState: MutableSelectionState,
transition: () -> Float,
tileWidths: () -> TileWidths? = { null },
- onResize: () -> Unit = {},
) {
if (enabled) {
// Manually creating the touch target around the resizing dot to ensure that the next tile
@@ -56,9 +55,7 @@
Modifier.size(minTouchTargetSize).pointerInput(Unit) {
detectHorizontalDragGestures(
onHorizontalDrag = { _, offset -> selectionState.onResizingDrag(offset) },
- onDragStart = {
- tileWidths()?.let { selectionState.onResizingDragStart(it, onResize) }
- },
+ onDragStart = { tileWidths()?.let { selectionState.onResizingDragStart(it) } },
onDragEnd = selectionState::onResizingDragEnd,
onDragCancel = selectionState::onResizingDragEnd,
)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
index b16a707..b1841c4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -27,6 +27,7 @@
sealed interface GridCell {
val row: Int
val span: GridItemSpan
+ val s: String
}
/**
@@ -39,6 +40,7 @@
override val row: Int,
override val width: Int,
override val span: GridItemSpan = GridItemSpan(width),
+ override val s: String = "${tile.tileSpec.spec}-$row-$width",
) : GridCell, SizedTile<EditTileViewModel>, CategoryAndName by tile {
val key: String = "${tile.tileSpec.spec}-$row"
@@ -53,22 +55,30 @@
data class SpacerGridCell(
override val row: Int,
override val span: GridItemSpan = GridItemSpan(1),
+ override val s: String = "spacer",
) : GridCell
+/**
+ * Generates a list of [GridCell] from a list of [SizedTile]
+ *
+ * Builds rows based on the tiles' widths, and fill each hole with a [SpacerGridCell]
+ *
+ * @param startingRow The row index the grid is built from, used in cases where only end rows need
+ * to be regenerated
+ */
fun List<SizedTile<EditTileViewModel>>.toGridCells(
columns: Int,
- includeSpacers: Boolean = false,
+ startingRow: Int = 0,
): List<GridCell> {
return splitInRowsSequence(this, columns)
.flatMapIndexed { rowIndex, sizedTiles ->
- val row: List<GridCell> = sizedTiles.map { TileGridCell(it, rowIndex) }
+ val correctedRowIndex = rowIndex + startingRow
+ val row: List<GridCell> = sizedTiles.map { TileGridCell(it, correctedRowIndex) }
- if (includeSpacers) {
- // Fill the incomplete rows with spacers
- val numSpacers = columns - sizedTiles.sumOf { it.width }
- row.toMutableList().apply { repeat(numSpacers) { add(SpacerGridCell(rowIndex)) } }
- } else {
- row
+ // Fill the incomplete rows with spacers
+ val numSpacers = columns - sizedTiles.sumOf { it.width }
+ row.toMutableList().apply {
+ repeat(numSpacers) { add(SpacerGridCell(correctedRowIndex)) }
}
}
.toList()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
index b604e18..4e698ed 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -27,7 +27,7 @@
fun isIconTile(spec: TileSpec): Boolean
- fun resize(spec: TileSpec)
+ fun resize(spec: TileSpec, toIcon: Boolean)
}
@SysUISingleton
@@ -37,5 +37,5 @@
override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec)
- override fun resize(spec: TileSpec) = interactor.resize(spec)
+ override fun resize(spec: TileSpec, toIcon: Boolean) = interactor.resize(spec, toIcon)
}
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/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/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 5eef8ea..769abaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -207,8 +207,8 @@
protected boolean mPowerPluggedInWireless;
protected boolean mPowerPluggedInDock;
protected int mChargingSpeed;
+ protected boolean mPowerCharged;
- private boolean mPowerCharged;
/** Whether the battery defender is triggered. */
private boolean mBatteryDefender;
/** Whether the battery defender is triggered with the device plugged. */
@@ -1100,14 +1100,15 @@
String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
return mContext.getResources().getString(
R.string.keyguard_plugged_in_incompatible_charger, percentage);
- } else if (mPowerCharged) {
- return mContext.getResources().getString(R.string.keyguard_charged);
}
return computePowerChargingStringIndication();
}
protected String computePowerChargingStringIndication() {
+ if (mPowerCharged) {
+ return mContext.getResources().getString(R.string.keyguard_charged);
+ }
final boolean hasChargingTime = mChargingTimeRemaining > 0;
int chargingId;
if (mPowerPluggedInWired) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 7f55512..8a6ec2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -50,8 +50,8 @@
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
import com.android.systemui.scene.data.model.SceneStack;
@@ -115,6 +115,7 @@
private final UiEventLogger mUiEventLogger;
private final Lazy<InteractionJankMonitor> mInteractionJankMonitorLazy;
private final JavaAdapter mJavaAdapter;
+ private final Lazy<KeyguardInteractor> mKeyguardInteractorLazy;
private final Lazy<KeyguardTransitionInteractor> mKeyguardTransitionInteractorLazy;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
@@ -185,6 +186,7 @@
UiEventLogger uiEventLogger,
Lazy<InteractionJankMonitor> interactionJankMonitorLazy,
JavaAdapter javaAdapter,
+ Lazy<KeyguardInteractor> keyguardInteractor,
Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor,
Lazy<ShadeInteractor> shadeInteractorLazy,
Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
@@ -195,6 +197,7 @@
mUiEventLogger = uiEventLogger;
mInteractionJankMonitorLazy = interactionJankMonitorLazy;
mJavaAdapter = javaAdapter;
+ mKeyguardInteractorLazy = keyguardInteractor;
mKeyguardTransitionInteractorLazy = keyguardTransitionInteractor;
mShadeInteractorLazy = shadeInteractorLazy;
mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
@@ -233,8 +236,8 @@
this::onStatusBarStateChanged);
mJavaAdapter.alwaysCollectFlow(
- mKeyguardTransitionInteractorLazy.get().transitionValue(KeyguardState.AOD),
- this::onAodKeyguardStateTransitionValueChanged);
+ mKeyguardInteractorLazy.get().getDozeAmount(),
+ this::setDozeAmountInternal);
}
}
@@ -404,6 +407,7 @@
@Override
public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) {
+ SceneContainerFlag.assertInLegacyMode();
if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
if (animated && mDozeAmountTarget == dozeAmount) {
return;
@@ -439,6 +443,7 @@
}
private void startDozeAnimation() {
+ SceneContainerFlag.assertInLegacyMode();
if (mDozeAmount == 0f || mDozeAmount == 1f) {
mDozeInterpolator = mIsDozing
? Interpolators.FAST_OUT_SLOW_IN
@@ -457,6 +462,7 @@
@VisibleForTesting
protected ObjectAnimator createDarkAnimator() {
+ SceneContainerFlag.assertInLegacyMode();
ObjectAnimator darkAnimator = ObjectAnimator.ofFloat(
this, SET_DARK_AMOUNT_PROPERTY, mDozeAmountTarget);
darkAnimator.setInterpolator(Interpolators.LINEAR);
@@ -710,14 +716,6 @@
updateStateAndNotifyListeners(newState);
}
- private void onAodKeyguardStateTransitionValueChanged(float value) {
- if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
- return;
- }
-
- setDozeAmountInternal(value);
- }
-
private static final Map<SceneKey, Integer> sStatusBarStateByLockedSceneKey = Map.of(
Scenes.Lockscreen, StatusBarState.KEYGUARD,
Scenes.Bouncer, StatusBarState.KEYGUARD,
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/notification/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
index 9b96931..6907eef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
@@ -106,7 +106,8 @@
@VisibleForTesting
public final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
= new ArraySet<>();
- private boolean mIsExpanded;
+ private boolean mIsShadeOrQsExpanded;
+ private boolean mIsQsExpanded;
private int mStatusBarState;
private AnimationStateHandler mAnimationStateHandler;
@@ -178,6 +179,10 @@
});
javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
this::onShadeOrQsExpanded);
+ if (SceneContainerFlag.isEnabled()) {
+ javaAdapter.alwaysCollectFlow(shadeInteractor.isQsExpanded(),
+ this::onQsExpanded);
+ }
if (NotificationThrottleHun.isEnabled()) {
mVisualStabilityProvider.addPersistentReorderingBannedListener(
mOnReorderingBannedListener);
@@ -287,14 +292,19 @@
}
private void onShadeOrQsExpanded(Boolean isExpanded) {
- if (isExpanded != mIsExpanded) {
- mIsExpanded = isExpanded;
+ if (isExpanded != mIsShadeOrQsExpanded) {
+ mIsShadeOrQsExpanded = isExpanded;
if (!SceneContainerFlag.isEnabled() && isExpanded) {
mHeadsUpAnimatingAway.setValue(false);
}
}
}
+ private void onQsExpanded(Boolean isQsExpanded) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ if (isQsExpanded != mIsQsExpanded) mIsQsExpanded = isQsExpanded;
+ }
+
/**
* Set that we are exiting the headsUp pinned mode, but some notifications might still be
* animating out. This is used to keep the touchable regions in a reasonable state.
@@ -490,7 +500,10 @@
@Override
protected boolean shouldHeadsUpBecomePinned(NotificationEntry entry) {
- boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsExpanded;
+ boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsShadeOrQsExpanded;
+ if (SceneContainerFlag.isEnabled()) {
+ pin |= mIsQsExpanded;
+ }
if (mBypassController.getBypassEnabled()) {
pin |= mStatusBarState == StatusBarState.KEYGUARD;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 560028c..7b6a2cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -444,11 +444,9 @@
if (onFinishedRunnable != null) {
onFinishedRunnable.run();
}
- if (mRunWithoutInterruptions) {
- enableAppearDrawing(false);
- }
// We need to reset the View state, even if the animation was cancelled
+ enableAppearDrawing(false);
onAppearAnimationFinished(isAppearing);
if (mRunWithoutInterruptions) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 48796d8..b108388 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -634,8 +634,10 @@
}
private View inflateDivider() {
- return LayoutInflater.from(mContext).inflate(
+ View divider = LayoutInflater.from(mContext).inflate(
R.layout.notification_children_divider, this, false);
+ divider.setAlpha(0f);
+ return divider;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e7c67f9..3c6962a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1260,6 +1260,7 @@
@Override
public void setHeadsUpBottom(float headsUpBottom) {
mAmbientState.setHeadsUpBottom(headsUpBottom);
+ mStateAnimator.setHeadsUpAppearHeightBottom(Math.round(headsUpBottom));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index e34eb61..8ca26be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -45,6 +45,7 @@
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
@@ -112,6 +113,7 @@
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
+ dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
@@ -506,6 +508,7 @@
merge(
lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+ dozingToGlanceableHubTransitionViewModel.notificationAlpha,
)
// Manually emit on start because [notificationAlpha] only starts emitting
// when transitions start.
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/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 68163b2..4ef328c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -149,7 +149,7 @@
override val isForceHidden: Flow<Boolean>,
connectionRepository: MobileConnectionRepository,
private val context: Context,
- val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
+ val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl(),
) : MobileIconInteractor {
override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
@@ -182,7 +182,7 @@
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- connectionRepository.networkName.value
+ connectionRepository.networkName.value,
)
override val carrierName =
@@ -198,7 +198,7 @@
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- connectionRepository.carrierName.value.name
+ connectionRepository.carrierName.value.name,
)
/** What the mobile icon would be before carrierId overrides */
@@ -219,10 +219,7 @@
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
override val networkTypeIconGroup =
- combine(
- defaultNetworkType,
- carrierIdIconOverrideExists,
- ) { networkType, overrideExists ->
+ combine(defaultNetworkType, carrierIdIconOverrideExists) { networkType, overrideExists ->
// DefaultIcon comes out of the icongroup lookup, we check for overrides here
if (overrideExists) {
val iconOverride =
@@ -316,30 +313,30 @@
/** Whether or not to show the error state of [SignalDrawable] */
private val showExclamationMark: StateFlow<Boolean> =
- combine(
- defaultSubscriptionHasDataEnabled,
+ combine(defaultSubscriptionHasDataEnabled, isDefaultConnectionFailed, isInService) {
+ isDefaultDataEnabled,
isDefaultConnectionFailed,
- isInService,
- ) { isDefaultDataEnabled, isDefaultConnectionFailed, isInService ->
+ isInService ->
!isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
}
.stateIn(scope, SharingStarted.WhileSubscribed(), true)
private val cellularShownLevel: StateFlow<Int> =
- combine(
+ combine(level, isInService, connectionRepository.inflateSignalStrength) {
level,
isInService,
- connectionRepository.inflateSignalStrength,
- ) { level, isInService, inflate ->
+ inflate ->
if (isInService) {
if (inflate) level + 1 else level
} else 0
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
- // Satellite level is unaffected by the isInService or inflateSignalStrength properties
+ // Satellite level is unaffected by the inflateSignalStrength property
// See b/346904529 for details
- private val satelliteShownLevel: StateFlow<Int> = level
+ private val satelliteShownLevel: StateFlow<Int> =
+ combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
private val cellularIcon: Flow<SignalIconModel.Cellular> =
combine(
@@ -362,7 +359,7 @@
level = it,
icon =
SatelliteIconModel.fromSignalStrength(it)
- ?: SatelliteIconModel.fromSignalStrength(0)!!
+ ?: SatelliteIconModel.fromSignalStrength(0)!!,
)
}
@@ -383,11 +380,7 @@
}
}
.distinctUntilChanged()
- .logDiffsForTable(
- tableLogBuffer,
- columnPrefix = "icon",
- initialValue = initial,
- )
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "icon", initialValue = initial)
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
}
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..eea4c21 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
@@ -86,19 +89,30 @@
launch {
viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
OngoingActivityChipBinder.bind(primaryChipModel, primaryChipView)
- when (primaryChipModel) {
- is OngoingActivityChipModel.Shown ->
- listener.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity = true,
- hasSecondaryOngoingActivity = false,
- shouldAnimate = true,
- )
- is OngoingActivityChipModel.Hidden ->
- listener.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity = false,
- hasSecondaryOngoingActivity = false,
- shouldAnimate = primaryChipModel.shouldAnimate,
- )
+ if (StatusBarSimpleFragment.isEnabled) {
+ when (primaryChipModel) {
+ is OngoingActivityChipModel.Shown ->
+ primaryChipView.show(shouldAnimateChange = true)
+ is OngoingActivityChipModel.Hidden ->
+ primaryChipView.hide(
+ shouldAnimateChange = primaryChipModel.shouldAnimate
+ )
+ }
+ } else {
+ when (primaryChipModel) {
+ is OngoingActivityChipModel.Shown ->
+ listener.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity = true,
+ hasSecondaryOngoingActivity = false,
+ shouldAnimate = true,
+ )
+ is OngoingActivityChipModel.Hidden ->
+ listener.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity = false,
+ hasSecondaryOngoingActivity = false,
+ shouldAnimate = primaryChipModel.shouldAnimate,
+ )
+ }
}
}
}
@@ -115,14 +129,22 @@
// TODO(b/364653005): Don't show the secondary chip if there isn't
// enough space for it.
OngoingActivityChipBinder.bind(chips.secondary, secondaryChipView)
- listener.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity =
- chips.primary is OngoingActivityChipModel.Shown,
- hasSecondaryOngoingActivity =
- chips.secondary is OngoingActivityChipModel.Shown,
- // TODO(b/364653005): Figure out the animation story here.
- shouldAnimate = true,
- )
+
+ if (StatusBarSimpleFragment.isEnabled) {
+ primaryChipView.adjustVisibility(chips.primary.toVisibilityModel())
+ secondaryChipView.adjustVisibility(
+ chips.secondary.toVisibilityModel()
+ )
+ } else {
+ listener.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity =
+ chips.primary is OngoingActivityChipModel.Shown,
+ hasSecondaryOngoingActivity =
+ chips.secondary is OngoingActivityChipModel.Shown,
+ // TODO(b/364653005): Figure out the animation story here.
+ shouldAnimate = true,
+ )
+ }
}
}
}
@@ -134,10 +156,42 @@
}
}
}
+
+ 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.
+ }
+ }
+ }
}
}
}
+ private fun OngoingActivityChipModel.toVisibilityModel():
+ CollapsedStatusBarViewModel.VisibilityModel {
+ return CollapsedStatusBarViewModel.VisibilityModel(
+ visibility = if (this is OngoingActivityChipModel.Shown) View.VISIBLE else View.GONE,
+ // TODO(b/364653005): Figure out the animation story here.
+ shouldAnimateChange = true,
+ )
+ }
+
private fun animateLightsOutView(view: View, visible: Boolean) {
view.animate().cancel()
@@ -167,6 +221,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..366ea35 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,16 @@
/**
* 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>
+
/**
* 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 +106,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 +170,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/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 27bc6d3..76389f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -71,7 +71,7 @@
.semantics { stateDescription = viewModel.stateDescription },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement =
- Arrangement.spacedBy(space = 8.dp, alignment = Alignment.Start),
+ Arrangement.spacedBy(space = 12.dp, alignment = Alignment.Start),
) {
Icon(icon = viewModel.icon, modifier = Modifier.size(24.dp))
Column {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
index 5953ea5..5392e38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
@@ -26,7 +26,6 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.systemui.Flags
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
@Composable
@@ -34,8 +33,8 @@
val tiles by viewModel.tiles.collectAsStateWithLifecycle(initialValue = emptyList())
LazyVerticalGrid(
- columns = GridCells.Fixed(if (Flags.modesDialogSingleRows()) 1 else 2),
- modifier = Modifier.fillMaxWidth().heightIn(max = 300.dp),
+ columns = GridCells.Fixed(1),
+ modifier = Modifier.fillMaxWidth().heightIn(max = 320.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
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/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
rename to packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
index 5d76e32..85e8ab4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
@@ -22,10 +22,12 @@
import android.graphics.Bitmap
import android.net.Uri
import android.os.PersistableBundle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE
import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.whenever
import java.io.IOException
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -37,6 +39,7 @@
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -88,7 +91,8 @@
@Test
@Throws(IOException::class)
- fun test_imageClipData() {
+ @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
+ fun test_imageClipData_legacy() {
val testBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
whenever(mMockContext.resources).thenReturn(mContext.resources)
@@ -103,6 +107,21 @@
@Test
@Throws(IOException::class)
+ @EnableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
+ fun test_imageClipData() {
+ val testBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
+ whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
+ whenever(mMockContext.resources).thenReturn(mContext.resources)
+ whenever(mMockContentResolver.loadThumbnail(any(), any(), any())).thenReturn(testBitmap)
+ whenever(mMockContentResolver.getType(any())).thenReturn("text")
+ val imageClipData = ClipData("Test", arrayOf("image/png"), ClipData.Item(Uri.parse("test")))
+ val model = ClipboardModel.fromClipData(mMockContext, mClipboardUtils, imageClipData, "")
+ assertEquals(ClipboardModel.Type.IMAGE, model.type)
+ assertEquals(testBitmap, model.loadThumbnail(mMockContext))
+ }
+
+ @Test
+ @Throws(IOException::class)
fun test_imageClipData_loadFailure() {
whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
whenever(mMockContext.resources).thenReturn(mContext.resources)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index 6423d25..8d060e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -60,13 +60,13 @@
onSetTiles: (List<TileSpec>) -> Unit,
) {
DefaultEditTileGrid(
- currentListState = listState,
+ listState = listState,
otherTiles = listOf(),
columns = 4,
modifier = Modifier.fillMaxSize(),
onRemoveTile = {},
onSetTiles = onSetTiles,
- onResize = {},
+ onResize = { _, _ -> },
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
index 682ed92..ee1c0e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
@@ -25,7 +25,11 @@
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performCustomAccessibilityActionWithLabel
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
import androidx.compose.ui.text.AnnotatedString
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -43,15 +47,19 @@
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalTestApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ResizingTest : SysuiTestCase() {
@get:Rule val composeRule = createComposeRule()
@Composable
- private fun EditTileGridUnderTest(listState: EditTileListState, onResize: (TileSpec) -> Unit) {
+ private fun EditTileGridUnderTest(
+ listState: EditTileListState,
+ onResize: (TileSpec, Boolean) -> Unit,
+ ) {
DefaultEditTileGrid(
- currentListState = listState,
+ listState = listState,
otherTiles = listOf(),
columns = 4,
modifier = Modifier.fillMaxSize(),
@@ -61,22 +69,12 @@
)
}
- @OptIn(ExperimentalTestApi::class)
@Test
- fun resizedIcon_shouldBeLarge() {
+ fun toggleIconTile_shouldBeLarge() {
var tiles by mutableStateOf(TestEditTiles)
val listState = EditTileListState(tiles, 4)
composeRule.setContent {
- EditTileGridUnderTest(listState) { spec ->
- tiles =
- tiles.map {
- if (it.tile.tileSpec == spec) {
- toggleWidth(it)
- } else {
- it
- }
- }
- }
+ EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
}
composeRule.waitForIdle()
@@ -87,22 +85,12 @@
assertThat(tiles.find { it.tile.tileSpec.spec == "tileA" }?.width).isEqualTo(2)
}
- @OptIn(ExperimentalTestApi::class)
@Test
- fun resizedLarge_shouldBeIcon() {
+ fun toggleLargeTile_shouldBeIcon() {
var tiles by mutableStateOf(TestEditTiles)
val listState = EditTileListState(tiles, 4)
composeRule.setContent {
- EditTileGridUnderTest(listState) { spec ->
- tiles =
- tiles.map {
- if (it.tile.tileSpec == spec) {
- toggleWidth(it)
- } else {
- it
- }
- }
- }
+ EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
}
composeRule.waitForIdle()
@@ -113,9 +101,58 @@
assertThat(tiles.find { it.tile.tileSpec.spec == "tileD_large" }?.width).isEqualTo(1)
}
+ @Test
+ fun resizedLarge_shouldBeIcon() {
+ var tiles by mutableStateOf(TestEditTiles)
+ val listState = EditTileListState(tiles, 4)
+ composeRule.setContent {
+ EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
+ }
+ composeRule.waitForIdle()
+
+ composeRule
+ .onNodeWithContentDescription("tileA")
+ .performClick() // Select
+ .performTouchInput { // Resize down
+ swipeRight()
+ }
+ composeRule.waitForIdle()
+
+ assertThat(tiles.find { it.tile.tileSpec.spec == "tileA" }?.width).isEqualTo(1)
+ }
+
+ @Test
+ fun resizedIcon_shouldBeLarge() {
+ var tiles by mutableStateOf(TestEditTiles)
+ val listState = EditTileListState(tiles, 4)
+ composeRule.setContent {
+ EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
+ }
+ composeRule.waitForIdle()
+
+ composeRule
+ .onNodeWithContentDescription("tileD_large")
+ .performClick() // Select
+ .performTouchInput { // Resize down
+ swipeLeft()
+ }
+ composeRule.waitForIdle()
+
+ assertThat(tiles.find { it.tile.tileSpec.spec == "tileD_large" }?.width).isEqualTo(1)
+ }
+
companion object {
- private fun toggleWidth(tile: SizedTile<EditTileViewModel>): SizedTile<EditTileViewModel> {
- return SizedTileImpl(tile.tile, width = if (tile.isIcon) 2 else 1)
+ private fun List<SizedTile<EditTileViewModel>>.resize(
+ spec: TileSpec,
+ toIcon: Boolean,
+ ): List<SizedTile<EditTileViewModel>> {
+ return map {
+ if (it.tile.tileSpec == spec) {
+ SizedTileImpl(it.tile, width = if (toIcon) 1 else 2)
+ } else {
+ it
+ }
+ }
}
private fun createEditTile(tileSpec: String): SizedTile<EditTileViewModel> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 4bd0c75..a6afd0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -453,6 +453,7 @@
mUiEventLogger,
() -> mKosmos.getInteractionJankMonitor(),
mJavaAdapter,
+ () -> mKeyguardInteractor,
() -> mKeyguardTransitionInteractor,
() -> mShadeInteractor,
() -> mKosmos.getDeviceUnlockedInteractor(),
@@ -611,6 +612,7 @@
new UiEventLoggerFake(),
() -> mKosmos.getInteractionJankMonitor(),
mJavaAdapter,
+ () -> mKeyguardInteractor,
() -> mKeyguardTransitionInteractor,
() -> mShadeInteractor,
() -> mKosmos.getDeviceUnlockedInteractor(),
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/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 4fd830d..a8bcfbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -756,6 +756,27 @@
assertThat(latest!!.level).isEqualTo(4)
}
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun satBasedIcon_reportsLevelZeroWhenOutOfService() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // GIVEN a satellite connection
+ connectionRepository.isNonTerrestrial.value = true
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepository.inflateSignalStrength.value = true
+
+ connectionRepository.primaryLevel.value = 4
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepository.isInService.value = false
+ connectionRepository.primaryLevel.value = 4
+
+ // THEN level reports 0, by policy
+ assertThat(latest!!.level).isEqualTo(0)
+ }
+
private fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) =
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/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/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index 64ae051..e6c98cd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.domain.interactor
+import android.service.dream.dreamManager
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -39,10 +41,12 @@
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
glanceableHubTransitions = glanceableHubTransitions,
+ communalInteractor = communalInteractor,
communalSceneInteractor = communalSceneInteractor,
communalSettingsInteractor = communalSettingsInteractor,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ dreamManager = dreamManager,
deviceEntryInteractor = deviceEntryInteractor,
)
}
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/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
index 52416ba..ace1157 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
@@ -41,6 +41,5 @@
trustRepository = trustRepository,
alternateBouncerInteractor = alternateBouncerInteractor,
powerInteractor = powerInteractor,
- keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..ef10459
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.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.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.dozingToGlanceableHubTransitionViewModel by Fixture {
+ DozingToGlanceableHubTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 2deeb25..cfc31c7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
@@ -36,6 +37,7 @@
uiEventLogger,
{ interactionJankMonitor },
mock(),
+ { keyguardInteractor },
{ keyguardTransitionInteractor },
{ shadeInteractor },
{ deviceUnlockedInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStateCallbackInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStateCallbackInteractorKosmos.kt
new file mode 100644
index 0000000..58dd522
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStateCallbackInteractorKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.domain.interactor
+
+import com.android.keyguard.trustManager
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.keyguard.dismissCallbackRegistry
+import com.android.systemui.keyguard.domain.interactor.KeyguardStateCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
+import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.keyguardStateCallbackInteractor by
+ Kosmos.Fixture {
+ KeyguardStateCallbackInteractor(
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
+ selectedUserInteractor = selectedUserInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ trustInteractor = trustInteractor,
+ simBouncerInteractor = simBouncerInteractor,
+ dismissCallbackRegistry = dismissCallbackRegistry,
+ wmLockscreenVisibilityInteractor = windowManagerLockscreenVisibilityInteractor,
+ trustManager = trustManager,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index ffd8aab..a9e117a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -25,6 +25,7 @@
import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
@@ -66,6 +67,7 @@
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
+ dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
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/TEST_MAPPING b/ravenwood/TEST_MAPPING
index d4d188d..86246e2 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -34,15 +34,18 @@
},
{
"name": "CarLibHostUnitTest",
- "host": true
+ "host": true,
+ "keywords": ["automotive_code_coverage"]
},
{
"name": "CarServiceHostUnitTest",
- "host": true
+ "host": true,
+ "keywords": ["automotive_code_coverage"]
},
{
"name": "CarSystemUIRavenTests",
- "host": true
+ "host": true,
+ "keywords": ["automotive_code_coverage"]
},
{
"name": "CtsAccountManagerTestCasesRavenwood",
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/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/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index 759f02e..d84b205 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -53,12 +53,10 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
-import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
/**
* This class implements helper methods for synchronously interacting with AppSearch while
@@ -74,8 +72,9 @@
private final AppSearchManager mAppSearchManager;
private final PackageManager mPackageManager;
private final Object mLock = new Object();
+
@GuardedBy("mLock")
- private Future<AndroidFuture<Boolean>> mCurrentSyncTask;
+ private Future<?> mCurrentSyncTask;
// Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
// by permissions.
@@ -105,7 +104,7 @@
AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB)
.build();
AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
- Callable<AndroidFuture<Boolean>> callableTask =
+ Runnable runnable =
() -> {
try (FutureAppSearchSession staticMetadataSearchSession =
new FutureAppSearchSessionImpl(
@@ -125,14 +124,13 @@
} catch (Exception ex) {
settableSyncStatus.completeExceptionally(ex);
}
- return settableSyncStatus;
};
synchronized (mLock) {
if (mCurrentSyncTask != null && !mCurrentSyncTask.isDone()) {
- boolean cancel = mCurrentSyncTask.cancel(false);
+ var unused = mCurrentSyncTask.cancel(false);
}
- mCurrentSyncTask = mExecutor.submit(callableTask);
+ mCurrentSyncTask = mExecutor.submit(runnable);
}
return settableSyncStatus;
@@ -140,11 +138,7 @@
/** This method shuts down the {@link MetadataSyncAdapter} scheduler. */
public void shutDown() {
- try {
- var unused = mExecutor.awaitTermination(30, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- Slog.e(TAG, "Error shutting down MetadataSyncAdapter scheduler", e);
- }
+ mExecutor.shutdown();
}
@WorkerThread
diff --git a/services/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/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 0e19347..210301e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -133,6 +133,7 @@
import com.android.server.am.nano.VMInfo;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.utils.AnrTimer;
import com.android.server.utils.Slogf;
import dalvik.annotation.optimization.NeverCompile;
@@ -285,6 +286,8 @@
return -1;
case "trace-ipc":
return runTraceIpc(pw);
+ case "trace-timer":
+ return runTraceTimer(pw);
case "profile":
return runProfile(pw);
case "dumpheap":
@@ -1062,6 +1065,23 @@
return 0;
}
+ // Update AnrTimer tracing.
+ private int runTraceTimer(PrintWriter pw) throws RemoteException {
+ if (!AnrTimer.traceFeatureEnabled()) return -1;
+
+ // Delegate all argument parsing to the AnrTimer method.
+ try {
+ final String result = AnrTimer.traceTimers(peekRemainingArgs());
+ if (result != null) {
+ pw.println(result);
+ }
+ return 0;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println("Error: bad trace-timer command: " + e);
+ return -1;
+ }
+ }
+
// NOTE: current profiles can only be started on default display (even on automotive builds with
// passenger displays), so there's no need to pass a display-id
private int runProfile(PrintWriter pw) throws RemoteException {
@@ -4352,6 +4372,7 @@
pw.println(" start: start tracing IPC transactions.");
pw.println(" stop: stop tracing IPC transactions and dump the results to file.");
pw.println(" --dump-file <FILE>: Specify the file the trace should be dumped to.");
+ anrTimerHelp(pw);
pw.println(" profile start [--user <USER_ID> current]");
pw.println(" [--clock-type <TYPE>]");
pw.println(" [" + PROFILER_OUTPUT_VERSION_FLAG + " VERSION]");
@@ -4605,4 +4626,19 @@
Intent.printIntentArgsHelp(pw, "");
}
}
+
+ static void anrTimerHelp(PrintWriter pw) {
+ // Return silently if tracing is not feature-enabled.
+ if (!AnrTimer.traceFeatureEnabled()) return;
+
+ String h = AnrTimer.traceTimers(new String[]{"help"});
+ if (h == null) {
+ return;
+ }
+
+ pw.println(" trace-timer <cmd>");
+ for (String s : h.split("\n")) {
+ pw.println(" " + s);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index f7085b4..15f1085 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -57,6 +57,7 @@
import android.app.ApplicationThreadConstants;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
+import android.app.BroadcastStickyCache;
import android.app.IApplicationThread;
import android.app.compat.CompatChanges;
import android.appwidget.AppWidgetManager;
@@ -685,6 +686,7 @@
boolean serialized, boolean sticky, int userId) {
mService.enforceNotIsolatedCaller("broadcastIntent");
+ int result;
synchronized (mService) {
intent = verifyBroadcastLocked(intent);
@@ -706,7 +708,7 @@
final long origId = Binder.clearCallingIdentity();
try {
- return broadcastIntentLocked(callerApp,
+ result = broadcastIntentLocked(callerApp,
callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
@@ -717,6 +719,10 @@
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
+ if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
+ BroadcastStickyCache.incrementVersion(intent.getAction());
+ }
+ return result;
}
// Not the binder call surface
@@ -727,6 +733,7 @@
boolean serialized, boolean sticky, int userId,
BackgroundStartPrivileges backgroundStartPrivileges,
@Nullable int[] broadcastAllowList) {
+ int result;
synchronized (mService) {
intent = verifyBroadcastLocked(intent);
@@ -734,7 +741,7 @@
String[] requiredPermissions = requiredPermission == null ? null
: new String[] {requiredPermission};
try {
- return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+ result = broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
resultToApp, resultTo, resultCode, resultData, resultExtras,
requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
uid, realCallingUid, realCallingPid, userId,
@@ -744,6 +751,10 @@
Binder.restoreCallingIdentity(origId);
}
}
+ if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
+ BroadcastStickyCache.incrementVersion(intent.getAction());
+ }
+ return result;
}
@GuardedBy("mService")
@@ -1442,6 +1453,7 @@
list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
callingUid, callerAppProcessState, resolvedType));
}
+ BroadcastStickyCache.incrementVersion(intent.getAction());
}
}
@@ -1708,6 +1720,7 @@
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
+ final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
synchronized (mStickyBroadcasts) {
ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
if (stickies != null) {
@@ -1724,12 +1737,16 @@
if (list.size() <= 0) {
stickies.remove(intent.getAction());
}
+ changedStickyBroadcasts.add(intent.getAction());
}
if (stickies.size() <= 0) {
mStickyBroadcasts.remove(userId);
}
}
}
+ for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
+ BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
+ }
}
void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -1892,7 +1909,9 @@
private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
- }private List<ResolveInfo> collectReceiverComponents(
+ }
+
+ private List<ResolveInfo> collectReceiverComponents(
Intent intent, String resolvedType, int callingUid, int callingPid,
int[] users, int[] broadcastAllowList) {
// TODO: come back and remove this assumption to triage all broadcasts
@@ -2108,9 +2127,18 @@
}
void removeStickyBroadcasts(int userId) {
+ final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
synchronized (mStickyBroadcasts) {
+ final ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
+ mStickyBroadcasts.get(userId);
+ if (stickies != null) {
+ changedStickyBroadcasts.addAll(stickies.keySet());
+ }
mStickyBroadcasts.remove(userId);
}
+ for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
+ BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
+ }
}
@NeverCompile
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/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6ae6f3d..6af4be5 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -619,7 +619,7 @@
this.op = op;
this.uid = uid;
this.uidState = uidState;
- this.packageName = packageName;
+ this.packageName = packageName.intern();
// We keep an invariant that the persistent device will always have an entry in
// mDeviceAttributedOps.
mDeviceAttributedOps.put(PERSISTENT_DEVICE_ID_DEFAULT,
@@ -1031,7 +1031,7 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+ String pkgName = intent.getData().getEncodedSchemeSpecificPart().intern();
int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
if (action.equals(ACTION_PACKAGE_ADDED)
@@ -1235,7 +1235,7 @@
Ops ops = uidState.pkgOps.get(packageName);
if (ops == null) {
ops = new Ops(packageName, uidState);
- uidState.pkgOps.put(packageName, ops);
+ uidState.pkgOps.put(packageName.intern(), ops);
}
SparseIntArray packageModes =
@@ -4739,7 +4739,7 @@
return null;
}
ops = new Ops(packageName, uidState);
- uidState.pkgOps.put(packageName, ops);
+ uidState.pkgOps.put(packageName.intern(), ops);
}
if (edit) {
@@ -5076,7 +5076,7 @@
Ops ops = uidState.pkgOps.get(pkgName);
if (ops == null) {
ops = new Ops(pkgName, uidState);
- uidState.pkgOps.put(pkgName, ops);
+ uidState.pkgOps.put(pkgName.intern(), ops);
}
ops.put(op.op, op);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 55d9c6e..0fd22c5 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -17,6 +17,11 @@
import static android.media.audio.Flags.scoManagedByAudio;
+import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
+import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_HEADSET;
+import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER;
+import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_SCO;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.compat.CompatChanges;
@@ -64,6 +69,7 @@
import android.util.PrintWriterPrinter;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.audio.AudioService.BtCommDeviceActiveType;
import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
@@ -333,8 +339,8 @@
Log.v(TAG, "setCommunicationDevice, device: " + device + ", uid: " + uid);
}
- synchronized (mDeviceStateLock) {
- if (device == null) {
+ if (device == null) {
+ synchronized (mDeviceStateLock) {
CommunicationRouteClient client = getCommunicationRouteClientForUid(uid);
if (client == null) {
return false;
@@ -835,15 +841,15 @@
return isDeviceOnForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
}
- /*package*/ boolean isBluetoothScoActive() {
+ private boolean isBluetoothScoActive() {
return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
}
- /*package*/ boolean isBluetoothBleHeadsetActive() {
+ private boolean isBluetoothBleHeadsetActive() {
return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLE_HEADSET);
}
- /*package*/ boolean isBluetoothBleSpeakerActive() {
+ private boolean isBluetoothBleSpeakerActive() {
return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLE_SPEAKER);
}
@@ -1437,7 +1443,20 @@
}
mCurCommunicationPortId = portId;
- mAudioService.postScoDeviceActive(isBluetoothScoActive());
+ @BtCommDeviceActiveType int btCommDeviceActiveType = 0;
+ if (equalScoLeaVcIndexRange()) {
+ if (isBluetoothScoActive()) {
+ btCommDeviceActiveType = BT_COMM_DEVICE_ACTIVE_SCO;
+ } else if (isBluetoothBleHeadsetActive()) {
+ btCommDeviceActiveType = BT_COMM_DEVICE_ACTIVE_BLE_HEADSET;
+ } else if (isBluetoothBleSpeakerActive()) {
+ btCommDeviceActiveType = BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER;
+ }
+ mAudioService.postBtCommDeviceActive(btCommDeviceActiveType);
+ } else {
+ mAudioService.postBtCommDeviceActive(
+ isBluetoothScoActive() ? BT_COMM_DEVICE_ACTIVE_SCO : btCommDeviceActiveType);
+ }
final int nbDispatchers = mCommDevDispatchers.beginBroadcast();
for (int i = 0; i < nbDispatchers; i++) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9e11081..e83b036 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -66,6 +66,7 @@
import static com.android.media.audio.Flags.asDeviceConnectionFailure;
import static com.android.media.audio.Flags.audioserverPermissions;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
+import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
import static com.android.media.audio.Flags.replaceStreamBtSco;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
import static com.android.media.audio.Flags.setStreamVolumeOrder;
@@ -470,7 +471,7 @@
private static final int MSG_CONFIGURATION_CHANGED = 54;
private static final int MSG_BROADCAST_MASTER_MUTE = 55;
private static final int MSG_UPDATE_CONTEXTUAL_VOLUMES = 56;
- private static final int MSG_SCO_DEVICE_ACTIVE_UPDATE = 57;
+ private static final int MSG_BT_COMM_DEVICE_ACTIVE_UPDATE = 57;
/**
* Messages handled by the {@link SoundDoseHelper}, do not exceed
@@ -766,7 +767,21 @@
* @see System#MUTE_STREAMS_AFFECTED */
private int mUserMutableStreams;
- private final AtomicBoolean mScoDeviceActive = new AtomicBoolean(false);
+ /** The active bluetooth device type used for communication is sco. */
+ /*package*/ static final int BT_COMM_DEVICE_ACTIVE_SCO = 1;
+ /** The active bluetooth device type used for communication is ble headset. */
+ /*package*/ static final int BT_COMM_DEVICE_ACTIVE_BLE_HEADSET = 1 << 1;
+ /** The active bluetooth device type used for communication is ble speaker. */
+ /*package*/ static final int BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER = 1 << 2;
+ @IntDef({
+ BT_COMM_DEVICE_ACTIVE_SCO, BT_COMM_DEVICE_ACTIVE_BLE_HEADSET,
+ BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BtCommDeviceActiveType {
+ }
+
+ private final AtomicInteger mBtCommDeviceActive = new AtomicInteger(0);
@NonNull
private SoundEffectsHelper mSfxHelper;
@@ -2522,12 +2537,18 @@
// this should not happen, throwing exception
throw new IllegalArgumentException("STREAM_BLUETOOTH_SCO is deprecated");
}
- return streamType == AudioSystem.STREAM_VOICE_CALL && mScoDeviceActive.get();
+ return streamType == AudioSystem.STREAM_VOICE_CALL
+ && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO;
} else {
return streamType == AudioSystem.STREAM_BLUETOOTH_SCO;
}
}
+ private boolean isStreamBluetoothComm(int streamType) {
+ return (streamType == AudioSystem.STREAM_VOICE_CALL && mBtCommDeviceActive.get() != 0)
+ || streamType == AudioSystem.STREAM_BLUETOOTH_SCO;
+ }
+
private void dumpStreamStates(PrintWriter pw) {
pw.println("\nStream volumes (device: index)");
int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -4761,7 +4782,7 @@
+ asDeviceConnectionFailure());
pw.println("\tandroid.media.audio.autoPublicVolumeApiHardening:"
+ autoPublicVolumeApiHardening());
- pw.println("\tandroid.media.audio.Flags.automaticBtDeviceType:"
+ pw.println("\tandroid.media.audio.automaticBtDeviceType:"
+ automaticBtDeviceType());
pw.println("\tandroid.media.audio.featureSpatialAudioHeadtrackingLowLatency:"
+ featureSpatialAudioHeadtrackingLowLatency());
@@ -4783,6 +4804,8 @@
+ absVolumeIndexFix());
pw.println("\tcom.android.media.audio.replaceStreamBtSco:"
+ replaceStreamBtSco());
+ pw.println("\tcom.android.media.audio.equalScoLeaVcIndexRange:"
+ + equalScoLeaVcIndexRange());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -4896,7 +4919,7 @@
final VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
if (!replaceStreamBtSco() && (streamType == AudioManager.STREAM_VOICE_CALL)
- && isInCommunication() && mDeviceBroker.isBluetoothScoActive()) {
+ && isInCommunication() && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
Log.i(TAG, "setStreamVolume for STREAM_VOICE_CALL, switching to STREAM_BLUETOOTH_SCO");
streamType = AudioManager.STREAM_BLUETOOTH_SCO;
}
@@ -5947,10 +5970,10 @@
final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE
|| ringerMode == AudioManager.RINGER_MODE_SILENT;
final boolean shouldRingSco = ringerMode == AudioManager.RINGER_MODE_VIBRATE
- && mDeviceBroker.isBluetoothScoActive();
+ && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO;
final boolean shouldRingBle = ringerMode == AudioManager.RINGER_MODE_VIBRATE
- && (mDeviceBroker.isBluetoothBleHeadsetActive()
- || mDeviceBroker.isBluetoothBleSpeakerActive());
+ && (mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_BLE_HEADSET
+ || mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER);
// Ask audio policy engine to force use Bluetooth SCO/BLE channel if needed
final String eventSource = "muteRingerModeStreams() from u/pid:" + Binder.getCallingUid()
+ "/" + Binder.getCallingPid();
@@ -7419,7 +7442,8 @@
case AudioSystem.PLATFORM_VOICE:
if (isInCommunication()
|| mAudioSystem.isStreamActive(AudioManager.STREAM_VOICE_CALL, 0)) {
- if (!replaceStreamBtSco() && mDeviceBroker.isBluetoothScoActive()) {
+ if (!replaceStreamBtSco()
+ && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
if (DEBUG_VOL) {
Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
}
@@ -7463,7 +7487,8 @@
}
default:
if (isInCommunication()) {
- if (!replaceStreamBtSco() && mDeviceBroker.isBluetoothScoActive()) {
+ if (!replaceStreamBtSco()
+ && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO");
return AudioSystem.STREAM_BLUETOOTH_SCO;
} else {
@@ -7788,15 +7813,15 @@
0 /*delay*/);
}
- /*package*/ void postScoDeviceActive(boolean scoDeviceActive) {
+ /*package*/ void postBtCommDeviceActive(@BtCommDeviceActiveType int btCommDeviceActive) {
sendMsg(mAudioHandler,
- MSG_SCO_DEVICE_ACTIVE_UPDATE,
- SENDMSG_QUEUE, scoDeviceActive ? 1 : 0 /*arg1*/, 0 /*arg2*/, null /*obj*/,
+ MSG_BT_COMM_DEVICE_ACTIVE_UPDATE,
+ SENDMSG_QUEUE, btCommDeviceActive /*arg1*/, 0 /*arg2*/, null /*obj*/,
0 /*delay*/);
}
- private void onUpdateScoDeviceActive(boolean scoDeviceActive) {
- if (mScoDeviceActive.compareAndSet(!scoDeviceActive, scoDeviceActive)) {
+ private void onUpdateBtCommDeviceActive(@BtCommDeviceActiveType int btCommDeviceActive) {
+ if (mBtCommDeviceActive.getAndSet(btCommDeviceActive) != btCommDeviceActive) {
getVssForStreamOrDefault(AudioSystem.STREAM_VOICE_CALL).updateIndexFactors();
}
}
@@ -8997,7 +9022,7 @@
}
public void updateIndexFactors() {
- if (!replaceStreamBtSco()) {
+ if (!replaceStreamBtSco() && !equalScoLeaVcIndexRange()) {
return;
}
@@ -9008,10 +9033,18 @@
mIndexMax = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
}
- // SCO devices have a different min index
- if (isStreamBluetoothSco(mStreamType)) {
+ if (!equalScoLeaVcIndexRange() && isStreamBluetoothSco(mStreamType)) {
+ // SCO devices have a different min index
mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
mIndexStepFactor = 1.f;
+ } else if (equalScoLeaVcIndexRange() && isStreamBluetoothComm(mStreamType)) {
+ // For non SCO devices the stream state does not change the min index
+ if (mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
+ mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+ } else {
+ mIndexMin = MIN_STREAM_VOLUME[mStreamType] * 10;
+ }
+ mIndexStepFactor = 1.f;
} else {
mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] * 10;
mIndexStepFactor = (float) (mIndexMax - mIndexMin) / (float) (
@@ -9207,7 +9240,7 @@
private void setStreamVolumeIndex(int index, int device) {
// Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
// This allows RX path muting by the audio HAL only when explicitly muted but not when
- // index is just set to 0 to repect BT requirements
+ // index is just set to 0 to respect BT requirements
if (isStreamBluetoothSco(mStreamType) && index == 0 && !isFullyMuted()) {
index = 1;
}
@@ -10217,8 +10250,8 @@
onUpdateContextualVolumes();
break;
- case MSG_SCO_DEVICE_ACTIVE_UPDATE:
- onUpdateScoDeviceActive(msg.arg1 != 0);
+ case MSG_BT_COMM_DEVICE_ACTIVE_UPDATE:
+ onUpdateBtCommDeviceActive(msg.arg1);
break;
case MusicFxHelper.MSG_EFFECT_CLIENT_GONE:
@@ -10809,7 +10842,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,
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/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 5d85089..2d802b2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -49,7 +49,6 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorPropertiesInternal;
-import android.hardware.biometrics.face.IFace;
import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -73,6 +72,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.SystemService;
+import com.android.server.biometrics.sensors.face.FaceService;
import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -211,7 +211,7 @@
*/
@VisibleForTesting
public String[] getFaceAidlInstances() {
- return ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+ return FaceService.getDeclaredInstances();
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index bd6d593..8c98872 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_FACE;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.face.FaceSensorConfigurations.getIFace;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -60,6 +61,7 @@
import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
@@ -753,7 +755,7 @@
public FaceService(Context context) {
this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), null /* faceProvider */,
- () -> ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR));
+ () -> getDeclaredInstances());
}
@VisibleForTesting FaceService(Context context,
@@ -778,8 +780,7 @@
mFaceProvider = faceProvider != null ? faceProvider : (name) -> {
final String fqName = IFace.DESCRIPTOR + "/" + name;
- final IFace face = IFace.Stub.asInterface(
- Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+ final IFace face = getIFace(fqName);
if (face == null) {
Slog.e(TAG, "Unable to get declared service: " + fqName);
return null;
@@ -835,6 +836,23 @@
*/
public static native void releaseSurfaceHandle(@NonNull NativeHandle handle);
+ /**
+ * Get all face hal instances declared in manifest
+ * @return instance names
+ */
+ public static String[] getDeclaredInstances() {
+ String[] a = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+ Slog.i(TAG, "Before:getDeclaredInstances: IFace instance found, a.length="
+ + a.length);
+ if (!ArrayUtils.contains(a, "virtual")) {
+ // Now, the virtual hal is registered with IVirtualHal interface and it is also
+ // moved from vendor to system_ext partition without a device manifest. So
+ // if the old vhal is not declared, add here.
+ a = ArrayUtils.appendElement(String.class, a, "virtual");
+ }
+ Slog.i(TAG, "After:getDeclaredInstances: a.length=" + a.length);
+ return a;
+ }
void syncEnrollmentsNow() {
Utils.checkPermissionOrShell(getContext(), MANAGE_FACE);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index dca1491..3ed01d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -23,6 +23,9 @@
import android.hardware.biometrics.face.AuthenticationFrame;
import android.hardware.biometrics.face.BaseFrame;
import android.hardware.biometrics.face.EnrollmentFrame;
+import android.hardware.biometrics.face.virtualhal.AcquiredInfoAndVendorCode;
+import android.hardware.biometrics.face.virtualhal.EnrollmentProgressStep;
+import android.hardware.biometrics.face.virtualhal.NextEnrollment;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticationFrame;
import android.hardware.face.FaceEnrollFrame;
@@ -50,6 +53,7 @@
public class BiometricTestSessionImpl extends ITestSession.Stub {
private static final String TAG = "face/aidl/BiometricTestSessionImpl";
+ private static final int VHAL_ENROLLMENT_ID = 9999;
@NonNull private final Context mContext;
private final int mSensorId;
@@ -144,16 +148,35 @@
super.setTestHalEnabled_enforcePermission();
- mProvider.setTestHalEnabled(enabled);
mSensor.setTestHalEnabled(enabled);
+ mProvider.setTestHalEnabled(enabled);
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void startEnroll(int userId) {
+ public void startEnroll(int userId) throws RemoteException {
super.startEnroll_enforcePermission();
+ Slog.i(TAG, "startEnroll(): isVhalForTesting=" + mProvider.isVhalForTesting());
+ if (mProvider.isVhalForTesting()) {
+ final AcquiredInfoAndVendorCode[] acquiredInfoAndVendorCodes =
+ {new AcquiredInfoAndVendorCode()};
+ final EnrollmentProgressStep[] enrollmentProgressSteps =
+ {new EnrollmentProgressStep(), new EnrollmentProgressStep()};
+ enrollmentProgressSteps[0].durationMs = 100;
+ enrollmentProgressSteps[0].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes;
+ enrollmentProgressSteps[1].durationMs = 200;
+ enrollmentProgressSteps[1].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes;
+
+ final NextEnrollment nextEnrollment = new NextEnrollment();
+ nextEnrollment.id = VHAL_ENROLLMENT_ID;
+ nextEnrollment.progressSteps = enrollmentProgressSteps;
+ nextEnrollment.result = true;
+ mProvider.getVhal().setNextEnrollment(nextEnrollment);
+ mProvider.getVhal().setOperationAuthenticateDuration(6000);
+ }
+
mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
null /* previewSurface */, false /* debugConsent */,
@@ -166,6 +189,10 @@
super.finishEnroll_enforcePermission();
+ if (mProvider.isVhalForTesting()) {
+ return;
+ }
+
int nextRandomId = mRandom.nextInt();
while (mEnrollmentIds.contains(nextRandomId)) {
nextRandomId = mRandom.nextInt();
@@ -178,11 +205,16 @@
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void acceptAuthentication(int userId) {
+ public void acceptAuthentication(int userId) throws RemoteException {
// Fake authentication with any of the existing faces
super.acceptAuthentication_enforcePermission();
+ if (mProvider.isVhalForTesting()) {
+ mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID);
+ return;
+ }
+
List<Face> faces = FaceUtils.getInstance(mSensorId)
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
@@ -196,10 +228,15 @@
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void rejectAuthentication(int userId) {
+ public void rejectAuthentication(int userId) throws RemoteException {
super.rejectAuthentication_enforcePermission();
+ if (mProvider.isVhalForTesting()) {
+ mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID + 1);
+ return;
+ }
+
mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
}
@@ -236,11 +273,17 @@
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void cleanupInternalState(int userId) {
+ public void cleanupInternalState(int userId) throws RemoteException {
super.cleanupInternalState_enforcePermission();
Slog.d(TAG, "cleanupInternalState: " + userId);
+
+ if (mProvider.isVhalForTesting()) {
+ Slog.i(TAG, "cleanup virtualhal configurations");
+ mProvider.getVhal().resetConfigurations(); //setEnrollments(new int[]{});
+ }
+
mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index bb213bf..5127e68 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.hardware.face.FaceSensorConfigurations.getIFace;
+import static android.hardware.face.FaceSensorConfigurations.remapFqName;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -32,6 +35,7 @@
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.face.virtualhal.IVirtualHal;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceEnrollOptions;
@@ -54,6 +58,7 @@
import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.BiometricDanglingReceiver;
import com.android.server.biometrics.BiometricHandlerProvider;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -130,6 +135,11 @@
private AuthenticationStatsCollector mAuthenticationStatsCollector;
@Nullable
private IFace mDaemon;
+ @Nullable
+ private IVirtualHal mVhal;
+ @Nullable
+ private String mHalInstanceNameCurrent;
+
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -286,14 +296,37 @@
if (mTestHalEnabled) {
return true;
}
- return ServiceManager.checkService(IFace.DESCRIPTOR + "/" + mHalInstanceName) != null;
+ return ServiceManager.checkService(
+ remapFqName(IFace.DESCRIPTOR + "/" + mHalInstanceName)) != null;
}
@Nullable
@VisibleForTesting
synchronized IFace getHalInstance() {
if (mTestHalEnabled) {
- return new TestHal();
+ if (Flags.useVhalForTesting()) {
+ if (!mHalInstanceNameCurrent.contains("virtual")) {
+ Slog.i(getTag(), "Switching face hal from " + mHalInstanceName
+ + " to virtual hal");
+ mHalInstanceNameCurrent = "virtual";
+ mDaemon = null;
+ }
+ } else {
+ // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
+ // the test HAL for all sensors under that HAL. This can be updated in the future if
+ // necessary.
+ return new TestHal();
+ }
+ } else {
+ if (mHalInstanceNameCurrent == null) {
+ mHalInstanceNameCurrent = mHalInstanceName;
+ } else if (mHalInstanceNameCurrent.contains("virtual")
+ && mHalInstanceNameCurrent != mHalInstanceName) {
+ Slog.i(getTag(), "Switching face from virtual hal " + "to "
+ + mHalInstanceName);
+ mHalInstanceNameCurrent = mHalInstanceName;
+ mDaemon = null;
+ }
}
if (mDaemon != null) {
@@ -302,10 +335,7 @@
Slog.d(getTag(), "Daemon was null, reconnecting");
- mDaemon = IFace.Stub.asInterface(
- Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(
- IFace.DESCRIPTOR + "/" + mHalInstanceName)));
+ mDaemon = getIFace(IFace.DESCRIPTOR + "/" + mHalInstanceNameCurrent);
if (mDaemon == null) {
Slog.e(getTag(), "Unable to get daemon");
return null;
@@ -833,7 +863,13 @@
}
void setTestHalEnabled(boolean enabled) {
+ final boolean changed = enabled != mTestHalEnabled;
mTestHalEnabled = enabled;
+ Slog.i(getTag(), "setTestHalEnabled(): isVhalForTestingFlags=" + Flags.useVhalForTesting()
+ + " mTestHalEnabled=" + mTestHalEnabled + " changed=" + changed);
+ if (changed && isVhalForTesting()) {
+ getHalInstance();
+ }
}
@Override
@@ -851,9 +887,40 @@
}
/**
+ * Return true if vhal_for_testing feature is enabled and test is active
+ */
+ public boolean isVhalForTesting() {
+ return (Flags.useVhalForTesting() && mTestHalEnabled);
+ }
+
+
+ /**
* Sends a face re enroll notification.
*/
public void sendFaceReEnrollNotification() {
mAuthenticationStatsCollector.sendFaceReEnrollNotification();
}
+
+ /**
+ * Sends a fingerprint enroll notification.
+ */
+ public void sendFingerprintReEnrollNotification() {
+ mAuthenticationStatsCollector.sendFingerprintReEnrollNotification();
+ }
+
+ /**
+ * Return virtual hal AIDL interface if it is used for testing
+ *
+ */
+ public IVirtualHal getVhal() throws RemoteException {
+ if (mVhal == null && isVhalForTesting()) {
+ mVhal = IVirtualHal.Stub.asInterface(
+ Binder.allowBlocking(
+ ServiceManager.waitForService(
+ IVirtualHal.DESCRIPTOR + "/"
+ + mHalInstanceNameCurrent)));
+ Slog.d(getTag(), "getVhal " + mHalInstanceNameCurrent);
+ }
+ return mVhal;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 6f95349..9fddcfc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -17,6 +17,7 @@
package com.android.server.biometrics.sensors.face.aidl;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE;
+import static android.hardware.face.FaceSensorConfigurations.remapFqName;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -337,7 +338,8 @@
if (mTestHalEnabled) {
return true;
}
- return ServiceManager.checkService(IFace.DESCRIPTOR + "/" + halInstanceName) != null;
+ return ServiceManager.checkService(
+ remapFqName(IFace.DESCRIPTOR + "/" + halInstanceName)) != null;
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e7fd8f7..ae33b83 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -571,6 +571,10 @@
private final DisplayNotificationManager mDisplayNotificationManager;
private final ExternalDisplayStatsService mExternalDisplayStatsService;
+ // Manages the relative placement of extended displays
+ @Nullable
+ private final DisplayTopologyCoordinator mDisplayTopologyCoordinator;
+
/**
* Applications use {@link android.view.Display#getRefreshRate} and
* {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -644,6 +648,11 @@
mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext,
mExternalDisplayStatsService);
mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
+ if (mFlags.isDisplayTopologyEnabled()) {
+ mDisplayTopologyCoordinator = new DisplayTopologyCoordinator();
+ } else {
+ mDisplayTopologyCoordinator = null;
+ }
}
public void setupSchedulerPolicies() {
@@ -3474,9 +3483,13 @@
mSmallAreaDetectionController.dump(pw);
}
+ if (mDisplayTopologyCoordinator != null) {
+ pw.println();
+ mDisplayTopologyCoordinator.dump(pw);
+ }
+
pw.println();
mFlags.dump(pw);
-
}
private static float[] getFloatArray(TypedArray array) {
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
new file mode 100644
index 0000000..631f147
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class manages the relative placement (topology) of extended displays. It is responsible for
+ * updating and persisting the topology.
+ */
+class DisplayTopologyCoordinator {
+
+ /**
+ * The topology tree
+ */
+ @Nullable
+ private TopologyTreeNode mRoot;
+
+ /**
+ * The logical display ID of the primary display that will show certain UI elements.
+ * This is not necessarily the same as the default display.
+ */
+ private int mPrimaryDisplayId;
+
+ /**
+ * Print the object's state and debug information into the given stream.
+ * @param pw The stream to dump information to.
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("DisplayTopologyCoordinator:");
+ pw.println("--------------------");
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.increaseIndent();
+
+ ipw.println("mPrimaryDisplayId: " + mPrimaryDisplayId);
+
+ ipw.println("Topology tree:");
+ if (mRoot != null) {
+ ipw.increaseIndent();
+ mRoot.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+
+ private static class TopologyTreeNode {
+
+ /**
+ * The logical display ID
+ */
+ private int mDisplayId;
+
+ private final List<TopologyTreeNode> mChildren = new ArrayList<>();
+
+ /**
+ * The position of this display relative to its parent.
+ */
+ private Position mPosition;
+
+ /**
+ * The distance from the top edge of the parent display to the top edge of this display (in
+ * case of POSITION_LEFT or POSITION_RIGHT) or from the left edge of the parent display
+ * to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit
+ * used is density-independent pixels (dp).
+ */
+ private double mOffset;
+
+ /**
+ * Print the object's state and debug information into the given stream.
+ * @param ipw The stream to dump information to.
+ */
+ void dump(IndentingPrintWriter ipw) {
+ ipw.println("Display {id=" + mDisplayId + ", position=" + mPosition
+ + ", offset=" + mOffset + "}");
+ ipw.increaseIndent();
+ for (TopologyTreeNode child : mChildren) {
+ child.dump(ipw);
+ }
+ ipw.decreaseIndent();
+ }
+
+ private enum Position {
+ POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index f600e7f..df66893 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -69,6 +69,10 @@
Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY,
Flags::enableModeLimitForExternalDisplay);
+ private final FlagState mDisplayTopology = new FlagState(
+ Flags.FLAG_DISPLAY_TOPOLOGY,
+ Flags::displayTopology);
+
private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState(
Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING,
Flags::enableConnectedDisplayErrorHandling);
@@ -266,6 +270,10 @@
return mExternalDisplayLimitModeState.isEnabled();
}
+ public boolean isDisplayTopologyEnabled() {
+ return mDisplayTopology.isEnabled();
+ }
+
/**
* @return Whether displays refresh rate synchronization is enabled.
*/
@@ -441,6 +449,7 @@
pw.println(" " + mConnectedDisplayManagementFlagState);
pw.println(" " + mDisplayOffloadFlagState);
pw.println(" " + mExternalDisplayLimitModeState);
+ pw.println(" " + mDisplayTopology);
pw.println(" " + mHdrClamperFlagState);
pw.println(" " + mNbmControllerFlagState);
pw.println(" " + mPowerThrottlingClamperFlagState);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 9968ba5..e3ebe5b 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -92,6 +92,14 @@
}
flag {
+ name: "display_topology"
+ namespace: "display_manager"
+ description: "Display topology for moving cursors and windows between extended displays"
+ bug: "278199220"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_displays_refresh_rates_synchronization"
namespace: "display_manager"
description: "Enables synchronization of refresh rates across displays"
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 101596d..aae7b59 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -261,6 +261,7 @@
.setDisplayName(HdmiUtils.getDefaultDeviceName(source))
.setDeviceType(deviceTypes.get(0))
.setVendorId(Constants.VENDOR_ID_UNKNOWN)
+ .setPortId(mService.getHdmiCecNetwork().physicalAddressToPortId(physicalAddress))
.build();
mService.getHdmiCecNetwork().addCecDevice(newDevice);
}
@@ -1433,6 +1434,7 @@
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
assertRunOnServiceThread();
mService.unregisterTvInputCallback(mTvInputCallback);
+ mTvInputs.clear();
// Remove any repeated working actions.
// HotplugDetectionAction will be reinstated during the wake up process.
// HdmiControlService.onWakeUp() -> initializeLocalDevices() ->
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..a1e5ebc 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -24,7 +24,6 @@
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.hardware.input.InputManager;
import android.util.Slog;
import android.util.TypedValue;
import android.view.Gravity;
@@ -42,6 +41,7 @@
import com.android.server.input.TouchpadHardwareState;
import java.util.Objects;
+import java.util.function.Consumer;
public class TouchpadDebugView extends LinearLayout {
private static final float MAX_SCREEN_WIDTH_PROPORTION = 0.4f;
@@ -52,7 +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.
*/
@@ -74,22 +75,24 @@
private int mWindowLocationBeforeDragX;
private int mWindowLocationBeforeDragY;
private int mLatestGestureType = 0;
+ private TouchpadSelectionView mTouchpadSelectionView;
+ private TouchpadVisualizationView mTouchpadVisualizationView;
private TextView mGestureInfoView;
@NonNull
private TouchpadHardwareState mLastTouchpadState =
new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
new TouchpadFingerState[0]);
- private TouchpadVisualizationView mTouchpadVisualizationView;
private final TouchpadHardwareProperties mTouchpadHardwareProperties;
public TouchpadDebugView(Context context, int touchpadId,
- TouchpadHardwareProperties touchpadHardwareProperties) {
+ TouchpadHardwareProperties touchpadHardwareProperties,
+ Consumer<Integer> touchpadSwitchHandler) {
super(context);
mTouchpadId = touchpadId;
mWindowManager =
Objects.requireNonNull(getContext().getSystemService(WindowManager.class));
mTouchpadHardwareProperties = touchpadHardwareProperties;
- init(context, touchpadId);
+ init(context, touchpadId, touchpadSwitchHandler);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mWindowLayoutParams = new WindowManager.LayoutParams();
@@ -111,7 +114,8 @@
mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
}
- private void init(Context context, int touchpadId) {
+ private void init(Context context, int touchpadId,
+ Consumer<Integer> touchpadSwitchHandler) {
updateScreenDimensions();
setOrientation(VERTICAL);
setLayoutParams(new LayoutParams(
@@ -119,35 +123,31 @@
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(
- mContext.getSystemService(InputManager.class))
- .getInputDevice(touchpadId)).getName());
- nameView.setGravity(Gravity.CENTER);
- nameView.setTextColor(Color.WHITE);
+ mTouchpadSelectionView = new TouchpadSelectionView(context,
+ touchpadId, touchpadSwitchHandler);
+ mTouchpadSelectionView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
+ mTouchpadSelectionView.setGravity(Gravity.CENTER);
int paddingInDP = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, TEXT_PADDING_DP,
getResources().getDisplayMetrics());
- nameView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
- nameView.setLayoutParams(
+ mTouchpadSelectionView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
+ mTouchpadSelectionView.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));
+ //TODO(b/369061237): Handle longer text
- addView(nameView);
+ updateTheme(getResources().getConfiguration().uiMode);
+
+ addView(mTouchpadSelectionView);
addView(mTouchpadVisualizationView);
addView(mGestureInfoView);
@@ -239,6 +239,8 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+
+ updateTheme(newConfig.uiMode);
updateScreenDimensions();
updateViewsDimensions();
@@ -250,6 +252,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 +356,12 @@
private void onTouchpadButtonPress() {
Slog.d(TAG, "You clicked me!");
- getChildAt(0).setBackgroundColor(Color.BLUE);
+ mTouchpadSelectionView.setBackgroundColor(BUTTON_PRESSED_BACKGROUND_COLOR);
}
private void onTouchpadButtonRelease() {
Slog.d(TAG, "You released the click");
- getChildAt(0).setBackgroundColor(Color.RED);
+ mTouchpadSelectionView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
}
/**
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
index cb43977..9cfbfa64 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
@@ -16,6 +16,7 @@
package com.android.server.input.debug;
+import android.annotation.AnyThread;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.input.InputManager;
@@ -45,8 +46,8 @@
private boolean mTouchpadVisualizerEnabled = false;
public TouchpadDebugViewController(Context context, Looper looper,
- InputManagerService inputManagerService) {
- //TODO(b/363979581): Handle multi-display scenarios
+ InputManagerService inputManagerService) {
+ //TODO(b/369059937): Handle multi-display scenarios
mContext = context;
mHandler = new Handler(looper);
mInputManagerService = inputManagerService;
@@ -77,6 +78,14 @@
}
}
+ /**
+ * Switch to showing the touchpad with the given device ID
+ */
+ public void switchVisualisationToTouchpadId(int newDeviceId) {
+ if (mTouchpadDebugView != null) hideDebugView(mTouchpadDebugView.getTouchpadId());
+ showDebugView(newDeviceId);
+ }
+
@Override
public void onInputDeviceChanged(int deviceId) {
}
@@ -117,7 +126,7 @@
touchpadId);
mTouchpadDebugView = new TouchpadDebugView(mContext, touchpadId,
- touchpadHardwareProperties);
+ touchpadHardwareProperties, this::switchVisualisationToTouchpadId);
final WindowManager.LayoutParams mWindowLayoutParams =
mTouchpadDebugView.getWindowLayoutParams();
@@ -149,19 +158,28 @@
* @param touchpadHardwareState the hardware state of a touchpad
* @param deviceId the deviceId of the touchpad that is sending the hardware state
*/
+ @AnyThread
public void updateTouchpadHardwareState(TouchpadHardwareState touchpadHardwareState,
- int deviceId) {
- if (mTouchpadDebugView != null) {
- mTouchpadDebugView.updateHardwareState(touchpadHardwareState, deviceId);
- }
+ int deviceId) {
+ mHandler.post(() -> {
+ if (mTouchpadDebugView != null) {
+ mTouchpadDebugView.post(
+ () -> mTouchpadDebugView.updateHardwareState(touchpadHardwareState,
+ deviceId));
+ }
+ });
}
/**
* Notify the TouchpadDebugView of a new touchpad gesture.
*/
+ @AnyThread
public void updateTouchpadGestureInfo(int gestureType, int deviceId) {
- if (mTouchpadDebugView != null) {
- mTouchpadDebugView.updateGestureInfo(gestureType, deviceId);
- }
+ mHandler.post(() -> {
+ if (mTouchpadDebugView != null) {
+ mTouchpadDebugView.post(
+ () -> mTouchpadDebugView.updateGestureInfo(gestureType, deviceId));
+ }
+ });
}
}
diff --git a/services/core/java/com/android/server/input/debug/TouchpadSelectionView.java b/services/core/java/com/android/server/input/debug/TouchpadSelectionView.java
new file mode 100644
index 0000000..05217b6
--- /dev/null
+++ b/services/core/java/com/android/server/input/debug/TouchpadSelectionView.java
@@ -0,0 +1,111 @@
+/*
+ * 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.input.debug;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.hardware.input.InputManager;
+import android.view.Gravity;
+import android.view.InputDevice;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public class TouchpadSelectionView extends LinearLayout {
+ private static final float TEXT_SIZE_SP = 16.0f;
+
+ int mCurrentTouchpadId;
+
+ public TouchpadSelectionView(Context context, int touchpadId,
+ Consumer<Integer> touchpadSwitchHandler) {
+ super(context);
+ mCurrentTouchpadId = touchpadId;
+ init(context, touchpadSwitchHandler);
+ }
+
+ private void init(Context context, Consumer<Integer> touchpadSwitchHandler) {
+ setOrientation(HORIZONTAL);
+ setLayoutParams(new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+ setBackgroundColor(Color.TRANSPARENT);
+
+ TextView nameView = new TextView(context);
+ nameView.setTextSize(TEXT_SIZE_SP);
+ nameView.setText(getTouchpadName(mCurrentTouchpadId));
+ nameView.setGravity(Gravity.LEFT);
+ nameView.setTextColor(Color.WHITE);
+
+ LayoutParams textParams = new LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ textParams.rightMargin = 16;
+ nameView.setLayoutParams(textParams);
+
+ ImageButton arrowButton = new ImageButton(context);
+ arrowButton.setImageDrawable(context.getDrawable(android.R.drawable.arrow_down_float));
+ arrowButton.setForegroundGravity(Gravity.RIGHT);
+ arrowButton.setBackgroundColor(Color.TRANSPARENT);
+ arrowButton.setLayoutParams(new LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ arrowButton.setOnClickListener(v -> showPopupMenu(v, context, touchpadSwitchHandler));
+
+ addView(nameView);
+ addView(arrowButton);
+ }
+
+ private void showPopupMenu(View anchorView, Context context,
+ Consumer<Integer> touchpadSwitchHandler) {
+ int i = 0;
+ PopupMenu popupMenu = new PopupMenu(context, anchorView);
+
+ final InputManager inputManager = Objects.requireNonNull(
+ mContext.getSystemService(InputManager.class));
+ for (int deviceId : inputManager.getInputDeviceIds()) {
+ InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+ if (Objects.requireNonNull(inputDevice).supportsSource(
+ InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE)) {
+ popupMenu.getMenu().add(0, deviceId, i, getTouchpadName(deviceId));
+ i++;
+ }
+ }
+
+ popupMenu.setOnMenuItemClickListener(item -> {
+ if (item.getItemId() == mCurrentTouchpadId) {
+ return false;
+ }
+
+ touchpadSwitchHandler.accept(item.getItemId());
+ return true;
+ });
+
+ popupMenu.show();
+ }
+
+ private String getTouchpadName(int touchpadId) {
+ return Objects.requireNonNull(Objects.requireNonNull(
+ mContext.getSystemService(InputManager.class))
+ .getInputDevice(touchpadId)).getName();
+ }
+}
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..eeec5cc 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;
@@ -29,6 +30,7 @@
import java.util.ArrayDeque;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
public class TouchpadVisualizationView extends View {
@@ -49,6 +51,7 @@
private final Paint mOvalFillPaint;
private final Paint mTracePaint;
private final Paint mCenterPointPaint;
+ private final Paint mPressureTextPaint;
private final RectF mTempOvalRect = new RectF();
public TouchpadVisualizationView(Context context,
@@ -58,11 +61,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);
@@ -72,6 +73,8 @@
mCenterPointPaint.setAntiAlias(true);
mCenterPointPaint.setARGB(255, 255, 0, 0);
mCenterPointPaint.setStrokeWidth(2);
+ mPressureTextPaint = new Paint();
+ mPressureTextPaint.setAntiAlias(true);
}
private void removeOldPoints() {
@@ -135,6 +138,13 @@
mOvalFillPaint.setAlpha((int) pressureToOpacity);
drawOval(canvas, newX, newY, newTouchMajor, newTouchMinor, newAngle);
+
+ String formattedPressure = String.format(Locale.getDefault(), "Ps: %.2f",
+ touchpadFingerState.getPressure());
+ float textWidth = mPressureTextPaint.measureText(formattedPressure);
+
+ canvas.drawText(formattedPressure, newX - textWidth / 2,
+ newY - newTouchMajor / 2, mPressureTextPaint);
}
mTempFingerStatesByTrackingId.clear();
@@ -195,6 +205,26 @@
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));
+ mPressureTextPaint.setARGB(255, 255, 255, 255);
+ 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));
+ mPressureTextPaint.setARGB(255, 0, 0, 0);
+ 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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ba7d4d2..b15fcc9 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -33,6 +33,7 @@
import static android.app.Notification.EXTRA_SUB_TEXT;
import static android.app.Notification.EXTRA_TEXT;
import static android.app.Notification.EXTRA_TEXT_LINES;
+import static android.app.Notification.EXTRA_TITLE;
import static android.app.Notification.EXTRA_TITLE_BIG;
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_AUTO_CANCEL;
@@ -45,6 +46,7 @@
import static android.app.Notification.FLAG_NO_DISMISS;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.Notification.FLAG_PROMOTED_ONGOING;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
import static android.app.NotificationChannel.NEWS_ID;
@@ -3516,7 +3518,7 @@
private String getHistoryTitle(Notification n) {
CharSequence title = null;
if (n.extras != null) {
- title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
+ title = n.extras.getCharSequence(EXTRA_TITLE);
if (title == null) {
title = n.extras.getCharSequence(EXTRA_TITLE_BIG);
}
@@ -4114,6 +4116,75 @@
}
@Override
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public boolean canBePromoted(String pkg, int uid) {
+ checkCallerIsSystemOrSystemUiOrShell();
+ if (!android.app.Flags.uiRichOngoing()) {
+ return false;
+ }
+ return mPreferencesHelper.canBePromoted(pkg, uid);
+ }
+
+ @Override
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void setCanBePromoted(String pkg, int uid, boolean promote) {
+ checkCallerIsSystemOrSystemUiOrShell();
+ if (!android.app.Flags.uiRichOngoing()) {
+ return;
+ }
+ boolean changed = mPreferencesHelper.setCanBePromoted(pkg, uid, promote);
+ if (changed) {
+ // check for pending/posted notifs from this app and update the flag
+ synchronized (mNotificationLock) {
+ // for enqueued we just need to update the flag
+ List<NotificationRecord> enqueued = findAppNotificationByListLocked(
+ mEnqueuedNotifications, pkg, UserHandle.getUserId(uid));
+ for (NotificationRecord r : enqueued) {
+ if (promote
+ && r.getNotification().hasPromotableCharacteristics()
+ && r.getImportance() > IMPORTANCE_MIN) {
+ r.getNotification().flags |= FLAG_PROMOTED_ONGOING;
+ } else if (!promote) {
+ r.getNotification().flags &= ~FLAG_PROMOTED_ONGOING;
+ }
+ }
+ // if the notification is posted we need to update the flag and tell listeners
+ List<NotificationRecord> posted = findAppNotificationByListLocked(
+ mNotificationList, pkg, UserHandle.getUserId(uid));
+ for (NotificationRecord r : posted) {
+ if (promote
+ && !hasFlag(r.getNotification().flags, FLAG_PROMOTED_ONGOING)
+ && r.getNotification().hasPromotableCharacteristics()
+ && r.getImportance() > IMPORTANCE_MIN) {
+ r.getNotification().flags |= FLAG_PROMOTED_ONGOING;
+ // we could set a wake lock here but this value should only change
+ // in response to user action, so the device should be awake long enough
+ // to post
+ PostNotificationTracker tracker =
+ mPostNotificationTrackerFactory.newTracker(null);
+ // Set false for isAppForeground because that field is only used
+ // for bubbles and messagingstyle can not be promoted
+ mHandler.post(new EnqueueNotificationRunnable(
+ r.getUser().getIdentifier(),
+ r, /* isAppForeground */ false, /* isAppProvided= */ false,
+ tracker));
+ } else if (!promote
+ && hasFlag(r.getNotification().flags, FLAG_PROMOTED_ONGOING)){
+ r.getNotification().flags &= ~FLAG_PROMOTED_ONGOING;
+ PostNotificationTracker tracker =
+ mPostNotificationTrackerFactory.newTracker(null);
+ mHandler.post(new EnqueueNotificationRunnable(
+ r.getUser().getIdentifier(),
+ r, /* isAppForeground */ false, /* isAppProvided= */ false,
+ tracker));
+ }
+ }
+ }
+ handleSavePolicyFile();
+ }
+ }
+
+ @Override
public boolean hasSentValidMsg(String pkg, int uid) {
checkCallerIsSystem();
return mPreferencesHelper.hasSentValidMsg(pkg, uid);
@@ -7698,6 +7769,16 @@
return false;
}
+ if (android.app.Flags.uiRichOngoing()) {
+ // This would normally be done in fixNotification(), but we need the channel info so
+ // it's done a little late
+ if (mPreferencesHelper.canBePromoted(pkg, notificationUid)
+ && notification.hasPromotableCharacteristics()
+ && channel.getImportance() > IMPORTANCE_MIN) {
+ notification.flags |= FLAG_PROMOTED_ONGOING;
+ }
+ }
+
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
r.setIsAppImportanceLocked(mPermissionHelper.isPermissionUserSet(pkg, userId));
r.setPostSilently(postSilently);
@@ -7938,6 +8019,9 @@
}
}
+ // Apps cannot set this flag
+ notification.flags &= ~FLAG_PROMOTED_ONGOING;
+
// Ensure CallStyle has all the correct actions
if (notification.isStyle(Notification.CallStyle.class)) {
Notification.Builder builder =
@@ -8061,12 +8145,7 @@
private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
if (android.app.Flags.removeRemoteViews()) {
- if (notification.contentView != null || notification.bigContentView != null
- || notification.headsUpContentView != null
- || (notification.publicVersion != null
- && (notification.publicVersion.contentView != null
- || notification.publicVersion.bigContentView != null
- || notification.publicVersion.headsUpContentView != null))) {
+ if (notification.containsCustomViews()) {
Slog.i(TAG, "Removed customViews for " + pkg);
mUsageStats.registerImageRemoved(pkg);
}
@@ -9236,8 +9315,8 @@
}
}
- final String oldTitle = String.valueOf(oldN.extras.get(Notification.EXTRA_TITLE));
- final String newTitle = String.valueOf(newN.extras.get(Notification.EXTRA_TITLE));
+ final String oldTitle = String.valueOf(oldN.extras.get(EXTRA_TITLE));
+ final String newTitle = String.valueOf(newN.extras.get(EXTRA_TITLE));
if (!Objects.equals(oldTitle, newTitle)) {
if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -10654,6 +10733,22 @@
}
@GuardedBy("mNotificationLock")
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ private @NonNull List<NotificationRecord> findAppNotificationByListLocked(
+ ArrayList<NotificationRecord> list, String pkg, int userId) {
+ List<NotificationRecord> records = new ArrayList<>();
+ final int len = list.size();
+ for (int i = 0; i < len; i++) {
+ NotificationRecord r = list.get(i);
+ if (notificationMatchesUserId(r, userId, false)
+ && r.getSbn().getPackageName().equals(pkg)) {
+ records.add(r);
+ }
+ }
+ return records;
+ }
+
+ @GuardedBy("mNotificationLock")
private @NonNull List<NotificationRecord> findGroupNotificationByListLocked(
ArrayList<NotificationRecord> list, String pkg, String groupKey, int userId) {
List<NotificationRecord> records = new ArrayList<>();
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index a4fdb75..fcc8d2f 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -41,6 +41,7 @@
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -162,6 +163,7 @@
private static final String ATT_SENT_VALID_MESSAGE = "sent_valid_msg";
private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app";
private static final String ATT_SENT_VALID_BUBBLE = "sent_valid_bubble";
+ private static final String ATT_PROMOTE_NOTIFS = "promote";
private static final String ATT_CREATION_TIME = "creation_time";
@@ -351,6 +353,10 @@
r.userDemotedMsgApp = parser.getAttributeBoolean(
null, ATT_USER_DEMOTED_INVALID_MSG_APP, false);
r.hasSentValidBubble = parser.getAttributeBoolean(null, ATT_SENT_VALID_BUBBLE, false);
+ if (android.app.Flags.uiRichOngoing()) {
+ r.canHavePromotedNotifs =
+ parser.getAttributeBoolean(null, ATT_PROMOTE_NOTIFS, false);
+ }
final int innerDepth = parser.getDepth();
int type;
@@ -739,6 +745,11 @@
out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
r.userDemotedMsgApp);
out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
+ if (android.app.Flags.uiRichOngoing()) {
+ if (r.canHavePromotedNotifs) {
+ out.attributeBoolean(null, ATT_PROMOTE_NOTIFS, r.canHavePromotedNotifs);
+ }
+ }
if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
@@ -839,6 +850,28 @@
}
}
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public boolean canBePromoted(String packageName, int uid) {
+ synchronized (mLock) {
+ return getOrCreatePackagePreferencesLocked(packageName, uid).canHavePromotedNotifs;
+ }
+ }
+
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public boolean setCanBePromoted(String packageName, int uid, boolean promote) {
+ boolean changed = false;
+ synchronized (mLock) {
+ PackagePreferences pkgPrefs = getOrCreatePackagePreferencesLocked(packageName, uid);
+ if (pkgPrefs.canHavePromotedNotifs != promote) {
+ pkgPrefs.canHavePromotedNotifs = promote;
+ changed = true;
+ }
+ }
+ // no need to send a ranking update because we need to update the flag value on all pending
+ // and posted notifs and NMS will take care of that
+ return changed;
+ }
+
public boolean isInInvalidMsgState(String packageName, int uid) {
synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
@@ -2180,6 +2213,10 @@
pw.print(" fixedImportance=");
pw.print(r.fixedImportance);
}
+ if (android.app.Flags.uiRichOngoing() && r.canHavePromotedNotifs) {
+ pw.print(" promoted=");
+ pw.print(r.canHavePromotedNotifs);
+ }
pw.println();
for (NotificationChannel channel : r.channels.values()) {
pw.print(prefix);
@@ -3028,6 +3065,9 @@
boolean migrateToPm = false;
long creationTime;
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ boolean canHavePromotedNotifs = false;
+
@UserIdInt int userId;
Delegate delegate = null;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index e9db1b5..626c3dd 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.
@@ -1721,7 +1722,7 @@
// booleans to determine whether to reset the rules to the default rules
boolean allRulesDisabled = true;
boolean hasDefaultRules = config.automaticRules.containsAll(
- ZenModeConfig.DEFAULT_RULE_IDS);
+ ZenModeConfig.getDefaultRuleIds());
long time = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis();
if (config.automaticRules != null && config.automaticRules.size() > 0) {
@@ -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";
}
@@ -1798,6 +1799,14 @@
config.deletedRules.clear();
}
+ if (Flags.modesUi() && config.automaticRules != null) {
+ ZenRule obsoleteEventsRule = config.automaticRules.get(
+ ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+ if (obsoleteEventsRule != null && !obsoleteEventsRule.enabled) {
+ config.automaticRules.remove(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+ }
+ }
+
if (DEBUG) Log.d(TAG, reason);
synchronized (mConfigLock) {
setConfigLocked(config, null,
@@ -2256,7 +2265,7 @@
private static void updateRuleStringsForCurrentLocale(Context context,
ZenModeConfig defaultConfig) {
for (ZenRule rule : defaultConfig.automaticRules.values()) {
- if (ZenModeConfig.EVENTS_DEFAULT_RULE_ID.equals(rule.id)) {
+ if (ZenModeConfig.EVENTS_OBSOLETE_RULE_ID.equals(rule.id)) {
rule.name = context.getResources()
.getString(R.string.zen_mode_default_events_name);
} else if (ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID.equals(rule.id)) {
@@ -2278,7 +2287,7 @@
}
ZenPolicy defaultPolicy = defaultConfig.getZenPolicy();
for (ZenRule rule : defaultConfig.automaticRules.values()) {
- if (ZenModeConfig.DEFAULT_RULE_IDS.contains(rule.id) && rule.zenPolicy == null) {
+ if (ZenModeConfig.getDefaultRuleIds().contains(rule.id) && rule.zenPolicy == null) {
rule.zenPolicy = defaultPolicy.copy();
}
}
@@ -2482,7 +2491,7 @@
List<StatsEvent> events) {
// Make the ID safe.
String id = rule.id == null ? "" : rule.id;
- if (!ZenModeConfig.DEFAULT_RULE_IDS.contains(id)) {
+ if (!ZenModeConfig.getDefaultRuleIds().contains(id)) {
id = "";
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 60056eb..6f50295 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -912,12 +912,13 @@
* available ShareTarget definitions in this package.
*/
public List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
- @NonNull final IntentFilter filter) {
- return getMatchingShareTargets(filter, null);
+ @NonNull final IntentFilter filter, final int callingUserId) {
+ return getMatchingShareTargets(filter, null, callingUserId);
}
List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets(
- @NonNull final IntentFilter filter, @Nullable final String pkgName) {
+ @NonNull final IntentFilter filter, @Nullable final String pkgName,
+ final int callingUserId) {
synchronized (mPackageItemLock) {
final List<ShareTargetInfo> matchedTargets = new ArrayList<>();
for (int i = 0; i < mShareTargets.size(); i++) {
@@ -941,7 +942,7 @@
// included in the result
findAll(shortcuts, ShortcutInfo::isNonManifestVisible,
ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION,
- pkgName, 0, /*getPinnedByAnyLauncher=*/ false);
+ pkgName, callingUserId, /*getPinnedByAnyLauncher=*/ false);
final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>();
for (int i = 0; i < shortcuts.size(); i++) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index a3ff195..5518bfa 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2591,7 +2591,8 @@
final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>();
final ShortcutUser user = getUserShortcutsLocked(userId);
user.forAllPackages(p -> shortcutInfoList.addAll(
- p.getMatchingShareTargets(filter, pkg)));
+ p.getMatchingShareTargets(filter, pkg,
+ mUserManagerInternal.getProfileParentId(userId))));
return new ParceledListSlice<>(shortcutInfoList);
}
}
@@ -2623,7 +2624,8 @@
final List<ShortcutManager.ShareShortcutInfo> matchedTargets =
getPackageShortcutsLocked(packageName, userId)
- .getMatchingShareTargets(filter);
+ .getMatchingShareTargets(filter,
+ mUserManagerInternal.getProfileParentId(callingUserId));
final int matchedSize = matchedTargets.size();
for (int i = 0; i < matchedSize; i++) {
if (matchedTargets.get(i).getShortcutInfo().getId().equals(shortcutId)) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 89417f3..8bab9de 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1101,8 +1101,6 @@
if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
UserManager.invalidateIsUserUnlockedCache();
UserManager.invalidateQuietModeEnabledCache();
- UserManager.invalidateStaticUserProperties();
- UserManager.invalidateUserPropertiesCache();
UserManager.invalidateUserSerialNumberCache();
}
}
@@ -2647,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/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/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index e3d71e4..f78c448 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -81,7 +81,7 @@
public final class RollbackPackageHealthObserver implements PackageHealthObserver {
private static final String TAG = "RollbackPackageHealthObserver";
private static final String NAME = "rollback-observer";
- private static final String ACTION_NAME = RollbackPackageHealthObserver.class.getName();
+ private static final String CLASS_NAME = RollbackPackageHealthObserver.class.getName();
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
@@ -610,14 +610,16 @@
}
};
+ String intentActionName = CLASS_NAME + rollback.getRollbackId();
// Register the BroadcastReceiver
mContext.registerReceiver(rollbackReceiver,
- new IntentFilter(ACTION_NAME),
+ new IntentFilter(intentActionName),
Context.RECEIVER_NOT_EXPORTED);
- Intent intentReceiver = new Intent(ACTION_NAME);
+ Intent intentReceiver = new Intent(intentActionName);
intentReceiver.putExtra("rollbackId", rollback.getRollbackId());
intentReceiver.setPackage(mContext.getPackageName());
+ intentReceiver.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext,
rollback.getRollbackId(),
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 153bb91..1ba2487 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -130,22 +130,35 @@
}
/**
- * Return true if freezing is enabled. This has no effect if the service is not enabled.
+ * Return true if freezing is feature-enabled. Freezing must still be enabled on a
+ * per-service basis.
*/
- private static boolean anrTimerFreezerEnabled() {
+ private static boolean freezerFeatureEnabled() {
return Flags.anrTimerFreezer();
}
/**
+ * Return true if tracing is feature-enabled. This has no effect unless tracing is configured.
+ * Note that this does not represent any per-process overrides via an Injector.
+ */
+ public static boolean traceFeatureEnabled() {
+ return anrTimerServiceEnabled() && Flags.anrTimerTrace();
+ }
+
+ /**
* This class allows test code to provide instance-specific overrides.
*/
static class Injector {
- boolean anrTimerServiceEnabled() {
+ boolean serviceEnabled() {
return AnrTimer.anrTimerServiceEnabled();
}
- boolean anrTimerFreezerEnabled() {
- return AnrTimer.anrTimerFreezerEnabled();
+ boolean freezerEnabled() {
+ return AnrTimer.freezerFeatureEnabled();
+ }
+
+ boolean traceEnabled() {
+ return AnrTimer.traceFeatureEnabled();
}
}
@@ -349,7 +362,7 @@
mWhat = what;
mLabel = label;
mArgs = args;
- boolean enabled = args.mInjector.anrTimerServiceEnabled() && nativeTimersSupported();
+ boolean enabled = args.mInjector.serviceEnabled() && nativeTimersSupported();
mFeature = createFeatureSwitch(enabled);
}
@@ -448,7 +461,7 @@
/**
* The FeatureDisabled class bypasses almost all AnrTimer logic. It is used when the AnrTimer
- * service is disabled via Flags.anrTimerServiceEnabled.
+ * service is disabled via Flags.anrTimerService().
*/
private class FeatureDisabled extends FeatureSwitch {
/** Start a timer by sending a message to the client's handler. */
@@ -515,7 +528,7 @@
/**
* The FeatureEnabled class enables the AnrTimer logic. It is used when the AnrTimer service
- * is enabled via Flags.anrTimerServiceEnabled.
+ * is enabled via Flags.anrTimerService().
*/
private class FeatureEnabled extends FeatureSwitch {
@@ -533,7 +546,7 @@
FeatureEnabled() {
mNative = nativeAnrTimerCreate(mLabel,
mArgs.mExtend,
- mArgs.mFreeze && mArgs.mInjector.anrTimerFreezerEnabled());
+ mArgs.mFreeze && mArgs.mInjector.freezerEnabled());
if (mNative == 0) throw new IllegalArgumentException("unable to create native timer");
synchronized (sAnrTimerList) {
sAnrTimerList.put(mNative, new WeakReference(AnrTimer.this));
@@ -550,7 +563,7 @@
// exist.
if (cancel(arg)) mTotalRestarted++;
- int timerId = nativeAnrTimerStart(mNative, pid, uid, timeoutMs);
+ final int timerId = nativeAnrTimerStart(mNative, pid, uid, timeoutMs);
if (timerId > 0) {
mTimerIdMap.put(arg, timerId);
mTimerArgMap.put(timerId, arg);
@@ -895,7 +908,7 @@
/** Dumpsys output, allowing for overrides. */
@VisibleForTesting
static void dump(@NonNull PrintWriter pw, boolean verbose, @NonNull Injector injector) {
- if (!injector.anrTimerServiceEnabled()) return;
+ if (!injector.serviceEnabled()) return;
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
ipw.println("AnrTimer statistics");
@@ -926,6 +939,18 @@
}
/**
+ * Set a trace specification. The input is a set of strings. On success, the function pushes
+ * the trace specification to all timers, and then returns a response message. On failure,
+ * the function throws IllegalArgumentException and tracing is disabled.
+ *
+ * An empty specification has no effect other than returning the current trace specification.
+ */
+ @Nullable
+ public static String traceTimers(@Nullable String[] spec) {
+ return nativeAnrTimerTrace(spec);
+ }
+
+ /**
* Return true if the native timers are supported. Native timers are supported if the method
* nativeAnrTimerSupported() can be executed and it returns true.
*/
@@ -981,6 +1006,15 @@
*/
private static native boolean nativeAnrTimerRelease(long service, int timerId);
+ /**
+ * Configure tracing. The input array is a set of words pulled from the command line. All
+ * parsing happens inside the native layer. The function returns a string which is either an
+ * error message (so nothing happened) or the current configuration after applying the config.
+ * Passing an null array or an empty array simply returns the current configuration.
+ * The function returns null if the native layer is not implemented.
+ */
+ private static native @Nullable String nativeAnrTimerTrace(@Nullable String[] config);
+
/** Retrieve runtime dump information from the native layer. */
private static native String[] nativeAnrTimerDump(long service);
}
diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig
index 00ebb66..333287f 100644
--- a/services/core/java/com/android/server/utils/flags.aconfig
+++ b/services/core/java/com/android/server/utils/flags.aconfig
@@ -17,3 +17,10 @@
bug: "325594551"
}
+flag {
+ name: "anr_timer_trace"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "When true, start a trace if an ANR timer reaches 50%"
+ bug: "352085328"
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ccc9b17..12d733f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2641,9 +2641,15 @@
return true;
}
// Only do transfer after transaction has done when starting window exist.
- if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) {
- mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
- return true;
+ if (mStartingData != null) {
+ final boolean isWaitingForSyncTransactionCommit =
+ Flags.removeStartingWindowWaitForMultiTransitions()
+ ? getSyncTransactionCommitCallbackDepth() > 0
+ : mStartingData.mWaitForSyncTransactionCommit;
+ if (isWaitingForSyncTransactionCommit) {
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
+ return true;
+ }
}
requestCopySplashScreen();
return isTransferringSplashScreen();
@@ -2847,7 +2853,11 @@
final boolean animate;
final boolean hasImeSurface;
if (mStartingData != null) {
- if (mStartingData.mWaitForSyncTransactionCommit
+ final boolean isWaitingForSyncTransactionCommit =
+ Flags.removeStartingWindowWaitForMultiTransitions()
+ ? getSyncTransactionCommitCallbackDepth() > 0
+ : mStartingData.mWaitForSyncTransactionCommit;
+ if (isWaitingForSyncTransactionCommit
|| mSyncState != SYNC_STATE_NONE) {
mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
mStartingData.mPrepareRemoveAnimation = prepareAnimation;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index f0a4763..57b8792 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -75,7 +75,8 @@
private final Rect mTmpRect = new Rect();
private final InsetsSourceControl mFakeControl;
- private final Consumer<Transaction> mSetLeashPositionConsumer;
+ private final Point mPosition = new Point();
+ private final Consumer<Transaction> mSetControlPositionConsumer;
private @Nullable InsetsControlTarget mPendingControlTarget;
private @Nullable InsetsControlTarget mFakeControlTarget;
@@ -126,13 +127,14 @@
source.getId(), source.getType(), null /* leash */, false /* initialVisible */,
new Point(), Insets.NONE);
mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0;
- mSetLeashPositionConsumer = t -> {
- if (mControl != null) {
- final SurfaceControl leash = mControl.getLeash();
- if (leash != null) {
- final Point position = mControl.getSurfacePosition();
- t.setPosition(leash, position.x, position.y);
- }
+ mSetControlPositionConsumer = t -> {
+ if (mControl == null || mControlTarget == null) {
+ return;
+ }
+ boolean changed = mControl.setSurfacePosition(mPosition.x, mPosition.y);
+ final SurfaceControl leash = mControl.getLeash();
+ if (changed && leash != null) {
+ t.setPosition(leash, mPosition.x, mPosition.y);
}
if (mHasPendingPosition) {
mHasPendingPosition = false;
@@ -140,9 +142,22 @@
mStateController.notifyControlTargetChanged(mPendingControlTarget, this);
}
}
+ changed |= updateInsetsHint();
+ if (changed) {
+ mStateController.notifyControlChanged(mControlTarget, this);
+ }
};
}
+ private boolean updateInsetsHint() {
+ final Insets insetsHint = getInsetsHint();
+ if (!mControl.getInsetsHint().equals(insetsHint)) {
+ mControl.setInsetsHint(insetsHint);
+ return true;
+ }
+ return false;
+ }
+
InsetsSource getSource() {
return mSource;
}
@@ -363,26 +378,32 @@
}
final boolean serverVisibleChanged = mServerVisible != isServerVisible;
setServerVisible(isServerVisible);
- updateInsetsControlPosition(windowState, serverVisibleChanged);
- }
-
- void updateInsetsControlPosition(WindowState windowState) {
- updateInsetsControlPosition(windowState, false);
- }
-
- private void updateInsetsControlPosition(WindowState windowState,
- boolean serverVisibleChanged) {
- if (mControl == null) {
- return;
+ final boolean positionChanged = updateInsetsControlPosition(windowState);
+ if (mControl != null && !positionChanged
+ // The insets hint would be updated if the position is changed. Here updates it for
+ // the possible change of the bounds or the server visibility.
+ && (updateInsetsHint()
+ || serverVisibleChanged
+ && android.view.inputmethod.Flags.refactorInsetsController())) {
+ // Only call notifyControlChanged here when the position is not changed. Otherwise, it
+ // is called or is scheduled to be called during updateInsetsControlPosition.
+ mStateController.notifyControlChanged(mControlTarget, this);
}
- boolean changed = false;
+ }
+
+ /**
+ * @return {#code true} if the surface position of the control is changed.
+ */
+ boolean updateInsetsControlPosition(WindowState windowState) {
+ if (mControl == null) {
+ return false;
+ }
final Point position = getWindowFrameSurfacePosition();
- if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) {
- changed = true;
+ if (!mPosition.equals(position)) {
+ mPosition.set(position.x, position.y);
if (windowState != null && windowState.getWindowFrames().didFrameSizeChange()
&& windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) {
- mHasPendingPosition = true;
- windowState.applyWithNextDraw(mSetLeashPositionConsumer);
+ windowState.applyWithNextDraw(mSetControlPositionConsumer);
} else {
Transaction t = mWindowContainer.getSyncTransaction();
if (windowState != null) {
@@ -399,20 +420,11 @@
}
}
}
- mSetLeashPositionConsumer.accept(t);
+ mSetControlPositionConsumer.accept(t);
}
+ return true;
}
- final Insets insetsHint = getInsetsHint();
- if (!mControl.getInsetsHint().equals(insetsHint)) {
- mControl.setInsetsHint(insetsHint);
- changed = true;
- }
- if (android.view.inputmethod.Flags.refactorInsetsController() && serverVisibleChanged) {
- changed = true;
- }
- if (changed) {
- mStateController.notifyControlChanged(mControlTarget, this);
- }
+ return false;
}
private Point getWindowFrameSurfacePosition() {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 5550f3e..d295378 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -699,8 +699,10 @@
final WindowState win = mService.windowForClientLocked(this, window,
false /* throwOnError */);
if (win != null) {
- ImeTracker.forLogging().onProgress(imeStatsToken,
- ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ ImeTracker.forLogging().onProgress(imeStatsToken,
+ ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
+ }
win.setRequestedVisibleTypes(requestedVisibleTypes);
win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win,
imeStatsToken);
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 24fb207..896612d 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -68,7 +68,9 @@
* window.
* Note this isn't equal to transition playing, the period should be
* Sync finishNow -> Start transaction apply.
+ * @deprecated TODO(b/362347290): cleanup after fix ramp up
*/
+ @Deprecated
boolean mWaitForSyncTransactionCommit;
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 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/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 92953e5..83e714d 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -429,7 +429,7 @@
}
final IBinder activityToken;
- if (activity.getPid() == mOrganizerPid) {
+ if (activity.getPid() == mOrganizerPid && activity.getUid() == mOrganizerUid) {
// We only pass the actual token if the activity belongs to the organizer process.
activityToken = activity.token;
} else {
@@ -458,7 +458,8 @@
change.setTaskFragmentToken(lastParentTfToken);
}
// Only pass the activity token to the client if it belongs to the same process.
- if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid) {
+ if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid
+ && nextFillTaskActivity.getUid() == mOrganizerUid) {
change.setOtherActivityToken(nextFillTaskActivity.token);
}
return change;
@@ -553,6 +554,10 @@
"Replacing existing organizer currently unsupported");
}
+ if (pid <= 0) {
+ throw new IllegalStateException("Cannot register from invalid pid: " + pid);
+ }
+
if (restoreFromCachedStateIfPossible(organizer, pid, uid, outSavedState)) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0a9cb1c..1c03ba5 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4351,4 +4351,7 @@
t.merge(mSyncTransaction);
}
+ int getSyncTransactionCommitCallbackDepth() {
+ return mSyncTransactionCommitCallbackDepth;
+ }
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 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/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index cf96114..2836d46 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -19,6 +19,8 @@
#include <sys/timerfd.h>
#include <inttypes.h>
#include <sys/stat.h>
+#include <unistd.h>
+#include <regex.h>
#include <algorithm>
#include <list>
@@ -26,6 +28,7 @@
#include <set>
#include <string>
#include <vector>
+#include <map>
#define LOG_TAG "AnrTimerService"
#define ATRACE_TAG ATRACE_TAG_ACTIVITY_MANAGER
@@ -33,8 +36,8 @@
#include <jni.h>
#include <nativehelper/JNIHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-#include "core_jni_helpers.h"
+#include <android_runtime/AndroidRuntime.h>
+#include <core_jni_helpers.h>
#include <processgroup/processgroup.h>
#include <utils/Log.h>
@@ -109,32 +112,336 @@
// Return the name of the process whose pid is the input. If the process does not exist, the
// name will "notfound".
std::string getProcessName(pid_t pid) {
- char buffer[PATH_MAX];
- snprintf(buffer, sizeof(buffer), "/proc/%d/cmdline", pid);
- int fd = ::open(buffer, O_RDONLY);
- if (fd >= 0) {
- size_t pos = 0;
- ssize_t result;
- while (pos < sizeof(buffer)-1) {
- result = ::read(fd, buffer + pos, (sizeof(buffer) - pos) - 1);
- if (result <= 0) {
- break;
- }
- }
- ::close(fd);
-
- if (result >= 0) {
- buffer[pos] = 0;
+ char path[PATH_MAX];
+ snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
+ FILE* cmdline = fopen(path, "r");
+ if (cmdline != nullptr) {
+ char name[PATH_MAX];
+ char const *retval = fgets(name, sizeof(name), cmdline);
+ fclose(cmdline);
+ if (retval == nullptr) {
+ return std::string("unknown");
} else {
- snprintf(buffer, sizeof(buffer), "err: %s", strerror(errno));
+ return std::string(name);
}
} else {
- snprintf(buffer, sizeof(buffer), "notfound");
+ return std::string("notfound");
}
- return std::string(buffer);
}
/**
+ * Three wrappers of the trace utilities, which hard-code the timer track.
+ */
+void traceBegin(const char* msg, int cookie) {
+ ATRACE_ASYNC_FOR_TRACK_BEGIN(ANR_TIMER_TRACK, msg, cookie);
+}
+
+void traceEnd(int cookie) {
+ ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie);
+}
+
+void traceEvent(const char* msg) {
+ ATRACE_INSTANT_FOR_TRACK(ANR_TIMER_TRACK, msg);
+}
+
+/**
+ * This class captures tracing information for processes tracked by an AnrTimer. A user can
+ * configure tracing to have the AnrTimerService emit extra information for watched processes.
+ * singleton.
+ *
+ * The tracing configuration has two components: process selection and an optional early action.
+ *
+ * Processes are selected in one of three ways:
+ * 1. A list of numeric linux process IDs.
+ * 2. A regular expression, matched against process names.
+ * 3. The keyword "all", to trace every process that uses an AnrTimer.
+ * Perfetto trace events are always emitted for every operation on a traced process.
+ *
+ * An early action occurs before the scheduled timeout. The early timeout is specified as a
+ * percentage (integer value in the range 0:100) of the programmed timeout. The AnrTimer will
+ * execute the early action at the early timeout. The early action may terminate the timer.
+ *
+ * There is one early action:
+ * 1. Expire - consider the AnrTimer expired and report it to the upper layers.
+ */
+class AnrTimerTracer {
+ public:
+ // Actions that can be taken when an early timer expires.
+ enum EarlyAction {
+ // Take no action. This is the value used when tracing is disabled.
+ None,
+ // Trace the timer but take no other action.
+ Trace,
+ // Report timer expiration to the upper layers. This is terminal, in that
+ Expire,
+ };
+
+ // The trace information for a single timer.
+ struct TraceConfig {
+ bool enabled = false;
+ EarlyAction action = None;
+ int earlyTimeout = 0;
+ };
+
+ AnrTimerTracer() {
+ AutoMutex _l(lock_);
+ resetLocked();
+ }
+
+ // Return the TraceConfig for a process.
+ TraceConfig getConfig(int pid) {
+ AutoMutex _l(lock_);
+ // The most likely situation: no tracing is configured.
+ if (!config_.enabled) return {};
+ if (matchAllPids_) return config_;
+ if (watched_.contains(pid)) return config_;
+ if (!matchNames_) return {};
+ if (matchedPids_.contains(pid)) return config_;
+ if (unmatchedPids_.contains(pid)) return {};
+ std::string proc_name = getProcessName(pid);
+ bool matched = regexec(®ex_, proc_name.c_str(), 0, 0, 0) == 0;
+ if (matched) {
+ matchedPids_.insert(pid);
+ return config_;
+ } else {
+ unmatchedPids_.insert(pid);
+ return {};
+ }
+ }
+
+ // Set the trace configuration. The input is a string that contains key/value pairs of the
+ // form "key=value". Pairs are separated by spaces. The function returns a string status.
+ // On success, the normalized config is returned. On failure, the configuration reset the
+ // result contains an error message. As a special case, an empty set of configs, or a
+ // config that contains only the keyword "show", will do nothing except return the current
+ // configuration. On any error, all tracing is disabled.
+ std::pair<bool, std::string> setConfig(const std::vector<std::string>& config) {
+ AutoMutex _l(lock_);
+ if (config.size() == 0) {
+ // Implicit "show"
+ return { true, currentConfigLocked() };
+ } else if (config.size() == 1) {
+ // Process the one-word commands
+ const char* s = config[0].c_str();
+ if (strcmp(s, "show") == 0) {
+ return { true, currentConfigLocked() };
+ } else if (strcmp(s, "off") == 0) {
+ resetLocked();
+ return { true, currentConfigLocked() };
+ } else if (strcmp(s, "help") == 0) {
+ return { true, help() };
+ }
+ } else if (config.size() > 2) {
+ return { false, "unexpected values in config" };
+ }
+
+ // Barring an error in the remaining specification list, tracing will be enabled.
+ resetLocked();
+ // Fetch the process specification. This must be the first configuration entry.
+ {
+ auto result = setTracedProcess(config[0]);
+ if (!result.first) return result;
+ }
+
+ // Process optional actions.
+ if (config.size() > 1) {
+ auto result = setTracedAction(config[1]);
+ if (!result.first) return result;
+ }
+
+ // Accept the result.
+ config_.enabled = true;
+ return { true, currentConfigLocked() };
+ }
+
+ private:
+ // Identify the processes to be traced.
+ std::pair<bool, std::string> setTracedProcess(std::string config) {
+ const char* s = config.c_str();
+ const char* word = nullptr;
+
+ if (strcmp(s, "pid=all") == 0) {
+ matchAllPids_ = true;
+ } else if ((word = startsWith(s, "pid=")) != nullptr) {
+ int p;
+ int n;
+ while (sscanf(word, "%d%n", &p, &n) == 1) {
+ watched_.insert(p);
+ word += n;
+ if (*word == ',') word++;
+ }
+ if (*word != 0) {
+ return { false, "invalid pid list" };
+ }
+ config_.action = Trace;
+ } else if ((word = startsWith(s, "name=")) != nullptr) {
+ if (matchNames_) {
+ regfree(®ex_);
+ matchNames_ = false;
+ }
+ if (regcomp(®ex_, word, REG_EXTENDED) != 0) {
+ return { false, "invalid regex" };
+ }
+ matchNames_ = true;
+ namePattern_ = word;
+ config_.action = Trace;
+ } else {
+ return { false, "no process specified" };
+ }
+ return { true, "" };
+ }
+
+ // Set the action to be taken on a traced process. The incoming default action is Trace;
+ // this method may overwrite that action.
+ std::pair<bool, std::string> setTracedAction(std::string config) {
+ const char* s = config.c_str();
+ const char* word = nullptr;
+ if (sscanf(s, "expire=%d", &config_.earlyTimeout) == 1) {
+ if (config_.earlyTimeout < 0) {
+ return { false, "invalid expire timeout" };
+ }
+ config_.action = Expire;
+ } else {
+ return { false, std::string("cannot parse action ") + s };
+ }
+ return { true, "" };
+ }
+
+ // Return the string value of an action.
+ static const char* toString(EarlyAction action) {
+ switch (action) {
+ case None: return "none";
+ case Trace: return "trace";
+ case Expire: return "expire";
+ }
+ return "unknown";
+ }
+
+ // Return the action represented by the string.
+ static EarlyAction fromString(const char* action) {
+ if (strcmp(action, "expire") == 0) return Expire;
+ return None;
+ }
+
+ // Return the help message. This has everything except the invocation command.
+ static std::string help() {
+ static const char* msg =
+ "help show this message\n"
+ "show report the current configuration\n"
+ "off clear the current configuration, turning off all tracing\n"
+ "spec... configure tracing according to the specification list\n"
+ " action=<action> what to do when a split timer expires\n"
+ " expire expire the timer to the upper levels\n"
+ " event generate extra trace events\n"
+ " pid=<pid>[,<pid>] watch the processes in the pid list\n"
+ " pid=all watch every process in the system\n"
+ " name=<regex> watch the processes whose name matches the regex\n";
+ return msg;
+ }
+
+ // A small convenience function for parsing. If the haystack starts with the needle and the
+ // haystack has at least one more character following, return a pointer to the following
+ // character. Otherwise return null.
+ static const char* startsWith(const char* haystack, const char* needle) {
+ if (strncmp(haystack, needle, strlen(needle)) == 0 && strlen(haystack) + strlen(needle)) {
+ return haystack + strlen(needle);
+ }
+ return nullptr;
+ }
+
+ // Return the currently watched pids. The lock must be held.
+ std::string watchedPidsLocked() const {
+ if (watched_.size() == 0) return "none";
+ bool first = true;
+ std::string result = "";
+ for (auto i = watched_.cbegin(); i != watched_.cend(); i++) {
+ if (first) {
+ result += StringPrintf("%d", *i);
+ } else {
+ result += StringPrintf(",%d", *i);
+ }
+ }
+ return result;
+ }
+
+ // Return the current configuration, in a form that can be consumed by setConfig().
+ std::string currentConfigLocked() const {
+ if (!config_.enabled) return "off";
+ std::string result;
+ if (matchAllPids_) {
+ result = "pid=all";
+ } else if (matchNames_) {
+ result = StringPrintf("name=\"%s\"", namePattern_.c_str());
+ } else {
+ result = std::string("pid=") + watchedPidsLocked();
+ }
+ switch (config_.action) {
+ case None:
+ break;
+ case Trace:
+ // The default action is Trace
+ break;
+ case Expire:
+ result += StringPrintf(" %s=%d", toString(config_.action), config_.earlyTimeout);
+ break;
+ }
+ return result;
+ }
+
+ // Reset the current configuration.
+ void resetLocked() {
+ if (!config_.enabled) return;
+
+ config_.enabled = false;
+ config_.earlyTimeout = 0;
+ config_.action = {};
+ matchAllPids_ = false;
+ watched_.clear();
+ if (matchNames_) regfree(®ex_);
+ matchNames_ = false;
+ namePattern_ = "";
+ matchedPids_.clear();
+ unmatchedPids_.clear();
+ }
+
+ // The lock for all operations
+ mutable Mutex lock_;
+
+ // The current tracing information, when a process matches.
+ TraceConfig config_;
+
+ // A short-hand flag that causes all processes to be tracing without the overhead of
+ // searching any of the maps.
+ bool matchAllPids_;
+
+ // A set of process IDs that should be traced. This is updated directly in setConfig()
+ // and only includes pids that were explicitly called out in the configuration.
+ std::set<pid_t> watched_;
+
+ // Name mapping is a relatively expensive operation, since the process name must be fetched
+ // from the /proc file system and then a regex must be evaluated. However, name mapping is
+ // useful to ensure processes are traced at the moment they start. To make this faster, a
+ // process's name is matched only once, and the result is stored in the matchedPids_ or
+ // unmatchedPids_ set, as appropriate. This can lead to confusion if a process changes its
+ // name after it starts.
+
+ // The global flag that enables name matching. If this is disabled then all name matching
+ // is disabled.
+ bool matchNames_;
+
+ // The regular expression that matches processes to be traced. This is saved for logging.
+ std::string namePattern_;
+
+ // The compiled regular expression.
+ regex_t regex_;
+
+ // The set of all pids that whose process names match (or do not match) the name regex.
+ // There is one set for pids that match and one set for pids that do not match.
+ std::set<pid_t> matchedPids_;
+ std::set<pid_t> unmatchedPids_;
+};
+
+/**
* This class encapsulates the anr timer service. The service manages a list of individual
* timers. A timer is either Running or Expired. Once started, a timer may be canceled or
* accepted. Both actions collect statistics about the timer and then delete it. An expired
@@ -177,7 +484,7 @@
* traditional void* and Java object pointer. The remaining parameters are
* configuration options.
*/
- AnrTimerService(char const* label, notifier_t notifier, void* cookie, jweak jtimer, Ticker*,
+ AnrTimerService(const char* label, notifier_t notifier, void* cookie, jweak jtimer, Ticker*,
bool extend, bool freeze);
// Delete the service and clean up memory.
@@ -211,6 +518,11 @@
// Release a timer. The timer must be in the expired list.
bool release(timer_id_t);
+ // Configure a trace specification to trace selected timers. See AnrTimerTracer for details.
+ static std::pair<bool, std::string> trace(const std::vector<std::string>& spec) {
+ return tracer_.setConfig(spec);
+ }
+
// Return the Java object associated with this instance.
jweak jtimer() const {
return notifierObject_;
@@ -221,7 +533,7 @@
private:
// The service cannot be copied.
- AnrTimerService(AnrTimerService const&) = delete;
+ AnrTimerService(const AnrTimerService&) = delete;
// Insert a timer into the running list. The lock must be held by the caller.
void insertLocked(const Timer&);
@@ -230,7 +542,7 @@
Timer removeLocked(timer_id_t timerId);
// Add a timer to the expired list.
- void addExpiredLocked(Timer const&);
+ void addExpiredLocked(const Timer&);
// Scrub the expired list by removing all entries for non-existent processes. The expired
// lock must be held by the caller.
@@ -240,10 +552,10 @@
static const char* statusString(Status);
// The name of this service, for logging.
- std::string const label_;
+ const std::string label_;
// The callback that is invoked when a timer expires.
- notifier_t const notifier_;
+ const notifier_t notifier_;
// The two cookies passed to the notifier.
void* notifierCookie_;
@@ -289,8 +601,13 @@
// The clock used by this AnrTimerService.
Ticker *ticker_;
+
+ // The global tracing specification.
+ static AnrTimerTracer tracer_;
};
+AnrTimerTracer AnrTimerService::tracer_;
+
class AnrTimerService::ProcessStats {
public:
nsecs_t cpu_time;
@@ -337,14 +654,23 @@
class AnrTimerService::Timer {
public:
// A unique ID assigned when the Timer is created.
- timer_id_t const id;
+ const timer_id_t id;
// The creation parameters. The timeout is the original, relative timeout.
- int const pid;
- int const uid;
- nsecs_t const timeout;
- bool const extend;
- bool const freeze;
+ const int pid;
+ const int uid;
+ const nsecs_t timeout;
+ // True if the timer may be extended.
+ const bool extend;
+ // True if process should be frozen when its timer expires.
+ const bool freeze;
+ // This is a percentage between 0 and 100. If it is non-zero then timer will fire at
+ // timeout*split/100, and the EarlyAction will be invoked. The timer may continue running
+ // or may expire, depending on the action. Thus, this value "splits" the timeout into two
+ // pieces.
+ const int split;
+ // The action to take if split (above) is non-zero, when the timer reaches the split point.
+ const AnrTimerTracer::EarlyAction action;
// The state of this timer.
Status status;
@@ -355,6 +681,9 @@
// The scheduled timeout. This is an absolute time. It may be extended.
nsecs_t scheduled;
+ // True if this timer is split and in its second half
+ bool splitting;
+
// True if this timer has been extended.
bool extended;
@@ -367,22 +696,10 @@
// The default constructor is used to create timers that are Invalid, representing the "not
// found" condition when a collection is searched.
- Timer() :
- id(NOTIMER),
- pid(0),
- uid(0),
- timeout(0),
- extend(false),
- freeze(false),
- status(Invalid),
- started(0),
- scheduled(0),
- extended(false),
- frozen(false) {
- }
+ Timer() : Timer(NOTIMER) { }
- // This constructor creates a timer with the specified id. This can be used as the argument
- // to find().
+ // This constructor creates a timer with the specified id and everything else set to
+ // "empty". This can be used as the argument to find().
Timer(timer_id_t id) :
id(id),
pid(0),
@@ -390,29 +707,37 @@
timeout(0),
extend(false),
freeze(false),
+ split(0),
+ action(AnrTimerTracer::None),
status(Invalid),
started(0),
scheduled(0),
+ splitting(false),
extended(false),
frozen(false) {
}
// Create a new timer. This starts the timer.
- Timer(int pid, int uid, nsecs_t timeout, bool extend, bool freeze) :
+ Timer(int pid, int uid, nsecs_t timeout, bool extend, bool freeze,
+ AnrTimerTracer::TraceConfig trace) :
id(nextId()),
pid(pid),
uid(uid),
timeout(timeout),
extend(extend),
freeze(pid != 0 && freeze),
+ split(trace.earlyTimeout),
+ action(trace.action),
status(Running),
started(now()),
- scheduled(started + timeout),
+ scheduled(started + (split > 0 ? (timeout*split)/100 : timeout)),
+ splitting(false),
extended(false),
frozen(false) {
if (extend && pid != 0) {
initial.fill(pid);
}
+
// A zero-pid is odd but it means the upper layers will never ANR the process. Freezing
// is always disabled. (It won't work anyway, but disabling it avoids error messages.)
ALOGI_IF(DEBUG_ERROR && pid == 0, "error: zero-pid %s", toString().c_str());
@@ -434,6 +759,23 @@
// returns false if the timer is eligible for extension. If the function returns false, the
// scheduled time is updated.
bool expire() {
+ if (split > 0 && !splitting) {
+ scheduled = started + timeout;
+ splitting = true;
+ event("split");
+ switch (action) {
+ case AnrTimerTracer::None:
+ case AnrTimerTracer::Trace:
+ break;
+ case AnrTimerTracer::Expire:
+ status = Expired;
+ maybeFreezeProcess();
+ event("expire");
+ break;
+ }
+ return status == Expired;
+ }
+
nsecs_t extension = 0;
if (extend && !extended) {
// Only one extension is permitted.
@@ -525,15 +867,15 @@
char tag[PATH_MAX];
snprintf(tag, sizeof(tag), "freeze(pid=%d,uid=%d)", pid, uid);
- ATRACE_ASYNC_FOR_TRACK_BEGIN(ANR_TIMER_TRACK, tag, cookie);
+ traceBegin(tag, cookie);
if (SetProcessProfiles(uid, pid, {"Frozen"})) {
ALOGI("freeze %s name=%s", toString().c_str(), getName().c_str());
frozen = true;
- ATRACE_ASYNC_FOR_TRACK_BEGIN(ANR_TIMER_TRACK, "frozen", cookie+1);
+ traceBegin("frozen", cookie+1);
} else {
ALOGE("error: freezing %s name=%s error=%s",
toString().c_str(), getName().c_str(), strerror(errno));
- ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie);
+ traceEnd(cookie);
}
}
@@ -543,7 +885,7 @@
// See maybeFreezeProcess for an explanation of the cookie.
const uint32_t cookie = id << 1;
- ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie+1);
+ traceEnd(cookie+1);
if (SetProcessProfiles(uid, pid, {"Unfrozen"})) {
ALOGI("unfreeze %s name=%s", toString().c_str(), getName().c_str());
frozen = false;
@@ -551,7 +893,7 @@
ALOGE("error: unfreezing %s name=%s error=%s",
toString().c_str(), getName().c_str(), strerror(errno));
}
- ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie);
+ traceEnd(cookie);
}
// Get the next free ID. NOTIMER is never returned.
@@ -564,12 +906,17 @@
}
// Log an event, non-verbose.
- void event(char const* tag) {
+ void event(const char* tag) {
event(tag, false);
}
// Log an event, guarded by the debug flag.
- void event(char const* tag, bool verbose) {
+ void event(const char* tag, bool verbose) {
+ if (action != AnrTimerTracer::None) {
+ char msg[PATH_MAX];
+ snprintf(msg, sizeof(msg), "%s(pid=%d)", tag, pid);
+ traceEvent(msg);
+ }
if (verbose) {
char name[PATH_MAX];
ALOGI_IF(DEBUG_TIMER, "event %s %s name=%s",
@@ -594,12 +941,12 @@
struct Entry {
const nsecs_t scheduled;
const timer_id_t id;
- AnrTimerService* const service;
+ AnrTimerService* service;
Entry(nsecs_t scheduled, timer_id_t id, AnrTimerService* service) :
scheduled(scheduled), id(id), service(service) {};
- bool operator<(const Entry &r) const {
+ bool operator<(const Entry& r) const {
return scheduled == r.scheduled ? id < r.id : scheduled < r.scheduled;
}
};
@@ -664,7 +1011,7 @@
}
// Remove every timer associated with the service.
- void remove(AnrTimerService const* service) {
+ void remove(const AnrTimerService* service) {
AutoMutex _l(lock_);
timer_id_t front = headTimerId();
for (auto i = running_.begin(); i != running_.end(); ) {
@@ -746,7 +1093,7 @@
// scheduled expiration time of the first entry.
void restartLocked() {
if (!running_.empty()) {
- Entry const x = *(running_.cbegin());
+ const Entry x = *(running_.cbegin());
nsecs_t delay = x.scheduled - now();
// Force a minimum timeout of 10ns.
if (delay < 10) delay = 10;
@@ -807,7 +1154,7 @@
std::atomic<size_t> AnrTimerService::Ticker::idGen_;
-AnrTimerService::AnrTimerService(char const* label, notifier_t notifier, void* cookie,
+AnrTimerService::AnrTimerService(const char* label, notifier_t notifier, void* cookie,
jweak jtimer, Ticker* ticker, bool extend, bool freeze) :
label_(label),
notifier_(notifier),
@@ -841,7 +1188,7 @@
AnrTimerService::timer_id_t AnrTimerService::start(int pid, int uid, nsecs_t timeout) {
AutoMutex _l(lock_);
- Timer t(pid, uid, timeout, extend_, freeze_);
+ Timer t(pid, uid, timeout, extend_, freeze_, tracer_.getConfig(pid));
insertLocked(t);
t.start();
counters_.started++;
@@ -918,7 +1265,7 @@
return okay;
}
-void AnrTimerService::addExpiredLocked(Timer const& timer) {
+void AnrTimerService::addExpiredLocked(const Timer& timer) {
scrubExpiredLocked();
expired_.insert(timer);
}
@@ -1077,7 +1424,7 @@
ScopedUtfChars name(env, jname);
jobject timer = env->NewWeakGlobalRef(jtimer);
AnrTimerService* service = new AnrTimerService(name.c_str(),
- anrNotify, &gAnrArgs, timer, gAnrArgs.ticker, extend, freeze);
+ anrNotify, &gAnrArgs, timer, gAnrArgs.ticker, extend, freeze);
return reinterpret_cast<jlong>(service);
}
@@ -1122,6 +1469,19 @@
return toService(ptr)->release(timerId);
}
+jstring anrTimerTrace(JNIEnv* env, jclass, jobjectArray jconfig) {
+ if (!nativeSupportEnabled) return nullptr;
+ std::vector<std::string> config;
+ const jsize jlen = jconfig == nullptr ? 0 : env->GetArrayLength(jconfig);
+ for (size_t i = 0; i < jlen; i++) {
+ jstring je = static_cast<jstring>(env->GetObjectArrayElement(jconfig, i));
+ ScopedUtfChars e(env, je);
+ config.push_back(e.c_str());
+ }
+ auto r = AnrTimerService::trace(config);
+ return env->NewStringUTF(r.second.c_str());
+}
+
jobjectArray anrTimerDump(JNIEnv *env, jclass, jlong ptr) {
if (!nativeSupportEnabled) return nullptr;
std::vector<std::string> stats = toService(ptr)->getDump();
@@ -1134,22 +1494,23 @@
}
static const JNINativeMethod methods[] = {
- {"nativeAnrTimerSupported", "()Z", (void*) anrTimerSupported},
- {"nativeAnrTimerCreate", "(Ljava/lang/String;ZZ)J", (void*) anrTimerCreate},
- {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose},
- {"nativeAnrTimerStart", "(JIIJ)I", (void*) anrTimerStart},
- {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel},
- {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept},
- {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard},
- {"nativeAnrTimerRelease", "(JI)Z", (void*) anrTimerRelease},
- {"nativeAnrTimerDump", "(J)[Ljava/lang/String;", (void*) anrTimerDump},
+ {"nativeAnrTimerSupported", "()Z", (void*) anrTimerSupported},
+ {"nativeAnrTimerCreate", "(Ljava/lang/String;ZZ)J", (void*) anrTimerCreate},
+ {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose},
+ {"nativeAnrTimerStart", "(JIIJ)I", (void*) anrTimerStart},
+ {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel},
+ {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept},
+ {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard},
+ {"nativeAnrTimerRelease", "(JI)Z", (void*) anrTimerRelease},
+ {"nativeAnrTimerTrace", "([Ljava/lang/String;)Ljava/lang/String;", (void*) anrTimerTrace},
+ {"nativeAnrTimerDump", "(J)[Ljava/lang/String;", (void*) anrTimerDump},
};
} // anonymous namespace
int register_android_server_utils_AnrTimer(JNIEnv* env)
{
- static const char *className = "com/android/server/utils/AnrTimer";
+ static const char* className = "com/android/server/utils/AnrTimer";
jniRegisterNativeMethods(env, className, methods, NELEM(methods));
nativeSupportEnabled = NATIVE_SUPPORT;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 3dd2f24a..1cad255 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -509,8 +509,12 @@
mAppStartInfoTracker.handleProcessBroadcastStart(3, app, buildIntent(COMPONENT),
false /* isAlarm */);
+ // Add a brief delay between timestamps to make sure the clock, which is in milliseconds has
+ // actually incremented.
+ sleep(1);
mAppStartInfoTracker.handleProcessBroadcastStart(2, app, buildIntent(COMPONENT),
false /* isAlarm */);
+ sleep(1);
mAppStartInfoTracker.handleProcessBroadcastStart(1, app, buildIntent(COMPONENT),
false /* isAlarm */);
@@ -557,9 +561,10 @@
// Now load from disk.
mAppStartInfoTracker.loadExistingProcessStartInfo();
- // Confirm clock has been set and that its current time is greater than the previous one.
+ // Confirm clock has been set and that its current time is greater than or equal to the
+ // previous one, thereby ensuring it was loaded from disk.
assertNotNull(mAppStartInfoTracker.mMonotonicClock);
- assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() > originalMonotonicTime);
+ assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() >= originalMonotonicTime);
}
private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 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/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
index b09e9b1..54282ff 100644
--- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -119,7 +119,7 @@
*/
private class TestInjector extends AnrTimer.Injector {
@Override
- boolean anrTimerServiceEnabled() {
+ boolean serviceEnabled() {
return mEnabled;
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index b8f9767..130690d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -38,6 +38,7 @@
import static android.app.Notification.FLAG_NO_DISMISS;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.Notification.FLAG_PROMOTED_ONGOING;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.VISIBILITY_PRIVATE;
@@ -53,6 +54,7 @@
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MAX;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
@@ -468,6 +470,8 @@
NotificationChannel mSilentChannel = new NotificationChannel("low", "low", IMPORTANCE_LOW);
+ NotificationChannel mMinChannel = new NotificationChannel("min", "min", IMPORTANCE_MIN);
+
private static final int NOTIFICATION_LOCATION_UNKNOWN = 0;
private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
@@ -558,8 +562,7 @@
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return FlagsParameterization.allCombinationsOf(
- FLAG_ALL_NOTIFS_NEED_TTL);
+ return FlagsParameterization.allCombinationsOf();
}
public NotificationManagerServiceTest(FlagsParameterization flags) {
@@ -856,15 +859,17 @@
mInternalService = mService.getInternalService();
mBinderService.createNotificationChannels(mPkg, new ParceledListSlice(
- Arrays.asList(mTestNotificationChannel, mSilentChannel)));
+ Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
mBinderService.createNotificationChannels(PKG_P, new ParceledListSlice(
- Arrays.asList(mTestNotificationChannel, mSilentChannel)));
+ Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
mBinderService.createNotificationChannels(PKG_O, new ParceledListSlice(
- Arrays.asList(mTestNotificationChannel, mSilentChannel)));
+ Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
assertNotNull(mBinderService.getNotificationChannel(
mPkg, mContext.getUserId(), mPkg, TEST_CHANNEL_ID));
assertNotNull(mBinderService.getNotificationChannel(
mPkg, mContext.getUserId(), mPkg, mSilentChannel.getId()));
+ assertNotNull(mBinderService.getNotificationChannel(
+ mPkg, mContext.getUserId(), mPkg, mMinChannel.getId()));
clearInvocations(mRankingHandler);
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
@@ -943,6 +948,16 @@
}
}
+ private ShortcutInfo createMockConvoShortcut() {
+ ShortcutInfo info = mock(ShortcutInfo.class);
+ when(info.getPackage()).thenReturn(mPkg);
+ when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
+ when(info.getUserId()).thenReturn(USER_SYSTEM);
+ when(info.isLongLived()).thenReturn(true);
+ when(info.isEnabled()).thenReturn(true);
+ return info;
+ }
+
private void simulatePackageSuspendBroadcast(boolean suspend, String pkg,
int uid) {
// mimics receive broadcast that package is (un)suspended
@@ -16540,13 +16555,298 @@
assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
}
- private ShortcutInfo createMockConvoShortcut() {
- ShortcutInfo info = mock(ShortcutInfo.class);
- when(info.getPackage()).thenReturn(mPkg);
- when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
- when(info.getUserId()).thenReturn(USER_SYSTEM);
- when(info.isLongLived()).thenReturn(true);
- when(info.isEnabled()).thenReturn(true);
- return info;
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testSetCanBePromoted_granted() throws Exception {
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ // qualifying posted notification
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ // qualifying enqueued notification
+ Notification n1 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+ StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0,
+ n1, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mTestNotificationChannel);
+
+ // another package but otherwise would qualify
+ Notification n2 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+ StatusBarNotification sbn2 = new StatusBarNotification(PKG_O, PKG_O, 7, null, UID_O, 0,
+ n2, UserHandle.getUserHandleForUid(UID_O), null, 0);
+ NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mTestNotificationChannel);
+
+ // not-qualifying posted notification
+ Notification n3 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .build();
+
+ StatusBarNotification sbn3 = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+ n3, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r3 = new NotificationRecord(mContext, sbn3, mTestNotificationChannel);
+
+ mService.addNotification(r3);
+ mService.addNotification(r2);
+ mService.addNotification(r);
+ mService.addEnqueuedNotification(r1);
+
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+
+ // the posted one
+ assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+ FLAG_PROMOTED_ONGOING)).isTrue();
+ // the enqueued one
+ assertThat(mService.hasFlag(r1.getNotification().flags, FLAG_PROMOTED_ONGOING)).isTrue();
+ // the other app
+ assertThat(mService.hasFlag(r2.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+ // same app, not qualifying
+ assertThat(mService.hasFlag(r3.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ // qualifying posted notification
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addNotification(r);
+
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+ waitForIdle();
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testSetCanBePromoted_revoked() throws Exception {
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ // start from true state
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+
+ // qualifying posted notification
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ // qualifying enqueued notification
+ Notification n1 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+ StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0,
+ n1, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mTestNotificationChannel);
+
+ // doesn't qualify, same package
+ Notification n2 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .build();
+ StatusBarNotification sbn2 = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+ n2, UserHandle.getUserHandleForUid(UID_O), null, 0);
+ NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mTestNotificationChannel);
+
+ mService.addNotification(r2);
+ mService.addNotification(r);
+ mService.addEnqueuedNotification(r1);
+
+ mBinderService.setCanBePromoted(mPkg, mUid, false);
+
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+
+ // the posted one
+ assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+ FLAG_PROMOTED_ONGOING)).isFalse();
+ // the enqueued one
+ assertThat(mService.hasFlag(r1.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+ // the not qualifying one
+ assertThat(mService.hasFlag(r2.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testSetCanBePromoted_revoked_onlyNotifiesOnce() throws Exception {
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ // start from true state
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+
+ // qualifying posted notification
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addNotification(r);
+
+ mBinderService.setCanBePromoted(mPkg, mUid, false);
+ waitForIdle();
+ mBinderService.setCanBePromoted(mPkg, mUid, false);
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testPostPromotableNotification() throws Exception {
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+ assertThat(mBinderService.canBePromoted(mPkg, mUid)).isTrue();
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .build();
+ //assertThat(n.hasPromotableCharacteristics()).isTrue();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+
+ assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+ FLAG_PROMOTED_ONGOING)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testPostPromotableNotification_noPermission() throws Exception {
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+
+ assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+ FLAG_PROMOTED_ONGOING)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testPostPromotableNotification_unimportantNotification() throws Exception {
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ Notification n = new Notification.Builder(mContext, mMinChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+
+ assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+ FLAG_PROMOTED_ONGOING)).isFalse();
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 1905ae4..7d63062 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -518,6 +518,17 @@
doneLatch.await();
}
+ private static NotificationChannel cloneChannel(NotificationChannel original) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return NotificationChannel.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
@Test
public void testWriteXml_onlyBackupsTargetUser() throws Exception {
// Setup package notifications.
@@ -631,6 +642,9 @@
}
mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
+ if (android.app.Flags.uiRichOngoing()) {
+ mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, true);
+ }
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
UserHandle.USER_ALL, channel1.getId(), channel2.getId(),
@@ -641,6 +655,9 @@
loadStreamXml(baos, false, UserHandle.USER_ALL);
assertTrue(mXmlHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
+ if (android.app.Flags.uiRichOngoing()) {
+ assertThat(mXmlHelper.canBePromoted(PKG_N_MR1, UID_N_MR1)).isTrue();
+ }
assertEquals(channel1,
mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
compareChannels(channel2,
@@ -6293,14 +6310,21 @@
}, 20, 50);
}
- private static NotificationChannel cloneChannel(NotificationChannel original) {
- Parcel parcel = Parcel.obtain();
- try {
- original.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- return NotificationChannel.CREATOR.createFromParcel(parcel);
- } finally {
- parcel.recycle();
- }
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testNoAppHasPermissionToPromoteByDefault() {
+ mHelper.setShowBadge(PKG_P, UID_P, true);
+ assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testSetCanBePromoted() {
+ mHelper.setCanBePromoted(PKG_P, UID_P, true);
+ assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
+
+ mHelper.setCanBePromoted(PKG_P, UID_P, false);
+ assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse();
+ verify(mHandler, never()).requestSort();
}
}
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..d4cba8d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -18,7 +18,7 @@
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
-import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.Flags.FLAG_MODES_API;
import static android.app.Flags.FLAG_MODES_UI;
@@ -221,7 +221,7 @@
@TestableLooper.RunWithLooper
public class ZenModeHelperTest extends UiServiceTestCase {
- private static final String EVENTS_DEFAULT_RULE_ID = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
+ private static final String EVENTS_DEFAULT_RULE_ID = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
private static final String SCHEDULE_DEFAULT_RULE_ID =
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID;
private static final String CUSTOM_PKG_NAME = "not.android";
@@ -1216,7 +1216,7 @@
// list for tracking which ids we've seen in the pulled atom output
List<String> ids = new ArrayList<>();
- ids.addAll(ZenModeConfig.DEFAULT_RULE_IDS);
+ ids.addAll(ZenModeConfig.getDefaultRuleIds());
ids.add(""); // empty string for root config
for (StatsEvent ev : events) {
@@ -1793,14 +1793,13 @@
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
assertTrue(rules.size() != 0);
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
assertTrue(rules.containsKey(defaultId));
}
assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
}
-
@Test
public void testReadXmlAllDisabledRulesResetDefaultRules() throws Exception {
setupZenConfig();
@@ -1830,7 +1829,7 @@
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
assertTrue(rules.size() != 0);
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
assertTrue(rules.containsKey(defaultId));
}
assertFalse(rules.containsKey("customRule"));
@@ -1839,6 +1838,7 @@
}
@Test
+ @DisableFlags(FLAG_MODES_UI) // modes_ui has only 1 default rule
public void testReadXmlOnlyOneDefaultRuleExists() throws Exception {
setupZenConfig();
Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
@@ -1882,11 +1882,11 @@
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
- assertTrue(rules.size() != 0);
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
- assertTrue(rules.containsKey(defaultId));
+ assertThat(rules).isNotEmpty();
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
+ assertThat(rules).containsKey(defaultId);
}
- assertFalse(rules.containsKey("customRule"));
+ assertThat(rules).doesNotContainKey("customRule");
assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
}
@@ -1932,13 +1932,13 @@
defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId(
defaultEventRuleInfo);
- defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
+ defaultEventRule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
defaultScheduleRule.zenPolicy = new ZenPolicy.Builder()
.allowAlarms(false)
.allowMedia(false)
.allowRepeatCallers(false)
.build();
- automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule);
+ automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, defaultEventRule);
mZenModeHelper.mConfig.automaticRules = automaticRules;
@@ -1951,18 +1951,19 @@
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
// check default rules
+ int expectedNumAutoRules = 1 + ZenModeConfig.getDefaultRuleIds().size(); // custom + default
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
- assertEquals(3, rules.size());
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
- assertTrue(rules.containsKey(defaultId));
+ assertThat(rules).hasSize(expectedNumAutoRules);
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
+ assertThat(rules).containsKey(defaultId);
}
- assertTrue(rules.containsKey("customRule"));
+ assertThat(rules).containsKey("customRule");
assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
- assertEquals(4, events.size());
+ assertThat(events).hasSize(expectedNumAutoRules + 1); // auto + manual
}
@Test
@@ -2151,8 +2152,8 @@
defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId(
defaultEventRuleInfo);
- defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
- automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule);
+ defaultEventRule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
+ automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, defaultEventRule);
mZenModeHelper.mConfig.automaticRules = automaticRules;
@@ -2167,7 +2168,7 @@
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
assertThat(rules.size()).isGreaterThan(0);
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
assertThat(rules).containsKey(defaultId);
ZenRule rule = rules.get(defaultId);
assertThat(rule.zenPolicy).isNotNull();
@@ -2371,7 +2372,7 @@
// Find default rules; check they have non-null policies; check that they match the default
// and not whatever has been set up in setupZenConfig.
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
assertThat(rules).containsKey(defaultId);
ZenRule rule = rules.get(defaultId);
assertThat(rule.zenPolicy).isNotNull();
@@ -6884,7 +6885,7 @@
mZenModeHelper.onUserSwitched(101);
ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
- ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+ ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
assertThat(eventsRule).isNotNull();
assertThat(eventsRule.zenPolicy).isNull();
@@ -6900,7 +6901,7 @@
mZenModeHelper.onUserSwitched(201);
ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
- ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+ ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
assertThat(eventsRule).isNotNull();
assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
@@ -6915,11 +6916,11 @@
mZenModeHelper.onUserSwitched(301);
ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
- ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+ ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
assertThat(eventsRule).isNotNull();
assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
- assertThat(eventsRule.type).isEqualTo(TYPE_SCHEDULE_CALENDAR);
+ assertThat(eventsRule.type).isEqualTo(TYPE_SCHEDULE_TIME);
assertThat(eventsRule.triggerDescription).isNotEmpty();
}
@@ -6992,6 +6993,62 @@
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());
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void readXml_withDisabledEventsRule_deletesIt() throws Exception {
+ ZenRule rule = new ZenRule();
+ rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
+ rule.name = "Events";
+ rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rule.conditionId = Uri.parse("events");
+
+ rule.enabled = false;
+ mZenModeHelper.mConfig.automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, rule);
+ ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
+ TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
+
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules).doesNotContainKey(
+ ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void readXml_withEnabledEventsRule_keepsIt() throws Exception {
+ ZenRule rule = new ZenRule();
+ rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
+ rule.name = "Events";
+ rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rule.conditionId = Uri.parse("events");
+
+ rule.enabled = true;
+ mZenModeHelper.mConfig.automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, rule);
+ ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
+ TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
+
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules).containsKey(
+ ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+ }
+
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/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/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index a678147..0f4809c 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -33,6 +33,7 @@
import android.widget.Toast;
import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
/**
@@ -283,6 +284,8 @@
int minSdkVersion = Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck)
? query.minSdkVersionForFine : query.minSdkVersionForCoarse;
+ UserHandle callingUserHandle = UserHandle.getUserHandleForUid(query.callingUid);
+
// If the app fails for some reason, see if it should be allowed to proceed.
if (minSdkVersion > MAX_SDK_FOR_ANY_ENFORCEMENT) {
String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog
@@ -291,7 +294,8 @@
+ query.method;
logError(context, query, errorMsg);
return null;
- } else if (!isAppAtLeastSdkVersion(context, query.callingPackage, minSdkVersion)) {
+ } else if (!isAppAtLeastSdkVersion(context, callingUserHandle, query.callingPackage,
+ minSdkVersion)) {
String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog
+ " because it doesn't target API " + minSdkVersion + " yet."
+ " Please fix this app. Called from " + query.method;
@@ -420,11 +424,19 @@
}
}
- private static boolean isAppAtLeastSdkVersion(Context context, String pkgName, int sdkVersion) {
+ private static boolean isAppAtLeastSdkVersion(Context context,
+ @NonNull UserHandle callingUserHandle, String pkgName, int sdkVersion) {
try {
- if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
- >= sdkVersion) {
- return true;
+ if (Flags.hsumPackageManager()) {
+ if (context.getPackageManager().getApplicationInfoAsUser(
+ pkgName, 0, callingUserHandle).targetSdkVersion >= sdkVersion) {
+ return true;
+ }
+ } else {
+ if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
+ >= sdkVersion) {
+ return true;
+ }
}
} catch (PackageManager.NameNotFoundException e) {
// In case of exception, assume known app (more strict checking)
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/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/OWNERS
new file mode 100644
index 0000000..981b316
--- /dev/null
+++ b/tests/FlickerTests/ActivityEmbedding/OWNERS
@@ -0,0 +1 @@
+# Bug component: 1168918
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index b3a998e..b5258df 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -57,6 +57,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Consumer;
+
/**
* Build/Install/Run:
* atest TouchpadDebugViewTest
@@ -99,10 +101,12 @@
when(mInputManager.getInputDevice(TOUCHPAD_DEVICE_ID)).thenReturn(inputDevice);
+ Consumer<Integer> touchpadSwitchHandler = id -> {};
+
mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID,
new TouchpadHardwareProperties.Builder(0f, 0f, 500f,
500f, 45f, 47f, -4f, 5f, (short) 10, true,
- true).build());
+ true).build(), touchpadSwitchHandler);
mTouchpadDebugView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
@@ -321,26 +325,30 @@
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.parseColor("#769763"));
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.parseColor("#5455A9"));
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.parseColor("#769763"));
// 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.parseColor("#769763"));
}
@Test
diff --git a/tests/broadcasts/OWNERS b/tests/broadcasts/OWNERS
new file mode 100644
index 0000000..d2e1f81
--- /dev/null
+++ b/tests/broadcasts/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 316181
+include platform/frameworks/base:/BROADCASTS_OWNERS
diff --git a/tests/broadcasts/unit/Android.bp b/tests/broadcasts/unit/Android.bp
new file mode 100644
index 0000000..0692477
--- /dev/null
+++ b/tests/broadcasts/unit/Android.bp
@@ -0,0 +1,44 @@
+//
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "BroadcastUnitTests",
+ srcs: ["src/**/*.java"],
+ defaults: [
+ "modules-utils-extended-mockito-rule-defaults",
+ ],
+ static_libs: [
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "mockito-target-extended-minus-junit4",
+ "truth",
+ "flag-junit",
+ "android.app.flags-aconfig-java",
+ ],
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/tests/broadcasts/unit/AndroidManifest.xml b/tests/broadcasts/unit/AndroidManifest.xml
new file mode 100644
index 0000000..e9c5248
--- /dev/null
+++ b/tests/broadcasts/unit/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.broadcasts.unit" >
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.broadcasts.unit"
+ android:label="Broadcasts Unit Tests"/>
+</manifest>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/AndroidTest.xml b/tests/broadcasts/unit/AndroidTest.xml
new file mode 100644
index 0000000..b91e4783
--- /dev/null
+++ b/tests/broadcasts/unit/AndroidTest.xml
@@ -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.
+-->
+<configuration description="Runs Broadcasts tests">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="BroadcastUnitTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="BroadcastUnitTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.broadcasts.unit" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING
new file mode 100644
index 0000000..0e824c5
--- /dev/null
+++ b/tests/broadcasts/unit/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "postsubmit": [
+ {
+ "name": "BroadcastUnitTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
new file mode 100644
index 0000000..b7c412d
--- /dev/null
+++ b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+import static android.content.Intent.ACTION_BATTERY_CHANGED;
+import static android.content.Intent.ACTION_DEVICE_STORAGE_LOW;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.ArrayMap;
+
+import androidx.annotation.GuardedBy;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+@EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BroadcastStickyCacheTest {
+ @ClassRule
+ public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
+
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+ .mockStatic(SystemProperties.class)
+ .build();
+
+ private static final String PROP_KEY_BATTERY_CHANGED = BroadcastStickyCache.getKey(
+ ACTION_BATTERY_CHANGED);
+
+ private final TestSystemProps mTestSystemProps = new TestSystemProps();
+
+ @Before
+ public void setUp() {
+ doAnswer(invocation -> {
+ final String name = invocation.getArgument(0);
+ final long value = Long.parseLong(invocation.getArgument(1));
+ mTestSystemProps.add(name, value);
+ return null;
+ }).when(() -> SystemProperties.set(anyString(), anyString()));
+ doAnswer(invocation -> {
+ final String name = invocation.getArgument(0);
+ final TestSystemProps.Handle testHandle = mTestSystemProps.query(name);
+ if (testHandle == null) {
+ return null;
+ }
+ final SystemProperties.Handle handle = Mockito.mock(SystemProperties.Handle.class);
+ doAnswer(handleInvocation -> testHandle.getLong(-1)).when(handle).getLong(anyLong());
+ return handle;
+ }).when(() -> SystemProperties.find(anyString()));
+ }
+
+ @After
+ public void tearDown() {
+ mTestSystemProps.clear();
+ BroadcastStickyCache.clearForTest();
+ }
+
+ @Test
+ public void testUseCache_nullFilter() {
+ assertThat(BroadcastStickyCache.useCache(null)).isEqualTo(false);
+ }
+
+ @Test
+ public void testUseCache_noActions() {
+ final IntentFilter filter = new IntentFilter();
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+ }
+
+ @Test
+ public void testUseCache_multipleActions() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DEVICE_STORAGE_LOW);
+ filter.addAction(ACTION_BATTERY_CHANGED);
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+ }
+
+ @Test
+ public void testUseCache_valueNotSet() {
+ final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+ }
+
+ @Test
+ public void testUseCache() {
+ final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+ final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
+ .putExtra(BatteryManager.EXTRA_LEVEL, 90);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ BroadcastStickyCache.add(filter, intent);
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
+ }
+
+ @Test
+ public void testUseCache_versionMismatch() {
+ final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+ final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
+ .putExtra(BatteryManager.EXTRA_LEVEL, 90);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ BroadcastStickyCache.add(filter, intent);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
+ }
+
+ @Test
+ public void testAdd() {
+ final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
+ Intent intent = new Intent(ACTION_BATTERY_CHANGED)
+ .putExtra(BatteryManager.EXTRA_LEVEL, 90);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ BroadcastStickyCache.add(filter, intent);
+ assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
+ Intent actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
+ assertThat(actualIntent).isNotNull();
+ assertEquals(actualIntent, intent);
+
+ intent = new Intent(ACTION_BATTERY_CHANGED)
+ .putExtra(BatteryManager.EXTRA_LEVEL, 99);
+ BroadcastStickyCache.add(filter, intent);
+ actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
+ assertThat(actualIntent).isNotNull();
+ assertEquals(actualIntent, intent);
+ }
+
+ @Test
+ public void testIncrementVersion_propExists() {
+ SystemProperties.set(PROP_KEY_BATTERY_CHANGED, String.valueOf(100));
+
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(101);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(102);
+ }
+
+ @Test
+ public void testIncrementVersion_propNotExists() {
+ // Verify that the property doesn't exist
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(1);
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
+ }
+
+ @Test
+ public void testIncrementVersionIfExists_propExists() {
+ BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
+
+ BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
+ BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(3);
+ }
+
+ @Test
+ public void testIncrementVersionIfExists_propNotExists() {
+ // Verify that the property doesn't exist
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+
+ BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+ // Verify that property is not added as part of the querying.
+ BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
+ assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
+ }
+
+ private void assertEquals(Intent actualIntent, Intent expectedIntent) {
+ assertThat(actualIntent.getAction()).isEqualTo(expectedIntent.getAction());
+ assertEquals(actualIntent.getExtras(), expectedIntent.getExtras());
+ }
+
+ private void assertEquals(Bundle actualExtras, Bundle expectedExtras) {
+ assertWithMessage("Extras expected=%s, actual=%s", expectedExtras, actualExtras)
+ .that(actualExtras.kindofEquals(expectedExtras)).isTrue();
+ }
+
+ private static final class TestSystemProps {
+ @GuardedBy("mSysProps")
+ private final ArrayMap<String, Long> mSysProps = new ArrayMap<>();
+
+ public void add(String name, long value) {
+ synchronized (mSysProps) {
+ mSysProps.put(name, value);
+ }
+ }
+
+ public long get(String name, long defaultValue) {
+ synchronized (mSysProps) {
+ final int idx = mSysProps.indexOfKey(name);
+ return idx >= 0 ? mSysProps.valueAt(idx) : defaultValue;
+ }
+ }
+
+ public Handle query(String name) {
+ synchronized (mSysProps) {
+ return mSysProps.containsKey(name) ? new Handle(name) : null;
+ }
+ }
+
+ public void clear() {
+ synchronized (mSysProps) {
+ mSysProps.clear();
+ }
+ }
+
+ public class Handle {
+ private final String mName;
+
+ Handle(String name) {
+ mName = name;
+ }
+
+ public long getLong(long defaultValue) {
+ return get(mName, defaultValue);
+ }
+ }
+ }
+}
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index a9e6328..590f719 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -30,8 +30,8 @@
name: "systemfeatures-gen-tests-srcs",
cmd: "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwNoFeatures --readonly=false > $(location RwNoFeatures.java) && " +
"$(location systemfeatures-gen-tool) com.android.systemfeatures.RoNoFeatures --readonly=true --feature-apis=WATCH > $(location RoNoFeatures.java) && " +
- "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: > $(location RwFeatures.java) && " +
- "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: --feature-apis=WATCH,PC > $(location RoFeatures.java)",
+ "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:UNAVAILABLE --feature=AUTO: > $(location RwFeatures.java) && " +
+ "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:UNAVAILABLE --feature=AUTO: --feature-apis=WATCH,PC > $(location RoFeatures.java)",
out: [
"RwNoFeatures.java",
"RoNoFeatures.java",
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index 5df453d..cba521e 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -20,7 +20,10 @@
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeSpec
+import java.util.HashMap
+import java.util.Map
import javax.lang.model.element.Modifier
/*
@@ -31,7 +34,7 @@
*
* <pre>
* <cmd> com.foo.RoSystemFeatures --readonly=true \
- * --feature=WATCH:0 --feature=AUTOMOTIVE: --feature=VULKAN:9348
+ * --feature=WATCH:0 --feature=AUTOMOTIVE: --feature=VULKAN:9348 --feature=PC:UNAVAILABLE
* --feature-apis=WATCH,PC,LEANBACK
* </pre>
*
@@ -43,12 +46,13 @@
* @AssumeTrueForR8
* public static boolean hasFeatureWatch(Context context);
* @AssumeFalseForR8
- * public static boolean hasFeatureAutomotive(Context context);
+ * public static boolean hasFeaturePc(Context context);
* @AssumeTrueForR8
* public static boolean hasFeatureVulkan(Context context);
- * public static boolean hasFeaturePc(Context context);
+ * public static boolean hasFeatureAutomotive(Context context);
* public static boolean hasFeatureLeanback(Context context);
* public static Boolean maybeHasFeature(String feature, int version);
+ * public static ArrayMap<String, FeatureInfo> getCompileTimeAvailableFeatures();
* }
* </pre>
*/
@@ -58,6 +62,7 @@
private const val READONLY_ARG = "--readonly="
private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager")
private val CONTEXT_CLASS = ClassName.get("android.content", "Context")
+ private val FEATUREINFO_CLASS = ClassName.get("android.content.pm", "FeatureInfo")
private val ASSUME_TRUE_CLASS =
ClassName.get("com.android.aconfig.annotations", "AssumeTrueForR8")
private val ASSUME_FALSE_CLASS =
@@ -67,7 +72,10 @@
println("Usage: SystemFeaturesGenerator <outputClassName> [options]")
println(" Options:")
println(" --readonly=true|false Whether to encode features as build-time constants")
- println(" --feature=\$NAME:\$VER A feature+version pair (blank version == disabled)")
+ println(" --feature=\$NAME:\$VER A feature+version pair, where \$VER can be:")
+ println(" * blank/empty == undefined (variable API)")
+ println(" * valid int == enabled (constant API)")
+ println(" * UNAVAILABLE == disabled (constant API)")
println(" This will always generate associated query APIs,")
println(" adding to or replacing those from `--feature-apis=`.")
println(" --feature-apis=\$NAME_1,\$NAME_2")
@@ -89,7 +97,7 @@
var readonly = false
var outputClassName: ClassName? = null
- val featureArgs = mutableListOf<FeatureArg>()
+ val featureArgs = mutableListOf<FeatureInfo>()
// We could just as easily hardcode this list, as the static API surface should change
// somewhat infrequently, but this decouples the codegen from the framework completely.
val featureApiArgs = mutableSetOf<String>()
@@ -122,7 +130,7 @@
featureArgs.associateByTo(
features,
{ it.name },
- { FeatureInfo(it.name, it.version, readonly) },
+ { FeatureInfo(it.name, it.version, it.readonly && readonly) },
)
outputClassName
@@ -139,6 +147,7 @@
addFeatureMethodsToClass(classBuilder, features.values)
addMaybeFeatureMethodToClass(classBuilder, features.values)
+ addGetFeaturesMethodToClass(classBuilder, features.values)
// TODO(b/203143243): Add validation of build vs runtime values to ensure consistency.
JavaFile.builder(outputClassName.packageName(), classBuilder.build())
@@ -154,13 +163,17 @@
* Parses a feature argument of the form "--feature=$NAME:$VER", where "$VER" is optional.
* * "--feature=WATCH:0" -> Feature enabled w/ version 0 (default version when enabled)
* * "--feature=WATCH:7" -> Feature enabled w/ version 7
- * * "--feature=WATCH:" -> Feature disabled
+ * * "--feature=WATCH:" -> Feature status undefined, runtime API generated
+ * * "--feature=WATCH:UNAVAILABLE" -> Feature disabled
*/
- private fun parseFeatureArg(arg: String): FeatureArg {
+ private fun parseFeatureArg(arg: String): FeatureInfo {
val featureArgs = arg.substring(FEATURE_ARG.length).split(":")
val name = parseFeatureName(featureArgs[0])
- val version = featureArgs.getOrNull(1)?.toIntOrNull()
- return FeatureArg(name, version)
+ return when (featureArgs.getOrNull(1)) {
+ null, "" -> FeatureInfo(name, null, readonly = false)
+ "UNAVAILABLE" -> FeatureInfo(name, null, readonly = true)
+ else -> FeatureInfo(name, featureArgs[1].toIntOrNull(), readonly = true)
+ }
}
private fun parseFeatureName(name: String): String =
@@ -218,7 +231,7 @@
/*
* Adds a generic query method to the class with the form: {@code public static boolean
* maybeHasFeature(String featureName, int version)}, returning null if the feature version is
- * undefined or not readonly.
+ * undefined or not (compile-time) readonly.
*
* This method is useful for internal usage within the framework, e.g., from the implementation
* of {@link android.content.pm.PackageManager#hasSystemFeature(Context)}, when we may only
@@ -267,7 +280,41 @@
builder.addMethod(methodBuilder.build())
}
- private data class FeatureArg(val name: String, val version: Int?)
+ /*
+ * Adds a method to get all compile-time enabled features.
+ *
+ * This method is useful for internal usage within the framework to augment
+ * any system features that are parsed from the various partitions.
+ */
+ private fun addGetFeaturesMethodToClass(
+ builder: TypeSpec.Builder,
+ features: Collection<FeatureInfo>,
+ ) {
+ val methodBuilder =
+ MethodSpec.methodBuilder("getCompileTimeAvailableFeatures")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addAnnotation(ClassName.get("android.annotation", "NonNull"))
+ .addJavadoc("Gets features marked as available at compile-time, keyed by name." +
+ "\n\n@hide")
+ .returns(ParameterizedTypeName.get(
+ ClassName.get(Map::class.java),
+ ClassName.get(String::class.java),
+ FEATUREINFO_CLASS))
+
+ val availableFeatures = features.filter { it.readonly && it.version != null }
+ methodBuilder.addStatement("Map<String, FeatureInfo> features = new \$T<>(\$L)",
+ HashMap::class.java, availableFeatures.size)
+ if (!availableFeatures.isEmpty()) {
+ methodBuilder.addStatement("FeatureInfo fi = new FeatureInfo()")
+ }
+ for (feature in availableFeatures) {
+ methodBuilder.addStatement("fi.name = \$T.\$N", PACKAGEMANAGER_CLASS, feature.name)
+ methodBuilder.addStatement("fi.version = \$L", feature.version)
+ methodBuilder.addStatement("features.put(fi.name, new FeatureInfo(fi))")
+ }
+ methodBuilder.addStatement("return features")
+ builder.addMethod(methodBuilder.build())
+ }
private data class FeatureInfo(val name: String, val version: Int?, val readonly: Boolean)
}
diff --git a/tools/systemfeatures/tests/golden/RoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
index 724639b..edbfc42 100644
--- a/tools/systemfeatures/tests/golden/RoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
@@ -3,16 +3,20 @@
// --readonly=true \
// --feature=WATCH:1 \
// --feature=WIFI:0 \
-// --feature=VULKAN:-1 \
+// --feature=VULKAN:UNAVAILABLE \
// --feature=AUTO: \
// --feature-apis=WATCH,PC
package com.android.systemfeatures;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
import com.android.aconfig.annotations.AssumeFalseForR8;
import com.android.aconfig.annotations.AssumeTrueForR8;
+import java.util.HashMap;
+import java.util.Map;
/**
* @hide
@@ -62,9 +66,8 @@
*
* @hide
*/
- @AssumeFalseForR8
public static boolean hasFeatureAuto(Context context) {
- return false;
+ return hasFeatureFallback(context, PackageManager.FEATURE_AUTO);
}
private static boolean hasFeatureFallback(Context context, String featureName) {
@@ -79,10 +82,27 @@
switch (featureName) {
case PackageManager.FEATURE_WATCH: return 1 >= version;
case PackageManager.FEATURE_WIFI: return 0 >= version;
- case PackageManager.FEATURE_VULKAN: return -1 >= version;
- case PackageManager.FEATURE_AUTO: return false;
+ case PackageManager.FEATURE_VULKAN: return false;
default: break;
}
return null;
}
+
+ /**
+ * Gets features marked as available at compile-time, keyed by name.
+ *
+ * @hide
+ */
+ @NonNull
+ public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
+ Map<String, FeatureInfo> features = new HashMap<>(2);
+ FeatureInfo fi = new FeatureInfo();
+ fi.name = PackageManager.FEATURE_WATCH;
+ fi.version = 1;
+ features.put(fi.name, new FeatureInfo(fi));
+ fi.name = PackageManager.FEATURE_WIFI;
+ fi.version = 0;
+ features.put(fi.name, new FeatureInfo(fi));
+ return features;
+ }
}
diff --git a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
index 59c5b4e..bf7a006 100644
--- a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen
@@ -4,9 +4,13 @@
// --feature-apis=WATCH
package com.android.systemfeatures;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
+import java.util.HashMap;
+import java.util.Map;
/**
* @hide
@@ -32,4 +36,15 @@
public static Boolean maybeHasFeature(String featureName, int version) {
return null;
}
+
+ /**
+ * Gets features marked as available at compile-time, keyed by name.
+ *
+ * @hide
+ */
+ @NonNull
+ public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
+ Map<String, FeatureInfo> features = new HashMap<>(0);
+ return features;
+ }
}
diff --git a/tools/systemfeatures/tests/golden/RwFeatures.java.gen b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
index 6f89759..b20b228 100644
--- a/tools/systemfeatures/tests/golden/RwFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
@@ -3,13 +3,17 @@
// --readonly=false \
// --feature=WATCH:1 \
// --feature=WIFI:0 \
-// --feature=VULKAN:-1 \
+// --feature=VULKAN:UNAVAILABLE \
// --feature=AUTO:
package com.android.systemfeatures;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
+import java.util.HashMap;
+import java.util.Map;
/**
* @hide
@@ -62,4 +66,15 @@
public static Boolean maybeHasFeature(String featureName, int version) {
return null;
}
+
+ /**
+ * Gets features marked as available at compile-time, keyed by name.
+ *
+ * @hide
+ */
+ @NonNull
+ public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
+ Map<String, FeatureInfo> features = new HashMap<>(0);
+ return features;
+ }
}
diff --git a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
index 2111d56..d91f5b6 100644
--- a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen
@@ -3,8 +3,12 @@
// --readonly=false
package com.android.systemfeatures;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.FeatureInfo;
+import java.util.HashMap;
+import java.util.Map;
/**
* @hide
@@ -21,4 +25,15 @@
public static Boolean maybeHasFeature(String featureName, int version) {
return null;
}
+
+ /**
+ * Gets features marked as available at compile-time, keyed by name.
+ *
+ * @hide
+ */
+ @NonNull
+ public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() {
+ Map<String, FeatureInfo> features = new HashMap<>(0);
+ return features;
+ }
}
diff --git a/tools/systemfeatures/tests/src/FeatureInfo.java b/tools/systemfeatures/tests/src/FeatureInfo.java
new file mode 100644
index 0000000..9d57edc
--- /dev/null
+++ b/tools/systemfeatures/tests/src/FeatureInfo.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+/** Stub for testing */
+public final class FeatureInfo {
+ public String name;
+ public int version;
+
+ public FeatureInfo() {}
+
+ public FeatureInfo(FeatureInfo orig) {
+ name = orig.name;
+ version = orig.version;
+ }
+}
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
index 6dfd244..39f8fc4 100644
--- a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
+++ b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
import org.junit.Before;
@@ -36,6 +37,8 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Map;
+
@RunWith(JUnit4.class)
public class SystemFeaturesGeneratorTest {
@@ -57,6 +60,7 @@
assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
assertThat(RwNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
+ assertThat(RwNoFeatures.getCompileTimeAvailableFeatures()).isEmpty();
}
@Test
@@ -68,6 +72,7 @@
assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
assertThat(RoNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
+ assertThat(RoNoFeatures.getCompileTimeAvailableFeatures()).isEmpty();
// Also ensure we fall back to the PackageManager for feature APIs without an accompanying
// versioned feature definition.
@@ -101,6 +106,7 @@
assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull();
assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
assertThat(RwFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
+ assertThat(RwFeatures.getCompileTimeAvailableFeatures()).isEmpty();
}
@Test
@@ -110,10 +116,13 @@
assertThat(RoFeatures.hasFeatureWatch(mContext)).isTrue();
assertThat(RoFeatures.hasFeatureWifi(mContext)).isTrue();
assertThat(RoFeatures.hasFeatureVulkan(mContext)).isFalse();
- assertThat(RoFeatures.hasFeatureAuto(mContext)).isFalse();
verify(mPackageManager, never()).hasSystemFeature(anyString(), anyInt());
- // For defined feature types, conditional queries should reflect the build-time versions.
+ // For defined feature types, conditional queries should reflect either:
+ // * Enabled if the feature version is specified
+ // * Disabled if UNAVAILABLE is specified
+ // * Unknown if no version value is provided
+
// VERSION=1
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, -1)).isTrue();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isTrue();
@@ -124,15 +133,19 @@
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isTrue();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 100)).isFalse();
- // VERSION=-1
- assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, -1)).isTrue();
+ // VERSION=UNAVAILABLE
+ assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, -1)).isFalse();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isFalse();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 100)).isFalse();
- // DISABLED
- assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, -1)).isFalse();
- assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isFalse();
- assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 100)).isFalse();
+ // VERSION=
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(false);
+ assertThat(RoFeatures.hasFeatureAuto(mContext)).isFalse();
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(true);
+ assertThat(RoFeatures.hasFeatureAuto(mContext)).isTrue();
+ assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, -1)).isNull();
+ assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
+ assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 100)).isNull();
// For feature APIs without an associated feature definition, conditional queries should
// report null, and explicit queries should report runtime-defined versions.
@@ -148,5 +161,12 @@
assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", -1)).isNull();
assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 100)).isNull();
+ assertThat(RoFeatures.maybeHasFeature("", 0)).isNull();
+
+ Map<String, FeatureInfo> compiledFeatures = RoFeatures.getCompileTimeAvailableFeatures();
+ assertThat(compiledFeatures.keySet())
+ .containsExactly(PackageManager.FEATURE_WATCH, PackageManager.FEATURE_WIFI);
+ assertThat(compiledFeatures.get(PackageManager.FEATURE_WATCH).version).isEqualTo(1);
+ assertThat(compiledFeatures.get(PackageManager.FEATURE_WIFI).version).isEqualTo(0);
}
}