Merge "Add automotive_code_coverage keyword to Car host unit tests" into main
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/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/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/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/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 b24c17c..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(
@@ -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/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/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/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/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/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/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/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index 1f98334..c3b7087 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,15 +16,7 @@
package com.android.server.appfunctions;
-import android.annotation.NonNull;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-
import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -41,50 +33,5 @@
/* unit= */ TimeUnit.SECONDS,
/* workQueue= */ new LinkedBlockingQueue<>());
- /** A map of per-user executors for queued work. */
- @GuardedBy("sLock")
- private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>();
-
- private static final Object sLock = new Object();
-
- /**
- * Returns a per-user executor for queued metadata sync request.
- *
- * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence
- * the use of a single thread.
- *
- * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code
- * MetadataSyncAdapter}.
- */
- // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself.
- public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) {
- synchronized (sLock) {
- ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null);
- if (executor == null) {
- executor = Executors.newSingleThreadExecutor();
- mPerUserExecutorsLocked.put(user.getIdentifier(), executor);
- }
- return executor;
- }
- }
-
- /**
- * Shuts down and removes the per-user executor for queued work.
- *
- * <p>This should be called when the user is removed.
- */
- public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user)
- throws InterruptedException {
- ExecutorService executor;
- synchronized (sLock) {
- executor = mPerUserExecutorsLocked.get(user.getIdentifier());
- mPerUserExecutorsLocked.remove(user.getIdentifier());
- }
- if (executor != null) {
- executor.shutdown();
- var unused = executor.awaitTermination(30, TimeUnit.SECONDS);
- }
- }
-
private AppFunctionExecutors() {}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index b4713d9..1e723b5 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -95,12 +95,7 @@
public void onUserStopping(@NonNull TargetUser user) {
Objects.requireNonNull(user);
- try {
- AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle());
- MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
- } catch (InterruptedException e) {
- Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e);
- }
+ MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
}
@Override
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index e29b6e4..d84b205 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -42,6 +42,7 @@
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
@@ -53,7 +54,9 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
/**
* This class implements helper methods for synchronously interacting with AppSearch while
@@ -63,9 +66,15 @@
*/
public class MetadataSyncAdapter {
private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
- private final Executor mSyncExecutor;
+
+ private final ExecutorService mExecutor;
+
private final AppSearchManager mAppSearchManager;
private final PackageManager mPackageManager;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private Future<?> mCurrentSyncTask;
// Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
// by permissions.
@@ -73,12 +82,10 @@
public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
public MetadataSyncAdapter(
- @NonNull Executor syncExecutor,
- @NonNull PackageManager packageManager,
- @NonNull AppSearchManager appSearchManager) {
- mSyncExecutor = Objects.requireNonNull(syncExecutor);
+ @NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
mPackageManager = Objects.requireNonNull(packageManager);
mAppSearchManager = Objects.requireNonNull(appSearchManager);
+ mExecutor = Executors.newSingleThreadExecutor();
}
/**
@@ -97,7 +104,7 @@
AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB)
.build();
AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
- mSyncExecutor.execute(
+ Runnable runnable =
() -> {
try (FutureAppSearchSession staticMetadataSearchSession =
new FutureAppSearchSessionImpl(
@@ -117,10 +124,23 @@
} catch (Exception ex) {
settableSyncStatus.completeExceptionally(ex);
}
- });
+ };
+
+ synchronized (mLock) {
+ if (mCurrentSyncTask != null && !mCurrentSyncTask.isDone()) {
+ var unused = mCurrentSyncTask.cancel(false);
+ }
+ mCurrentSyncTask = mExecutor.submit(runnable);
+ }
+
return settableSyncStatus;
}
+ /** This method shuts down the {@link MetadataSyncAdapter} scheduler. */
+ public void shutDown() {
+ mExecutor.shutdown();
+ }
+
@WorkerThread
@VisibleForTesting
void trySyncAppFunctionMetadataBlocking(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
index f421527..e933ec1 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
@@ -55,10 +55,7 @@
PackageManager perUserPackageManager = userContext.getPackageManager();
if (perUserAppSearchManager != null) {
metadataSyncAdapter =
- new MetadataSyncAdapter(
- AppFunctionExecutors.getPerUserSyncExecutor(user),
- perUserPackageManager,
- perUserAppSearchManager);
+ new MetadataSyncAdapter(perUserPackageManager, perUserAppSearchManager);
sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter);
return metadataSyncAdapter;
}
@@ -74,7 +71,12 @@
*/
public static void removeUserSyncAdapter(UserHandle user) {
synchronized (sLock) {
- sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+ MetadataSyncAdapter metadataSyncAdapter =
+ sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null);
+ if (metadataSyncAdapter != null) {
+ metadataSyncAdapter.shutDown();
+ sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+ }
}
}
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 46d60f9..0c54720 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -454,10 +454,10 @@
@NonNull Associations associations)
throws IOException {
final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS);
+ writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId());
for (AssociationInfo association : associations.getAssociations()) {
writeAssociation(serializer, association);
}
- writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId());
serializer.endTag(null, XML_TAG_ASSOCIATIONS);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 871c320..414a4e6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15304,10 +15304,8 @@
}
psr.setReportedForegroundServiceTypes(fgServiceTypes);
- ProcessChangeItem item = mProcessList.enqueueProcessChangeItemLocked(
- proc.getPid(), proc.info.uid);
- item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
- item.foregroundServiceTypes = fgServiceTypes;
+ mProcessList.enqueueProcessChangeItemLocked(proc.getPid(), proc.info.uid,
+ ProcessChangeItem.CHANGE_FOREGROUND_SERVICES, fgServiceTypes);
}
if (oomAdj) {
updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
diff --git a/services/core/java/com/android/server/am/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/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..19c802b 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
@@ -45,8 +45,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 +77,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 +125,7 @@
touchpadId);
mTouchpadDebugView = new TouchpadDebugView(mContext, touchpadId,
- touchpadHardwareProperties);
+ touchpadHardwareProperties, this::switchVisualisationToTouchpadId);
final WindowManager.LayoutParams mWindowLayoutParams =
mTouchpadDebugView.getWindowLayoutParams();
@@ -150,7 +158,7 @@
* @param deviceId the deviceId of the touchpad that is sending the hardware state
*/
public void updateTouchpadHardwareState(TouchpadHardwareState touchpadHardwareState,
- int deviceId) {
+ int deviceId) {
if (mTouchpadDebugView != null) {
mTouchpadDebugView.updateHardwareState(touchpadHardwareState, 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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3e70d92..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();
}
}
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/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/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index c05c381..bc64e15 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -36,7 +36,6 @@
import com.android.internal.infra.AndroidFuture
import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults
import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.atomic.AtomicBoolean
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,7 +45,6 @@
class MetadataSyncAdapterTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val appSearchManager = context.getSystemService(AppSearchManager::class.java)
- private val testExecutor = MoreExecutors.directExecutor()
private val packageManager = context.packageManager
@Test
@@ -138,8 +136,7 @@
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
runtimeSearchSession.put(putDocumentsRequest).get()
staticSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter =
- MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+ val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -180,8 +177,7 @@
val putDocumentsRequest: PutDocumentsRequest =
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
staticSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter =
- MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+ val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -236,8 +232,7 @@
val putDocumentsRequest: PutDocumentsRequest =
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
runtimeSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter =
- MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+ val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
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/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/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);
}
}