Merge changes Ie1248eb3,Ie6788087 into main
* changes:
Add aconfig flag for wearable hotword
Add aconfig flags for wearable features.
diff --git a/MEMORY_OWNERS b/MEMORY_OWNERS
new file mode 100644
index 0000000..89ce5140
--- /dev/null
+++ b/MEMORY_OWNERS
@@ -0,0 +1,6 @@
+surenb@google.com
+tjmercier@google.com
+kaleshsingh@google.com
+jyescas@google.com
+carlosgalo@google.com
+jji@google.com
diff --git a/core/api/current.txt b/core/api/current.txt
index d7a99d3..a3775b0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9514,8 +9514,8 @@
method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
method @FlaggedApi("android.appwidget.flags.generated_previews") @Nullable public android.widget.RemoteViews getWidgetPreview(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle, int);
method public boolean isRequestPinAppWidgetSupported();
- method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
- method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
+ method public void notifyAppWidgetViewDataChanged(int[], int);
+ method public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
@@ -45225,13 +45225,13 @@
method @FlaggedApi("com.android.internal.telephony.flags.enforce_subscription_user_filter") @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES) public android.telephony.SubscriptionManager createForAllUserProfiles();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context);
- method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
+ method @Nullable public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
method public static int getActiveDataSubscriptionId();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfo(int);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getActiveSubscriptionInfoCount();
method public int getActiveSubscriptionInfoCountMax();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int);
- method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getActiveSubscriptionInfoList();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getActiveSubscriptionInfoList();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public java.util.List<android.telephony.SubscriptionInfo> getAllSubscriptionInfoList();
method @NonNull public java.util.List<android.telephony.SubscriptionInfo> getCompleteActiveSubscriptionInfoList();
method public static int getDefaultDataSubscriptionId();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 37be5c7..783bebd 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -384,6 +384,10 @@
field public static final int DEVICE_INITIAL_SDK_INT;
}
+ public class Environment {
+ method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") @NonNull public static java.io.File getDataSystemDeDirectory();
+ }
+
public class IpcDataCache<Query, Result> {
ctor public IpcDataCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.os.IpcDataCache.QueryHandler<Query,Result>);
method public void disableForCurrentProcess();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d0fdf69..fe97349 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -14590,7 +14590,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getActiveSubscriptionIdList();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public byte[] getAllSimSpecificSettingsForBackup();
- method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
+ method @Nullable public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getCompleteActiveSubscriptionIdList();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 77add41..850f149 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1745,6 +1745,7 @@
method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
+ method public int getMousePointerSpeed();
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
method public void removeUniqueIdAssociation(@NonNull String);
@@ -1754,6 +1755,7 @@
public class InputSettings {
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, @FloatRange(from=0, to=1) float);
+ field public static final int DEFAULT_POINTER_SPEED = 0; // 0x0
}
}
@@ -2882,6 +2884,10 @@
field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
}
+ public static final class Settings.System extends android.provider.Settings.NameValueTable {
+ field public static final String POINTER_SPEED = "pointer_speed";
+ }
+
public static final class Telephony.Sms.Intents {
field public static final String SMS_CARRIER_PROVISION_ACTION = "android.provider.Telephony.SMS_CARRIER_PROVISION";
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8883907..0a34d36 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4649,13 +4649,24 @@
* to turn it off and use a normal notification, as this can be extremely
* disruptive.
*
- * <p>
- * The system UI may choose to display a heads-up notification, instead of
- * launching this intent, while the user is using the device.
- * </p>
* <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
* a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
- * use full screen intents.</p>
+ * use full screen intents. </p>
+ * <p>
+ * Prior to {@link Build.VERSION_CODES#TIRAMISU}, the system may display a
+ * heads up notification (which may display on screen longer than other heads up
+ * notifications), instead of launching the intent, while the user is using the device.
+ * From {@link Build.VERSION_CODES#TIRAMISU},
+ * the system UI will display a heads up notification, instead of launching this intent,
+ * while the user is using the device. This notification will display with emphasized
+ * action buttons. If the posting app holds
+ * {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the heads
+ * up notification will appear persistently until the user dismisses or snoozes it, or
+ * the app cancels it. If the posting app does not hold
+ * {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the notification will
+ * appear as heads up notification even when the screen is locked or turned off, and this
+ * notification will only be persistent for 60 seconds.
+ * </p>
* <p>
* To be launched as a full screen intent, the notification must also be posted to a
* channel with importance level set to IMPORTANCE_HIGH or higher.
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 6204edc..eb82e1f 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -822,18 +822,7 @@
*
* @param appWidgetIds The AppWidget instances to notify of view data changes.
* @param viewId The collection view id.
- * @deprecated The corresponding API
- * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
- * deprecated. Moving forward please use
- * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
- * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
- * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
- * {@link #updateAppWidget(int, RemoteViews)},
- * {@link #updateAppWidget(ComponentName, RemoteViews)},
- * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
- * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
*/
- @Deprecated
public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
if (mService == null) {
return;
@@ -884,18 +873,7 @@
*
* @param appWidgetId The AppWidget instance to notify of view data changes.
* @param viewId The collection view id.
- * @deprecated The corresponding API
- * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
- * deprecated. Moving forward please use
- * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
- * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
- * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
- * {@link #updateAppWidget(int, RemoteViews)},
- * {@link #updateAppWidget(ComponentName, RemoteViews)},
- * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
- * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
*/
- @Deprecated
public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
if (mService == null) {
return;
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index 53dd3bf..fb95608895 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -10,3 +10,4 @@
per-file UserInfo* = file:/MULTIUSER_OWNERS
per-file *UserProperties* = file:/MULTIUSER_OWNERS
per-file *multiuser* = file:/MULTIUSER_OWNERS
+per-file IBackgroundInstallControlService.aidl = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 19bce0b..f31521d 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -199,3 +199,10 @@
bug: "282783453"
is_fixed_read_only: true
}
+
+flag {
+ name: "set_pre_verified_domains"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable pre-verified domains"
+ bug: "307327678"
+}
diff --git a/core/java/android/hardware/camera2/extension/AdvancedExtender.java b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
index a06f017..6653577 100644
--- a/core/java/android/hardware/camera2/extension/AdvancedExtender.java
+++ b/core/java/android/hardware/camera2/extension/AdvancedExtender.java
@@ -57,6 +57,7 @@
private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
private final CameraManager mCameraManager;
+ private CameraUsageTracker mCameraUsageTracker;
private static final String TAG = "AdvancedExtender";
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
@@ -82,6 +83,10 @@
}
}
+ void setCameraUsageTracker(CameraUsageTracker tracker) {
+ mCameraUsageTracker = tracker;
+ }
+
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
public long getMetadataVendorId(@NonNull String cameraId) {
long vendorId = mMetadataVendorIdMap.containsKey(cameraId) ?
@@ -282,7 +287,9 @@
@Override
public ISessionProcessorImpl getSessionProcessor() {
- return AdvancedExtender.this.getSessionProcessor().getSessionProcessorBinder();
+ SessionProcessor processor =AdvancedExtender.this.getSessionProcessor();
+ processor.setCameraUsageTracker(mCameraUsageTracker);
+ return processor.getSessionProcessorBinder();
}
@Override
diff --git a/core/java/android/hardware/camera2/extension/CameraExtensionService.java b/core/java/android/hardware/camera2/extension/CameraExtensionService.java
index 1426d7b..fa0d14a 100644
--- a/core/java/android/hardware/camera2/extension/CameraExtensionService.java
+++ b/core/java/android/hardware/camera2/extension/CameraExtensionService.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.AppOpsManager;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
@@ -29,6 +30,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.camera.flags.Flags;
+interface CameraUsageTracker {
+ void startCameraOperation();
+ void finishCameraOperation();
+}
+
/**
* Base service class that extension service implementations must extend.
*
@@ -38,8 +44,33 @@
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
public abstract class CameraExtensionService extends Service {
private static final String TAG = "CameraExtensionService";
+ private CameraUsageTracker mCameraUsageTracker;
private static Object mLock = new Object();
+ private final class CameraTracker implements CameraUsageTracker {
+
+ private final AppOpsManager mAppOpsService = getApplicationContext().getSystemService(
+ AppOpsManager.class);
+ private final String mPackageName = getPackageName();
+ private final String mAttributionTag = getAttributionTag();
+ private int mUid = getApplicationInfo().uid;
+
+ @Override
+ public void startCameraOperation() {
+ if (mAppOpsService != null) {
+ mAppOpsService.startOp(AppOpsManager.OPSTR_CAMERA, mUid, mPackageName,
+ mAttributionTag, "Camera extensions");
+ }
+ }
+
+ @Override
+ public void finishCameraOperation() {
+ if (mAppOpsService != null) {
+ mAppOpsService.finishOp(AppOpsManager.OPSTR_CAMERA, mUid, mPackageName,
+ mAttributionTag);
+ }
+ }
+ }
@GuardedBy("mLock")
private static IInitializeSessionCallback mInitializeCb = null;
@@ -49,16 +80,22 @@
synchronized (mLock) {
mInitializeCb = null;
}
+ if (mCameraUsageTracker != null) {
+ mCameraUsageTracker.finishCameraOperation();
+ }
}
};
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
- protected CameraExtensionService() {}
+ protected CameraExtensionService() { }
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
@Override
@NonNull
public IBinder onBind(@Nullable Intent intent) {
+ if (mCameraUsageTracker == null) {
+ mCameraUsageTracker = new CameraTracker();
+ }
return new CameraExtensionServiceImpl();
}
@@ -132,8 +169,10 @@
@Override
public IAdvancedExtenderImpl initializeAdvancedExtension(int extensionType)
throws RemoteException {
- return CameraExtensionService.this.onInitializeAdvancedExtension(
- extensionType).getAdvancedExtenderBinder();
+ AdvancedExtender extender = CameraExtensionService.this.onInitializeAdvancedExtension(
+ extensionType);
+ extender.setCameraUsageTracker(mCameraUsageTracker);
+ return extender.getAdvancedExtenderBinder();
}
}
diff --git a/core/java/android/hardware/camera2/extension/SessionProcessor.java b/core/java/android/hardware/camera2/extension/SessionProcessor.java
index 6ed0c14..9c5136b 100644
--- a/core/java/android/hardware/camera2/extension/SessionProcessor.java
+++ b/core/java/android/hardware/camera2/extension/SessionProcessor.java
@@ -76,10 +76,15 @@
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
public abstract class SessionProcessor {
private static final String TAG = "SessionProcessor";
+ private CameraUsageTracker mCameraUsageTracker;
@FlaggedApi(Flags.FLAG_CONCERT_MODE)
protected SessionProcessor() {}
+ void setCameraUsageTracker(CameraUsageTracker tracker) {
+ mCameraUsageTracker = tracker;
+ }
+
/**
* Callback for notifying the status of {@link
* #startCapture} and {@link #startRepeating}.
@@ -379,12 +384,18 @@
@Override
public void onCaptureSessionStart(IRequestProcessorImpl requestProcessor, String statsKey)
throws RemoteException {
+ if (mCameraUsageTracker != null) {
+ mCameraUsageTracker.startCameraOperation();
+ }
SessionProcessor.this.onCaptureSessionStart(
new RequestProcessor(requestProcessor, mVendorId), statsKey);
}
@Override
public void onCaptureSessionEnd() throws RemoteException {
+ if (mCameraUsageTracker != null) {
+ mCameraUsageTracker.finishCameraOperation();
+ }
SessionProcessor.this.onCaptureSessionEnd();
}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 7bea9ae..1f54959 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -67,6 +67,9 @@
KeyCharacterMap getKeyCharacterMap(String layoutDescriptor);
+ // Returns the mouse pointer speed.
+ int getMousePointerSpeed();
+
// Temporarily changes the pointer speed.
void tryPointerSpeed(int speed);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index db992cd..744dfae9 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -28,6 +28,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
@@ -857,6 +858,28 @@
}
/**
+ * Returns the mouse pointer speed.
+ *
+ * <p>The pointer speed is a value between {@link InputSettings#MIN_POINTER_SPEED} and
+ * {@link InputSettings#MAX_POINTER_SPEED}, the default value being
+ * {@link InputSettings#DEFAULT_POINTER_SPEED}.
+ *
+ * <p> Note that while setting the mouse pointer speed, it's possible that the input reader has
+ * only received this value and has not yet completed reconfiguring itself with this value.
+ *
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // TestApi without associated feature.
+ @TestApi
+ public int getMousePointerSpeed() {
+ try {
+ return mIm.getMousePointerSpeed();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Changes the mouse pointer speed temporarily, but does not save the setting.
* <p>
* Requires {@link android.Manifest.permission#SET_POINTER_SPEED}.
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 89fa5fb..54e34ec 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -54,8 +54,8 @@
/**
* Pointer Speed: The default pointer speed (0).
- * @hide
*/
+ @SuppressLint("UnflaggedApi") // TestApi without associated feature.
public static final int DEFAULT_POINTER_SPEED = 0;
/**
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 536ef31..a459aaa 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -17,6 +17,7 @@
package android.os;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -412,7 +413,9 @@
* Returns the base directory for per-user system directory, device encrypted.
* {@hide}
*/
- public static File getDataSystemDeDirectory() {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @FlaggedApi(android.crashrecovery.flags.Flags.FLAG_ENABLE_CRASHRECOVERY)
+ public static @NonNull File getDataSystemDeDirectory() {
return buildPath(getDataDirectory(), "system_de");
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 524b733..76fda06 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6015,8 +6015,10 @@
* +7 = fastest
* @hide
*/
+ @SuppressLint({"NoSettingsProvider", "UnflaggedApi"}) // TestApi without associated feature.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Readable
+ @TestApi
public static final String POINTER_SPEED = "pointer_speed";
/**
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 163dfa2..021bbf7 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -16,7 +16,6 @@
package android.view;
-import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -197,7 +196,6 @@
private Canvas mCanvas;
private int mSaveCount;
- @FloatRange(from = 0.0) float mFrameRate;
@Surface.FrameRateCompatibility int mFrameRateCompatibility;
private final Object[] mNativeWindowLock = new Object[0];
@@ -473,13 +471,13 @@
mLayer.setSurfaceTexture(mSurface);
mSurface.setDefaultBufferSize(getWidth(), getHeight());
mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
- if (Flags.toolkitSetFrameRate()) {
+ if (Flags.toolkitSetFrameRateReadOnly()) {
mSurface.setOnSetFrameRateListener(
(surfaceTexture, frameRate, compatibility, strategy) -> {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.instant(Trace.TRACE_TAG_VIEW, "setFrameRate: " + frameRate);
}
- mFrameRate = frameRate;
+ setRequestedFrameRate(frameRate);
mFrameRateCompatibility = compatibility;
}, mAttachInfo.mHandler);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c27b2b1..6534354 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -26,6 +26,7 @@
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -1013,8 +1014,10 @@
// Used to check if there were any view invalidations in
// the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
private boolean mHasInvalidation = false;
- // Used to check if it is in the touch boosting period.
+ // Used to check if it is in the frame rate boosting period.
private boolean mIsFrameRateBoosting = false;
+ // Used to check if it is in touch boosting period.
+ private boolean mIsTouchBoosting = false;
// Used to check if there is a message in the message queue
// for idleness handling.
private boolean mHasIdledMessage = false;
@@ -1024,6 +1027,9 @@
private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500;
// time for revaluating the idle status before lowering the frame rate.
private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
+ // time for evaluating the interval between current time and
+ // the time when frame rate was set previously.
+ private static final int FRAME_RATE_SETTING_REEVALUATE_TIME = 100;
/*
* the variables below are used to determine whther a dVRR feature should be enabled
@@ -4080,7 +4086,6 @@
setPreferredFrameRate(mPreferredFrameRate);
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- mPreferredFrameRate = 0;
}
private void createSyncIfNeeded() {
@@ -6134,6 +6139,7 @@
private static final int MSG_TOUCH_BOOST_TIMEOUT = 39;
private static final int MSG_CHECK_INVALIDATION_IDLE = 40;
private static final int MSG_REFRESH_POINTER_ICON = 41;
+ private static final int MSG_FRAME_RATE_SETTING = 42;
final class ViewRootHandler extends Handler {
@Override
@@ -6445,11 +6451,12 @@
* Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
*/
mIsFrameRateBoosting = false;
+ mIsTouchBoosting = false;
setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
mLastPreferredFrameRateCategory));
break;
case MSG_CHECK_INVALIDATION_IDLE:
- if (!mHasInvalidation && !mIsFrameRateBoosting) {
+ if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mHasIdledMessage = false;
@@ -6472,6 +6479,10 @@
}
updatePointerIcon(mPointerIconEvent);
break;
+ case MSG_FRAME_RATE_SETTING:
+ mPreferredFrameRate = 0;
+ setPreferredFrameRate(mPreferredFrameRate);
+ break;
}
}
}
@@ -7482,7 +7493,7 @@
// For the variable refresh rate project
if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
// set the frame rate to the maximum value.
- mIsFrameRateBoosting = true;
+ mIsTouchBoosting = true;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
}
/**
@@ -7490,7 +7501,7 @@
* MotionEvent.ACTION_CANCEL is detected.
* Not using ACTION_MOVE to avoid checking and sending messages too frequently.
*/
- if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP
+ if (mIsTouchBoosting && (action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_CANCEL)) {
mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT,
@@ -12234,17 +12245,32 @@
return;
}
- int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning
- ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
+ int frameRateCategory = mIsTouchBoosting
+ ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory;
+
+ // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
+ // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
+ // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
+ // (e.g., Window Initialization).
+ if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+ }
try {
if (mLastPreferredFrameRateCategory != frameRateCategory) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
+ + frameRateCategory);
+ }
mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
frameRateCategory, false).applyAsyncUnsafe();
mLastPreferredFrameRateCategory = frameRateCategory;
}
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate category", e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) {
@@ -12263,12 +12289,19 @@
try {
if (mLastPreferredFrameRate != preferredFrameRate) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(
+ Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRate "
+ + preferredFrameRate);
+ }
mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).applyAsyncUnsafe();
mLastPreferredFrameRate = preferredFrameRate;
}
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate", e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
@@ -12285,7 +12318,7 @@
private boolean shouldSetFrameRate() {
// use toolkitSetFrameRate flag to gate the change
- return mPreferredFrameRate > 0 && sToolkitSetFrameRateReadOnlyFlagValue;
+ return sToolkitSetFrameRateReadOnlyFlagValue;
}
private boolean shouldTouchBoost(int motionEventAction, int windowType) {
@@ -12336,6 +12369,9 @@
}
mHasInvalidation = true;
+ mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+ mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
+ FRAME_RATE_SETTING_REEVALUATE_TIME);
}
/**
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 1de77f6..82067de 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -66,3 +66,14 @@
bug: "314952133"
is_fixed_read_only: true
}
+
+flag {
+ name: "app_compat_refactoring"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether the changes about app compat refactoring are enabled./n"
+ "The goal is to simplify code readability unblocking the implementation of /n"
+ "app compat feature like reachability, animations and others related to/n"
+ "freeform windowing mode."
+ bug: "309593314"
+ is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 627e877..e591327 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -171,11 +171,11 @@
* Register a LockSettingsStateListener
* @param listener The listener to be registered
*/
- public abstract void registerLockSettingsStateListener(ILockSettingsStateListener listener);
+ public abstract void registerLockSettingsStateListener(LockSettingsStateListener listener);
/**
* Unregister a LockSettingsStateListener
* @param listener The listener to be unregistered
*/
- public abstract void unregisterLockSettingsStateListener(ILockSettingsStateListener listener);
+ public abstract void unregisterLockSettingsStateListener(LockSettingsStateListener listener);
}
diff --git a/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl b/core/java/com/android/internal/widget/LockSettingsStateListener.java
similarity index 91%
rename from core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
rename to core/java/com/android/internal/widget/LockSettingsStateListener.java
index 25e3003..869e676 100644
--- a/core/java/com/android/internal/widget/ILockSettingsStateListener.aidl
+++ b/core/java/com/android/internal/widget/LockSettingsStateListener.java
@@ -5,7 +5,7 @@
* 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
+ * 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,
@@ -21,7 +21,7 @@
* state of primary authentication (i.e. PIN/pattern/password).
* @hide
*/
-oneway interface ILockSettingsStateListener {
+public interface LockSettingsStateListener {
/**
* Defines behavior in response to a successful authentication
* @param userId The user Id for the requested authentication
@@ -33,4 +33,4 @@
* @param userId The user Id for the requested authentication
*/
void onAuthenticationFailed(int userId);
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java
index 1ad71da..3237812 100644
--- a/core/tests/coretests/src/android/os/HandlerThreadTest.java
+++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java
@@ -126,7 +126,7 @@
public void testUncaughtExceptionFails() throws Exception {
// For the moment we can only test Ravenwood; on a physical device uncaught exceptions
// are detected, but reported as test failures at a higher level where we can't inspect
- Assume.assumeTrue(RavenwoodRule.isOnRavenwood());
+ Assume.assumeTrue(false); // TODO: re-enable
mThrown.expect(IllegalStateException.class);
final HandlerThread thread = new HandlerThread("HandlerThreadTest");
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 60769c7..52e996c 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -20,6 +20,7 @@
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -669,8 +670,13 @@
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_HIGH_HINT);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
@@ -796,6 +802,33 @@
}
/**
+ * Test votePreferredFrameRate_voteFrameRateTimeOut
+ * If no frame rate is voted in 100 milliseconds, the value of
+ * mPreferredFrameRate should be set to 0.
+ */
+ @Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+ public void votePreferredFrameRate_voteFrameRateTimeOut() throws InterruptedException {
+ final long delay = 200L;
+
+ View view = new View(sContext);
+ attachViewToWindow(view);
+ sInstrumentation.waitForIdleSync();
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ viewRootImpl.votePreferredFrameRate(24);
+ assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
+ view.invalidate();
+ assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
+ });
+
+ Thread.sleep(delay);
+ assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ }
+
+ /**
* Test the logic of infrequent layer:
* - NORMAL for infrequent update: FT2-FT1 > 100 && FT3-FT2 > 100.
* - HIGH/NORMAL based on size for frequent update: (FT3-FT2) + (FT2 - FT1) < 100.
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index dd82fed..50b167e 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -489,7 +489,7 @@
@Surface.ChangeFrameRateStrategy int changeFrameRateStrategy) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "postOnSetFrameRateEventFromNative");
try {
- if (Flags.toolkitSetFrameRate()) {
+ if (Flags.toolkitSetFrameRateReadOnly()) {
SurfaceTexture st = weakSelf.get();
if (st != null) {
Handler handler = st.mOnSetFrameRateHandler;
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 f82212d..7c8fcbb 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
@@ -248,6 +248,12 @@
// Check if count changed
if (prevCount != newCount) {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: visibleTaskCount has changed from %d to %d",
+ prevCount,
+ newCount
+ )
notifyVisibleTaskListeners(displayId, newCount)
}
}
@@ -262,6 +268,11 @@
* Get number of tasks that are marked as visible on given [displayId]
*/
fun getVisibleTaskCount(displayId: Int): Int {
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: visibleTaskCount= %d",
+ displayData[displayId]?.visibleTasks?.size ?: 0
+ )
return displayData[displayId]?.visibleTasks?.size ?: 0
}
@@ -290,6 +301,10 @@
taskId
)
freeformTasksInZOrder.remove(taskId)
+ KtProtoLog.d(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTaskRepo: remaining freeform tasks: " + freeformTasksInZOrder.toDumpString()
+ )
}
/**
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 72ddecc..3d7e559 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -1,6 +1,13 @@
package: "com.android.graphics.hwui.flags"
flag {
+ name: "clip_shader"
+ namespace: "core_graphics"
+ description: "API for canvas shader clipping operations"
+ bug: "280116960"
+}
+
+flag {
name: "matrix_44"
namespace: "core_graphics"
description: "API for 4x4 matrix and related canvas functions"
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 9742d46..9f94ef9 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -98,12 +98,12 @@
field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_DISABLE = 0; // 0x0
- field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -1; // 0xffffffff
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -2147483648; // 0x80000000
field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_A = 1; // 0x1
field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_B = 2; // 0x2
field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_F = 4; // 0x4
field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_DISABLE = 0; // 0x0
- field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -1; // 0xffffffff
+ field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -2147483648; // 0x80000000
field public static final int FLAG_READER_NFC_A = 1; // 0x1
field public static final int FLAG_READER_NFC_B = 2; // 0x2
field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 55506a1..5b917a1 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -416,18 +416,18 @@
/**
* Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
* <p>
- * Setting this flag makes listening to use current flags.
+ * Setting this flag makes listening to keep the current technology configuration.
*/
@FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
- public static final int FLAG_LISTEN_KEEP = -1;
+ public static final int FLAG_LISTEN_KEEP = 0x80000000;
/**
* Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
* <p>
- * Setting this flag makes polling to use current flags.
+ * Setting this flag makes polling to keep the current technology configuration.
*/
@FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
- public static final int FLAG_READER_KEEP = -1;
+ public static final int FLAG_READER_KEEP = 0x80000000;
/** @hide */
public static final int FLAG_USE_ALL_TECH = 0xff;
@@ -1785,6 +1785,8 @@
*
* Use {@link #FLAG_READER_KEEP} to keep current polling technology.
* Use {@link #FLAG_LISTEN_KEEP} to keep current listenig technology.
+ * (if the _KEEP flag is specified the other technology flags shouldn't be set
+ * and are quietly ignored otherwise).
* Use {@link #FLAG_READER_DISABLE} to disable polling.
* Use {@link #FLAG_LISTEN_DISABLE} to disable listening.
* Also refer to {@link #resetDiscoveryTechnology(Activity)} to restore these changes.
@@ -1816,6 +1818,15 @@
@FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
public void setDiscoveryTechnology(@NonNull Activity activity,
@PollTechnology int pollTechnology, @ListenTechnology int listenTechnology) {
+
+ // A special treatment of the _KEEP flags
+ if ((listenTechnology & FLAG_LISTEN_KEEP) != 0) {
+ listenTechnology = -1;
+ }
+ if ((pollTechnology & FLAG_READER_KEEP) != 0) {
+ pollTechnology = -1;
+ }
+
if (listenTechnology == FLAG_LISTEN_DISABLE) {
synchronized (sLock) {
if (!sHasNfcFeature) {
@@ -1842,10 +1853,10 @@
}
/**
- * Restore the poll/listen technologies of NFC controller,
+ * Restore the poll/listen technologies of NFC controller to its default state,
* which were changed by {@link #setDiscoveryTechnology(Activity , int , int)}
*
- * @param activity The Activity that requests to changed technologies.
+ * @param activity The Activity that requested to change technologies.
*/
@FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
index 5636266..572a669 100644
--- a/packages/CrashRecovery/aconfig/flags.aconfig
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -6,4 +6,11 @@
description: "Feature flag for recoverability detection"
bug: "310236690"
is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+ name: "enable_crashrecovery"
+ namespace: "crashrecovery"
+ description: "Enables various dependencies of crashrecovery module"
+ bug: "289203818"
+}
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index fd2f9bd..bab6781 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -7,3 +7,13 @@
bug: "314812750"
}
+flag {
+ name: "enable_cached_bluetooth_device_dedup"
+ namespace: "bluetooth"
+ description: "Enable dedup in CachedBluetoothDevice"
+ bug: "319197962"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index c97445f..647fcb9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -16,6 +16,8 @@
package com.android.settingslib.bluetooth;
+import static com.android.settingslib.flags.Flags.enableCachedBluetoothDeviceDedup;
+
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -377,6 +379,10 @@
cachedDevice = mDeviceManager.addDevice(device);
}
+ if (enableCachedBluetoothDeviceDedup() && bondState == BluetoothDevice.BOND_BONDED) {
+ mDeviceManager.removeDuplicateInstanceForIdentityAddress(device);
+ }
+
for (BluetoothCallback callback : mCallbacks) {
callback.onDeviceBondStateChanged(cachedDevice, bondState);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 89fe268..32eec7e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -340,6 +340,20 @@
}
}
+ synchronized void removeDuplicateInstanceForIdentityAddress(BluetoothDevice device) {
+ String identityAddress = device.getIdentityAddress();
+ if (identityAddress == null || identityAddress.equals(device.getAddress())) {
+ return;
+ }
+ mCachedDevices.removeIf(d -> {
+ boolean shouldRemove = d.getDevice().getAddress().equals(identityAddress);
+ if (shouldRemove) {
+ Log.d(TAG, "Remove instance for identity address " + d);
+ }
+ return shouldRemove;
+ });
+ }
+
public synchronized boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice
cachedDevice, int state, int profileId) {
if (profileId == BluetoothProfile.HEARING_AID) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 1d2f790..6ee403d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -21,6 +21,7 @@
import android.annotation.CallbackExecutor;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeBroadcast;
@@ -62,6 +63,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
/**
* LocalBluetoothLeBroadcast provides an interface between the Settings app and the functionality of
@@ -88,6 +90,8 @@
Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY),
};
+ private final Context mContext;
+ private final CachedBluetoothDeviceManager mDeviceManager;
private BluetoothLeBroadcast mServiceBroadcast;
private BluetoothLeBroadcastAssistant mServiceBroadcastAssistant;
private BluetoothLeAudioContentMetadata mBluetoothLeAudioContentMetadata;
@@ -256,8 +260,19 @@
private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
new BluetoothLeBroadcastAssistant.Callback() {
@Override
- public void onSourceAdded(
- @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+ public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onSourceAdded(), sink = "
+ + sink
+ + ", reason = "
+ + reason
+ + ", sourceId = "
+ + sourceId);
+ }
+ updateFallbackActiveDeviceIfNeeded();
+ }
@Override
public void onSearchStarted(int reason) {}
@@ -301,6 +316,7 @@
+ ", sourceId = "
+ sourceId);
}
+ updateFallbackActiveDeviceIfNeeded();
}
@Override
@@ -348,7 +364,9 @@
}
}
- LocalBluetoothLeBroadcast(Context context) {
+ LocalBluetoothLeBroadcast(Context context, CachedBluetoothDeviceManager deviceManager) {
+ mContext = context;
+ mDeviceManager = deviceManager;
mExecutor = Executors.newSingleThreadExecutor();
mBuilder = new BluetoothLeAudioContentMetadata.Builder();
mContentResolver = context.getContentResolver();
@@ -430,49 +448,6 @@
mServiceBroadcast.startBroadcast(settings);
}
- /**
- * Start the private Broadcast for personal audio sharing or qr code sharing.
- *
- * <p>The broadcast will use random string for both broadcast name and subgroup program info;
- * The broadcast will use random string for broadcast code; The broadcast will only have one
- * subgroup due to system limitation; The subgroup language will be null.
- *
- * <p>If the system started the LE Broadcast, then the system calls the corresponding callback
- * {@link BluetoothLeBroadcast.Callback}.
- */
- public void startPrivateBroadcast(int quality) {
- mNewAppSourceName = "Sharing audio";
- if (mServiceBroadcast == null) {
- Log.d(TAG, "The BluetoothLeBroadcast is null when starting the private broadcast.");
- return;
- }
- if (mServiceBroadcast.getAllBroadcastMetadata().size()
- >= mServiceBroadcast.getMaximumNumberOfBroadcasts()) {
- Log.d(TAG, "Skip starting the broadcast due to number limit.");
- return;
- }
- String programInfo = getProgramInfo();
- if (DEBUG) {
- Log.d(TAG, "startBroadcast: language = null ,programInfo = " + programInfo);
- }
- // Current broadcast framework only support one subgroup
- BluetoothLeBroadcastSubgroupSettings subgroupSettings =
- buildBroadcastSubgroupSettings(
- /* language= */ null,
- programInfo,
- /* improveCompatibility= */
- BluetoothLeBroadcastSubgroupSettings.QUALITY_STANDARD == quality);
- BluetoothLeBroadcastSettings settings =
- buildBroadcastSettings(
- true, // TODO: set to false after framework fix
- TextUtils.isEmpty(programInfo) ? null : programInfo,
- (mBroadcastCode != null && mBroadcastCode.length > 0)
- ? mBroadcastCode
- : null,
- ImmutableList.of(subgroupSettings));
- mServiceBroadcast.startBroadcast(settings);
- }
-
private BluetoothLeBroadcastSettings buildBroadcastSettings(
boolean isPublic,
@Nullable String broadcastName,
@@ -1027,4 +1002,80 @@
}
}
}
+
+ /** Update fallback active device if needed. */
+ public void updateFallbackActiveDeviceIfNeeded() {
+ if (!isEnabled(null)) {
+ Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no ongoing broadcast");
+ return;
+ }
+ if (mServiceBroadcastAssistant == null) {
+ Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null");
+ return;
+ }
+ List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
+ List<BluetoothDevice> devicesInSharing =
+ connectedDevices.stream()
+ .filter(
+ bluetoothDevice -> {
+ List<BluetoothLeBroadcastReceiveState> sourceList =
+ mServiceBroadcastAssistant.getAllSources(
+ bluetoothDevice);
+ return !sourceList.isEmpty();
+ })
+ .collect(Collectors.toList());
+ if (devicesInSharing.isEmpty()) {
+ Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no sinks in broadcast");
+ return;
+ }
+ List<BluetoothDevice> devices =
+ BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices();
+ BluetoothDevice targetDevice = null;
+ // Find the earliest connected device in sharing session.
+ int targetDeviceIdx = -1;
+ for (BluetoothDevice device : devicesInSharing) {
+ if (devices.contains(device)) {
+ int idx = devices.indexOf(device);
+ if (idx > targetDeviceIdx) {
+ targetDeviceIdx = idx;
+ targetDevice = device;
+ }
+ }
+ }
+ if (targetDevice == null) {
+ Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, target is null");
+ return;
+ }
+ Log.d(
+ TAG,
+ "updateFallbackActiveDeviceIfNeeded, set active device: "
+ + targetDevice.getAnonymizedAddress());
+ CachedBluetoothDevice targetCachedDevice = mDeviceManager.findDevice(targetDevice);
+ if (targetCachedDevice == null) {
+ Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find cached bt device");
+ return;
+ }
+ int fallbackActiveGroupId = getFallbackActiveGroupId();
+ if (targetCachedDevice.getGroupId() == fallbackActiveGroupId) {
+ Log.d(
+ TAG,
+ "Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
+ + fallbackActiveGroupId);
+ return;
+ }
+ targetCachedDevice.setActive();
+ }
+
+ private boolean isDecryptedSource(BluetoothLeBroadcastReceiveState state) {
+ return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
+ && state.getBigEncryptionState()
+ == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING;
+ }
+
+ private int getFallbackActiveGroupId() {
+ return Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ "bluetooth_le_broadcast_fallback_active_group_id",
+ BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManagerExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManagerExt.kt
new file mode 100644
index 0000000..5dc0237
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManagerExt.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.bluetooth
+
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** Returns a [Flow] that emits a [Unit] whenever the headset audio mode changes. */
+val LocalBluetoothManager.headsetAudioModeChanges: Flow<Unit>
+ get() {
+ return callbackFlow {
+ val callback =
+ object : BluetoothCallback {
+ override fun onAudioModeChanged() {
+ launch { send(Unit) }
+ }
+ }
+
+ eventManager.registerCallback(callback)
+ awaitClose { eventManager.unregisterCallback(callback) }
+ }
+ }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 119aef6..79e4c37 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -257,7 +257,7 @@
if (DEBUG) {
Log.d(TAG, "Adding local LE_AUDIO_BROADCAST profile");
}
- mLeAudioBroadcast = new LocalBluetoothLeBroadcast(mContext);
+ mLeAudioBroadcast = new LocalBluetoothLeBroadcast(mContext, mDeviceManager);
// no event handler for the LE boradcast.
mProfileNameMap.put(LocalBluetoothLeBroadcast.NAME, mLeAudioBroadcast);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
new file mode 100644
index 0000000..1597b77
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
@@ -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.settingslib.volume.data.repository
+
+import com.android.settingslib.media.LocalMediaManager
+import com.android.settingslib.media.MediaDevice
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository providing data about connected media devices. */
+interface LocalMediaRepository {
+
+ /** Available devices list */
+ val mediaDevices: StateFlow<Collection<MediaDevice>>
+
+ /** Currently connected media device */
+ val currentConnectedDevice: StateFlow<MediaDevice?>
+}
+
+class LocalMediaRepositoryImpl(
+ private val localMediaManager: LocalMediaManager,
+ coroutineScope: CoroutineScope,
+ backgroundContext: CoroutineContext,
+) : LocalMediaRepository {
+
+ private val deviceUpdates: Flow<DevicesUpdate> = callbackFlow {
+ val callback =
+ object : LocalMediaManager.DeviceCallback {
+ override fun onDeviceListUpdate(newDevices: List<MediaDevice>?) {
+ trySend(DevicesUpdate.DeviceListUpdate(newDevices ?: emptyList()))
+ }
+
+ override fun onSelectedDeviceStateChanged(
+ device: MediaDevice?,
+ state: Int,
+ ) {
+ trySend(DevicesUpdate.SelectedDeviceStateChanged)
+ }
+
+ override fun onDeviceAttributesChanged() {
+ trySend(DevicesUpdate.DeviceAttributesChanged)
+ }
+ }
+ localMediaManager.registerCallback(callback)
+ localMediaManager.startScan()
+
+ awaitClose {
+ localMediaManager.stopScan()
+ localMediaManager.unregisterCallback(callback)
+ }
+ }
+
+ override val mediaDevices: StateFlow<Collection<MediaDevice>> =
+ deviceUpdates
+ .mapNotNull {
+ if (it is DevicesUpdate.DeviceListUpdate) {
+ it.newDevices ?: emptyList()
+ } else {
+ null
+ }
+ }
+ .flowOn(backgroundContext)
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
+
+ override val currentConnectedDevice: StateFlow<MediaDevice?> =
+ deviceUpdates
+ .map { localMediaManager.currentConnectedDevice }
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ localMediaManager.currentConnectedDevice
+ )
+
+ private sealed interface DevicesUpdate {
+
+ data class DeviceListUpdate(val newDevices: List<MediaDevice>?) : DevicesUpdate
+
+ data object SelectedDeviceStateChanged : DevicesUpdate
+
+ data object DeviceAttributesChanged : DevicesUpdate
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
new file mode 100644
index 0000000..93aa90d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.volume.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioManager
+import android.media.session.MediaController
+import android.media.session.MediaSessionManager
+import android.media.session.PlaybackState
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.headsetAudioModeChanges
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Provides controllers for currently active device media sessions. */
+interface MediaControllerRepository {
+
+ /** Current [MediaController]. Null is emitted when there is no active [MediaController]. */
+ val activeMediaController: StateFlow<MediaController?>
+}
+
+class MediaControllerRepositoryImpl(
+ private val context: Context,
+ private val mediaSessionManager: MediaSessionManager,
+ localBluetoothManager: LocalBluetoothManager?,
+ coroutineScope: CoroutineScope,
+ backgroundContext: CoroutineContext,
+) : MediaControllerRepository {
+
+ private val devicesChanges: Flow<Unit> =
+ callbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (AudioManager.STREAM_DEVICES_CHANGED_ACTION == intent?.action) {
+ launch { send(Unit) }
+ }
+ }
+ }
+ context.registerReceiver(
+ receiver,
+ IntentFilter(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
+ )
+
+ awaitClose { context.unregisterReceiver(receiver) }
+ }
+ .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
+
+ override val activeMediaController: StateFlow<MediaController?> =
+ combine(
+ localBluetoothManager?.headsetAudioModeChanges?.onStart { emit(Unit) }
+ ?: emptyFlow(),
+ devicesChanges.onStart { emit(Unit) },
+ ) { _, _ ->
+ getActiveLocalMediaController()
+ }
+ .flowOn(backgroundContext)
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+
+ private fun getActiveLocalMediaController(): MediaController? {
+ var localController: MediaController? = null
+ val remoteMediaSessionLists: MutableList<String> = ArrayList()
+ for (controller in mediaSessionManager.getActiveSessions(null)) {
+ val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
+ val playbackState = controller.playbackState ?: continue
+ if (inactivePlaybackStates.contains(playbackState.state)) {
+ continue
+ }
+ when (playbackInfo.playbackType) {
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
+ if (localController?.packageName.equals(controller.packageName)) {
+ localController = null
+ }
+ if (!remoteMediaSessionLists.contains(controller.packageName)) {
+ remoteMediaSessionLists.add(controller.packageName)
+ }
+ }
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
+ if (
+ localController == null &&
+ !remoteMediaSessionLists.contains(controller.packageName)
+ ) {
+ localController = controller
+ }
+ }
+ }
+ }
+ return localController
+ }
+
+ private companion object {
+ val inactivePlaybackStates =
+ setOf(PlaybackState.STATE_STOPPED, PlaybackState.STATE_NONE, PlaybackState.STATE_ERROR)
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
new file mode 100644
index 0000000..d106bce
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.volume.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.media.LocalMediaManager
+import com.android.settingslib.media.MediaDevice
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class LocalMediaRepositoryImplTest {
+
+ @Mock private lateinit var localMediaManager: LocalMediaManager
+ @Mock private lateinit var mediaDevice1: MediaDevice
+ @Mock private lateinit var mediaDevice2: MediaDevice
+
+ @Captor
+ private lateinit var deviceCallbackCaptor: ArgumentCaptor<LocalMediaManager.DeviceCallback>
+
+ private val testScope = TestScope()
+
+ private lateinit var underTest: LocalMediaRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ LocalMediaRepositoryImpl(
+ localMediaManager,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ )
+ }
+
+ @Test
+ fun mediaDevices_areUpdated() {
+ testScope.runTest {
+ var mediaDevices: Collection<MediaDevice>? = null
+ underTest.mediaDevices.onEach { mediaDevices = it }.launchIn(backgroundScope)
+ runCurrent()
+ verify(localMediaManager).registerCallback(deviceCallbackCaptor.capture())
+ deviceCallbackCaptor.value.onDeviceListUpdate(listOf(mediaDevice1, mediaDevice2))
+ runCurrent()
+
+ assertThat(mediaDevices).hasSize(2)
+ assertThat(mediaDevices).contains(mediaDevice1)
+ assertThat(mediaDevices).contains(mediaDevice2)
+ }
+ }
+
+ @Test
+ fun deviceListUpdated_currentConnectedDeviceUpdated() {
+ testScope.runTest {
+ var currentConnectedDevice: MediaDevice? = null
+ underTest.currentConnectedDevice
+ .onEach { currentConnectedDevice = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ `when`(localMediaManager.currentConnectedDevice).thenReturn(mediaDevice1)
+ verify(localMediaManager).registerCallback(deviceCallbackCaptor.capture())
+ deviceCallbackCaptor.value.onDeviceListUpdate(listOf(mediaDevice1, mediaDevice2))
+ runCurrent()
+
+ assertThat(currentConnectedDevice).isEqualTo(mediaDevice1)
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
new file mode 100644
index 0000000..f07b1bff
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.volume.data.repository
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.media.AudioManager
+import android.media.session.MediaController
+import android.media.session.MediaController.PlaybackInfo
+import android.media.session.MediaSessionManager
+import android.media.session.PlaybackState
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothCallback
+import com.android.settingslib.bluetooth.BluetoothEventManager
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class MediaControllerRepositoryImplTest {
+
+ @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ @Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothCallback>
+
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var mediaSessionManager: MediaSessionManager
+ @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+ @Mock private lateinit var eventManager: BluetoothEventManager
+
+ @Mock private lateinit var stoppedMediaController: MediaController
+ @Mock private lateinit var statelessMediaController: MediaController
+ @Mock private lateinit var errorMediaController: MediaController
+ @Mock private lateinit var remoteMediaController: MediaController
+ @Mock private lateinit var localMediaController: MediaController
+
+ @Mock private lateinit var remotePlaybackInfo: PlaybackInfo
+ @Mock private lateinit var localPlaybackInfo: PlaybackInfo
+
+ private val testScope = TestScope()
+
+ private lateinit var underTest: MediaControllerRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(localBluetoothManager.eventManager).thenReturn(eventManager)
+
+ `when`(stoppedMediaController.playbackState).thenReturn(stateStopped)
+ `when`(stoppedMediaController.packageName).thenReturn("test.pkg.stopped")
+ `when`(statelessMediaController.playbackState).thenReturn(stateNone)
+ `when`(statelessMediaController.packageName).thenReturn("test.pkg.stateless")
+ `when`(errorMediaController.playbackState).thenReturn(stateError)
+ `when`(errorMediaController.packageName).thenReturn("test.pkg.error")
+ `when`(remoteMediaController.playbackState).thenReturn(statePlaying)
+ `when`(remoteMediaController.playbackInfo).thenReturn(remotePlaybackInfo)
+ `when`(remoteMediaController.packageName).thenReturn("test.pkg.remote")
+ `when`(localMediaController.playbackState).thenReturn(statePlaying)
+ `when`(localMediaController.playbackInfo).thenReturn(localPlaybackInfo)
+ `when`(localMediaController.packageName).thenReturn("test.pkg.local")
+
+ `when`(remotePlaybackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+ `when`(localPlaybackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL)
+
+ underTest =
+ MediaControllerRepositoryImpl(
+ context,
+ mediaSessionManager,
+ localBluetoothManager,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ )
+ }
+
+ @Test
+ fun playingMediaDevicesAvailable_sessionIsActive() {
+ testScope.runTest {
+ `when`(mediaSessionManager.getActiveSessions(any()))
+ .thenReturn(
+ listOf(
+ stoppedMediaController,
+ statelessMediaController,
+ errorMediaController,
+ remoteMediaController,
+ localMediaController
+ )
+ )
+ var mediaController: MediaController? = null
+ underTest.activeMediaController
+ .onEach { mediaController = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ triggerDevicesChange()
+ triggerOnAudioModeChanged()
+ runCurrent()
+
+ assertThat(mediaController).isSameInstanceAs(localMediaController)
+ }
+ }
+
+ @Test
+ fun noPlayingMediaDevicesAvailable_sessionIsInactive() {
+ testScope.runTest {
+ `when`(mediaSessionManager.getActiveSessions(any()))
+ .thenReturn(
+ listOf(
+ stoppedMediaController,
+ statelessMediaController,
+ errorMediaController,
+ )
+ )
+ var mediaController: MediaController? = null
+ underTest.activeMediaController
+ .onEach { mediaController = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ triggerDevicesChange()
+ triggerOnAudioModeChanged()
+ runCurrent()
+
+ assertThat(mediaController).isNull()
+ }
+ }
+
+ private fun triggerDevicesChange() {
+ verify(context).registerReceiver(receiverCaptor.capture(), any())
+ receiverCaptor.value.onReceive(context, Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION))
+ }
+
+ private fun triggerOnAudioModeChanged() {
+ verify(eventManager).registerCallback(callbackCaptor.capture())
+ callbackCaptor.value.onAudioModeChanged()
+ }
+
+ private companion object {
+ val statePlaying =
+ PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0, 0f).build()
+ val stateError = PlaybackState.Builder().setState(PlaybackState.STATE_ERROR, 0, 0f).build()
+ val stateStopped =
+ PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0, 0f).build()
+ val stateNone = PlaybackState.Builder().setState(PlaybackState.STATE_NONE, 0, 0f).build()
+ }
+}
diff --git a/packages/SettingsLib/tests/unit/Android.bp b/packages/SettingsLib/tests/unit/Android.bp
index e2eda4f..6d6e2ff 100644
--- a/packages/SettingsLib/tests/unit/Android.bp
+++ b/packages/SettingsLib/tests/unit/Android.bp
@@ -32,7 +32,5 @@
"androidx.test.ext.junit",
"androidx.test.runner",
"truth",
- "kotlinx_coroutines_test",
- "mockito-target-minus-junit4",
],
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 6f3c88f..ae71cec 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -612,12 +612,19 @@
String packageName) {
List<String> changedKeys = new ArrayList<>();
final Iterator<Map.Entry<String, Setting>> iterator = mSettings.entrySet().iterator();
+ int index = prefix.lastIndexOf('/');
+ String namespace = index < 0 ? "" : prefix.substring(0, index);
+ Map<String, String> trunkFlagMap =
+ mNamespaceDefaults.get(namespace);
// Delete old keys with the prefix that are not part of the new set.
+ // trunk flags will not be configured with restricted propagation
+ // trunk flags will be explicitly set, so not removing them here
while (iterator.hasNext()) {
Map.Entry<String, Setting> entry = iterator.next();
final String key = entry.getKey();
final Setting oldState = entry.getValue();
- if (key != null && key.startsWith(prefix) && !keyValues.containsKey(key)) {
+ if (key != null && (trunkFlagMap == null || !trunkFlagMap.containsKey(key))
+ && key.startsWith(prefix) && !keyValues.containsKey(key)) {
iterator.remove();
FrameworkStatsLog.write(FrameworkStatsLog.SETTING_CHANGED, key,
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 13ee196..9a34d6f 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -27,6 +27,8 @@
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
@@ -112,6 +114,12 @@
dialogFactory: BouncerDialogFactory,
): View = throwComposeUnavailableError()
+ override fun createLockscreen(
+ context: Context,
+ viewModel: LockscreenContentViewModel,
+ blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
+ ): View = throwComposeUnavailableError()
+
private fun throwComposeUnavailableError(): Nothing {
error(
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index 725aef2..fc3912e 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -16,6 +16,16 @@
package com.android.systemui.scene
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
import dagger.Module
+import dagger.Provides
-@Module interface LockscreenSceneModule
+@Module
+interface LockscreenSceneModule {
+ companion object {
+ @Provides
+ fun providesLockscreenBlueprints(): Set<LockscreenSceneBlueprint> {
+ return emptySet()
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index f05c7f3..4cc7332 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -22,6 +22,8 @@
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -40,6 +42,10 @@
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.keyboard.stickykeys.ui.view.createStickyKeyIndicatorView
import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.composable.LockscreenContent
+import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -211,4 +217,19 @@
setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
}
}
+
+ override fun createLockscreen(
+ context: Context,
+ viewModel: LockscreenContentViewModel,
+ blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
+ ): View {
+ val sceneBlueprints =
+ blueprints.mapNotNull { it as? ComposableLockscreenSceneBlueprint }.toSet()
+ return ComposeView(context).apply {
+ setContent {
+ LockscreenContent(viewModel = viewModel, blueprints = sceneBlueprints)
+ .Content(modifier = Modifier.fillMaxSize())
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index cbf2496..f5dc154 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -20,8 +20,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardViewConfigurator
import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.composable.LockscreenScene
import com.android.systemui.keyguard.ui.composable.LockscreenSceneBlueprintModule
+import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
import com.android.systemui.scene.shared.model.Scene
import dagger.Binds
import dagger.Module
@@ -51,5 +53,12 @@
): () -> View {
return { configurator.get().getKeyguardRootView() }
}
+
+ @Provides
+ fun providesLockscreenBlueprints(
+ blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>
+ ): Set<LockscreenSceneBlueprint> {
+ return blueprints
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 2cb0034..b5499b7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -25,7 +25,7 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.transitions
-import com.android.systemui.keyguard.ui.composable.blueprint.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import javax.inject.Inject
@@ -39,10 +39,10 @@
@Inject
constructor(
private val viewModel: LockscreenContentViewModel,
- private val blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
+ private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
) {
- private val sceneKeyByBlueprint: Map<LockscreenSceneBlueprint, SceneKey> by lazy {
+ private val sceneKeyByBlueprint: Map<ComposableLockscreenSceneBlueprint, SceneKey> by lazy {
blueprints.associateWith { blueprint -> SceneKey(blueprint.id) }
}
private val sceneKeyByBlueprintId: Map<String, SceneKey> by lazy {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 86124c6..6b210af 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -36,7 +36,7 @@
@Inject
constructor(
private val viewModel: LockscreenContentViewModel,
-) : LockscreenSceneBlueprint {
+) : ComposableLockscreenSceneBlueprint {
override val id: String = "communal"
@@ -59,5 +59,5 @@
@Module
interface CommunalBlueprintModule {
- @Binds @IntoSet fun blueprint(blueprint: CommunalBlueprint): LockscreenSceneBlueprint
+ @Binds @IntoSet fun blueprint(blueprint: CommunalBlueprint): ComposableLockscreenSceneBlueprint
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
similarity index 87%
rename from packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
rename to packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
index 6d9cba4..cb73983 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ComposableLockscreenSceneBlueprint.kt
@@ -19,13 +19,10 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
/** Defines interface for classes that can render the content for a specific blueprint/layout. */
-interface LockscreenSceneBlueprint {
-
- /** The ID that uniquely identifies this blueprint across all other blueprints. */
- val id: String
-
+interface ComposableLockscreenSceneBlueprint : LockscreenSceneBlueprint {
/** Renders the content of this blueprint. */
@Composable fun SceneScope.Content(modifier: Modifier)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index bf02d8a..a07ab4a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -20,10 +20,12 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
@@ -38,6 +40,7 @@
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -61,7 +64,7 @@
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
private val clockInteractor: KeyguardClockInteractor,
-) : LockscreenSceneBlueprint {
+) : ComposableLockscreenSceneBlueprint {
override val id: String = "default"
@@ -84,6 +87,7 @@
with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
with(clockSection) {
SmallClock(
+ burnInParams = burnIn.parameters,
onTopChanged = burnIn.onSmallClockTopChanged,
modifier = Modifier.fillMaxWidth(),
)
@@ -95,7 +99,13 @@
modifier =
Modifier.fillMaxWidth()
.padding(
- top = { viewModel.getSmartSpacePaddingTop(resources) }
+ top = { viewModel.getSmartSpacePaddingTop(resources) },
+ )
+ .padding(
+ bottom =
+ dimensionResource(
+ R.dimen.keyguard_status_view_bottom_margin
+ ),
),
)
}
@@ -214,5 +224,5 @@
@Module
interface DefaultBlueprintModule {
- @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): LockscreenSceneBlueprint
+ @Binds @IntoSet fun blueprint(blueprint: DefaultBlueprint): ComposableLockscreenSceneBlueprint
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index d0aa444..b035e42 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -20,10 +20,12 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
@@ -38,6 +40,7 @@
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -61,7 +64,7 @@
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
private val clockInteractor: KeyguardClockInteractor,
-) : LockscreenSceneBlueprint {
+) : ComposableLockscreenSceneBlueprint {
override val id: String = "shortcuts-besides-udfps"
@@ -86,6 +89,7 @@
SmallClock(
onTopChanged = burnIn.onSmallClockTopChanged,
modifier = Modifier.fillMaxWidth(),
+ burnInParams = burnIn.parameters,
)
}
with(smartSpaceSection) {
@@ -96,6 +100,12 @@
Modifier.fillMaxWidth()
.padding(
top = { viewModel.getSmartSpacePaddingTop(resources) }
+ )
+ .padding(
+ bottom =
+ dimensionResource(
+ R.dimen.keyguard_status_view_bottom_margin
+ )
),
)
}
@@ -222,5 +232,5 @@
interface ShortcutsBesideUdfpsBlueprintModule {
@Binds
@IntoSet
- fun blueprint(blueprint: ShortcutsBesideUdfpsBlueprint): LockscreenSceneBlueprint
+ fun blueprint(blueprint: ShortcutsBesideUdfpsBlueprint): ComposableLockscreenSceneBlueprint
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index 616a7b4..660fc5a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -72,7 +72,7 @@
private val settingsMenuSection: SettingsMenuSection,
private val clockInteractor: KeyguardClockInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
-) : LockscreenSceneBlueprint {
+) : ComposableLockscreenSceneBlueprint {
override val id: String = "split-shade"
@@ -109,7 +109,14 @@
.padding(
top = {
viewModel.getSmartSpacePaddingTop(resources)
- }
+ },
+ )
+ .padding(
+ bottom =
+ dimensionResource(
+ R.dimen
+ .keyguard_status_view_bottom_margin
+ )
),
)
}
@@ -237,5 +244,7 @@
@Module
interface SplitShadeBlueprintModule {
- @Binds @IntoSet fun blueprint(blueprint: SplitShadeBlueprint): LockscreenSceneBlueprint
+ @Binds
+ @IntoSet
+ fun blueprint(blueprint: SplitShadeBlueprint): ComposableLockscreenSceneBlueprint
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
index 8f21879..fa07baf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -33,7 +33,10 @@
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.customization.R as customizationR
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import javax.inject.Inject
@@ -42,10 +45,12 @@
constructor(
private val viewModel: KeyguardClockViewModel,
private val clockInteractor: KeyguardClockInteractor,
+ private val aodBurnInViewModel: AodBurnInViewModel,
) {
@Composable
fun SceneScope.SmallClock(
+ burnInParams: BurnInParameters,
onTopChanged: (top: Float?) -> Unit,
modifier: Modifier = Modifier,
) {
@@ -89,7 +94,11 @@
dimensionResource(customizationR.dimen.clock_padding_start)
)
.padding(top = { viewModel.getSmallClockTopMargin(view.context) })
- .onTopPlacementChanged(onTopChanged),
+ .onTopPlacementChanged(onTopChanged)
+ .burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ ),
update = {
val newClockView = checkNotNull(currentClock).smallClock.view
it.removeAllViews()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 5e27d82..6a8da10 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -19,6 +19,11 @@
import android.content.Context
import android.view.ViewGroup
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
@@ -40,56 +45,82 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DisposableHandle
@SysUISingleton
class NotificationSection
@Inject
constructor(
- @Application context: Context,
+ @Application private val context: Context,
private val viewModel: NotificationsPlaceholderViewModel,
- controller: NotificationStackScrollLayoutController,
- sceneContainerFlags: SceneContainerFlags,
- sharedNotificationContainer: SharedNotificationContainer,
- sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
- stackScrollLayout: NotificationStackScrollLayout,
- notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
- ambientState: AmbientState,
- notificationStackSizeCalculator: NotificationStackSizeCalculator,
- @Main mainDispatcher: CoroutineDispatcher,
+ private val controller: NotificationStackScrollLayoutController,
+ private val sceneContainerFlags: SceneContainerFlags,
+ private val sharedNotificationContainer: SharedNotificationContainer,
+ private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+ private val stackScrollLayout: NotificationStackScrollLayout,
+ private val notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
+ private val ambientState: AmbientState,
+ private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
+ @Main private val mainDispatcher: CoroutineDispatcher,
) {
- init {
- if (!KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) {
- // This scene container section moves the NSSL to the SharedNotificationContainer. This
- // also requires that SharedNotificationContainer gets moved to the SceneWindowRootView
- // by the SceneWindowRootViewBinder.
- // Prior to Scene Container, but when the KeyguardShadeMigrationNssl flag is enabled,
- // NSSL is moved into this container by the NotificationStackScrollLayoutSection.
- (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
- sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
+ @Composable
+ fun SceneScope.Notifications(modifier: Modifier = Modifier) {
+ if (KeyguardShadeMigrationNssl.isUnexpectedlyInLegacyMode()) {
+ // This scene container section moves the NSSL to the SharedNotificationContainer.
+ // This also requires that SharedNotificationContainer gets moved to the
+ // SceneWindowRootView by the SceneWindowRootViewBinder. Prior to Scene Container,
+ // but when the KeyguardShadeMigrationNssl flag is enabled, NSSL is moved into this
+ // container by the NotificationStackScrollLayoutSection.
+ return
+ }
- SharedNotificationContainerBinder.bind(
- sharedNotificationContainer,
- sharedNotificationContainerViewModel,
- sceneContainerFlags,
- controller,
- notificationStackSizeCalculator,
- mainDispatcher,
+ var isBound by remember { mutableStateOf(false) }
+
+ DisposableEffect(Unit) {
+ val disposableHandles: MutableList<DisposableHandle> = mutableListOf()
+
+ // Ensure stackScrollLayout is a child of sharedNotificationContainer.
+ if (stackScrollLayout.parent != sharedNotificationContainer) {
+ (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
+ sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
+ }
+
+ disposableHandles.add(
+ SharedNotificationContainerBinder.bind(
+ sharedNotificationContainer,
+ sharedNotificationContainerViewModel,
+ sceneContainerFlags,
+ controller,
+ notificationStackSizeCalculator,
+ mainDispatcher,
+ )
)
if (sceneContainerFlags.flexiNotifsEnabled()) {
- NotificationStackAppearanceViewBinder.bind(
- context,
- sharedNotificationContainer,
- notificationStackAppearanceViewModel,
- ambientState,
- controller,
+ disposableHandles.add(
+ NotificationStackAppearanceViewBinder.bind(
+ context,
+ sharedNotificationContainer,
+ notificationStackAppearanceViewModel,
+ ambientState,
+ controller,
+ )
)
}
- }
- }
- @Composable
- fun SceneScope.Notifications(modifier: Modifier = Modifier) {
+ isBound = true
+
+ onDispose {
+ disposableHandles.forEach { it.dispose() }
+ disposableHandles.clear()
+ isBound = false
+ }
+ }
+
+ if (!isBound) {
+ return
+ }
+
NotificationStack(
viewModel = viewModel,
modifier = modifier,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index dcd22fe..a7ec93f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -19,6 +19,7 @@
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Slider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -33,6 +34,7 @@
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
+ Slider(0.5f, {})
for (component in components) {
AnimatedVisibility(component.isVisible) {
with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
new file mode 100644
index 0000000..693de55
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.notification.stack.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationsPlaceholderViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val underTest = kosmos.notificationsPlaceholderViewModel
+ @Test
+ fun onBoundsChanged_setsNotificationContainerBounds() {
+ underTest.onBoundsChanged(left = 5f, top = 5f, right = 5f, bottom = 5f)
+ assertThat(kosmos.keyguardInteractor.notificationContainerBounds.value)
+ .isEqualTo(NotificationContainerBounds(left = 5f, top = 5f, right = 5f, bottom = 5f))
+ assertThat(kosmos.notificationStackAppearanceInteractor.stackBounds.value)
+ .isEqualTo(NotificationContainerBounds(left = 5f, top = 5f, right = 5f, bottom = 5f))
+ }
+ @Test
+ fun onContentTopChanged_setsContentTop() {
+ underTest.onContentTopChanged(padding = 5f)
+ assertThat(kosmos.notificationStackAppearanceInteractor.contentTop.value).isEqualTo(5f)
+ }
+}
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 4e04af6..6bf4906 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -54,7 +54,7 @@
"SystemUIUnfoldLib",
"SystemUISharedLib-Keyguard",
"WindowManager-Shell-shared",
- "tracinglib",
+ "tracinglib-platform",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
"androidx.lifecycle_lifecycle-runtime-ktx",
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index 11c7a31..ecbd3f9 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -69,7 +69,7 @@
}
val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
- getStartCameraIntent(),
+ getStartCameraIntent(selectedUserInteractor.getSelectedUserId()),
PackageManager.MATCH_DEFAULT_ONLY,
selectedUserInteractor.getSelectedUserId()
)
@@ -85,7 +85,7 @@
* @param source The source of the camera launch, to be passed to the camera app via [Intent]
*/
fun launchCamera(source: Int) {
- val intent: Intent = getStartCameraIntent()
+ val intent: Intent = getStartCameraIntent(selectedUserInteractor.getSelectedUserId())
intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
intent, selectedUserInteractor.getSelectedUserId()
@@ -143,13 +143,13 @@
* Returns an [Intent] that can be used to start the camera app such that it occludes the
* lock-screen, if needed.
*/
- private fun getStartCameraIntent(): Intent {
+ private fun getStartCameraIntent(userId: Int): Intent {
val isLockScreenDismissible = keyguardStateController.canDismissLockScreen()
val isSecure = keyguardStateController.isMethodSecure
return if (isSecure && !isLockScreenDismissible) {
- cameraIntents.getSecureCameraIntent()
+ cameraIntents.getSecureCameraIntent(userId)
} else {
- cameraIntents.getInsecureCameraIntent()
+ cameraIntents.getInsecureCameraIntent(userId)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index 1e17059..1137586 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -18,9 +18,11 @@
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.provider.MediaStore
import android.text.TextUtils
import com.android.systemui.res.R
+import android.util.Log
class CameraIntents {
companion object {
@@ -28,28 +30,33 @@
val DEFAULT_INSECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
private val VIDEO_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_VIDEO_CAMERA
const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
+ const val TAG = "CameraIntents"
@JvmStatic
- fun getOverrideCameraPackage(context: Context): String? {
- context.resources.getString(R.string.config_cameraGesturePackage)?.let {
- if (!TextUtils.isEmpty(it)) {
- return it
+ fun getOverrideCameraPackage(context: Context, userId: Int): String? {
+ val packageName = context.resources.getString(R.string.config_cameraGesturePackage)!!
+ try {
+ if (!TextUtils.isEmpty(packageName)
+ && context.packageManager.getApplicationInfoAsUser(packageName, 0, userId).enabled ?: false) {
+ return packageName
}
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Missing cameraGesturePackage $packageName", e)
}
return null
}
@JvmStatic
- fun getInsecureCameraIntent(context: Context): Intent {
+ fun getInsecureCameraIntent(context: Context, userId: Int): Intent {
val intent = Intent(DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
- getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
+ getOverrideCameraPackage(context, userId)?.let { intent.setPackage(it) }
return intent
}
@JvmStatic
- fun getSecureCameraIntent(context: Context): Intent {
+ fun getSecureCameraIntent(context: Context, userId: Int): Intent {
val intent = Intent(DEFAULT_SECURE_CAMERA_INTENT_ACTION)
- getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
+ getOverrideCameraPackage(context, userId)?.let { intent.setPackage(it) }
return intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
}
@@ -65,7 +72,7 @@
/** Returns an [Intent] that can be used to start the camera in video mode. */
@JvmStatic
- fun getVideoCameraIntent(): Intent {
+ fun getVideoCameraIntent(userId: Int): Intent {
return Intent(VIDEO_CAMERA_INTENT_ACTION)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
index a434617..b65c0d4 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
@@ -29,22 +29,22 @@
/**
* Returns an [Intent] that can be used to start the camera, suitable for when the device is
- * already unlocked
+ * locked
*/
- fun getSecureCameraIntent(): Intent {
- return CameraIntents.getSecureCameraIntent(context)
+ fun getSecureCameraIntent(userId: Int): Intent {
+ return CameraIntents.getSecureCameraIntent(context, userId)
}
/**
* Returns an [Intent] that can be used to start the camera, suitable for when the device is not
* already unlocked
*/
- fun getInsecureCameraIntent(): Intent {
- return CameraIntents.getInsecureCameraIntent(context)
+ fun getInsecureCameraIntent(userId: Int): Intent {
+ return CameraIntents.getInsecureCameraIntent(context, userId)
}
/** Returns an [Intent] that can be used to start the camera in video mode. */
- fun getVideoCameraIntent(): Intent {
- return CameraIntents.getVideoCameraIntent()
+ fun getVideoCameraIntent(userId: Int): Intent {
+ return CameraIntents.getVideoCameraIntent(userId)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index c5dac77..80db535 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -19,14 +19,12 @@
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
-import android.app.smartspace.SmartspaceTarget
import android.content.Context
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
-import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
@@ -64,11 +62,6 @@
// A shadow copy of listeners is maintained to track whether the session should remain open.
private var listeners = mutableSetOf<SmartspaceTargetListener>()
- private var unfilteredListeners = mutableSetOf<SmartspaceTargetListener>()
-
- // Smartspace can be used on multiple displays, such as when the user casts their screen
- private var smartspaceViews = mutableSetOf<SmartspaceView>()
-
var preconditionListener =
object : SmartspacePrecondition.Listener {
override fun onCriteriaChanged() {
@@ -101,9 +94,7 @@
}
private fun hasActiveSessionListeners(): Boolean {
- return smartspaceViews.isNotEmpty() ||
- listeners.isNotEmpty() ||
- unfilteredListeners.isNotEmpty()
+ return listeners.isNotEmpty()
}
private fun connectSession() {
@@ -188,8 +179,4 @@
private fun reloadSmartspace() {
session?.requestSmartspaceUpdate()
}
-
- private fun onTargetsAvailableUnfiltered(targets: List<SmartspaceTarget>) {
- unfilteredListeners.forEach { it.onSmartspaceTargetsUpdated(targets) }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 8df7e8b..9a4dfdd 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -27,6 +27,8 @@
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.keyboard.stickykeys.ui.viewmodel.StickyKeysIndicatorViewModel
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
@@ -115,4 +117,11 @@
/** Creates a container that hosts the communal UI and handles gesture transitions. */
fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View
+
+ /** Creates a [View] that represents the Lockscreen. */
+ fun createLockscreen(
+ context: Context,
+ viewModel: LockscreenContentViewModel,
+ blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
+ ): View
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 7f43fac..abe49ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -20,6 +20,12 @@
import android.content.Context
import android.view.LayoutInflater
import android.view.View
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.internal.jank.InteractionJankMonitor
import com.android.keyguard.KeyguardStatusView
import com.android.keyguard.KeyguardStatusViewController
@@ -29,10 +35,13 @@
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.compose.ComposeFacade
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.keyguard.shared.ComposeLockscreen
+import com.android.systemui.keyguard.shared.model.LockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
@@ -44,6 +53,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
@@ -88,6 +98,8 @@
private val falsingManager: FalsingManager,
private val aodAlphaViewModel: AodAlphaViewModel,
private val keyguardClockViewModel: KeyguardClockViewModel,
+ private val lockscreenContentViewModel: LockscreenContentViewModel,
+ private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -115,11 +127,28 @@
initializeViews()
if (!SceneContainerFlag.isEnabled) {
- KeyguardBlueprintViewBinder.bind(
- keyguardRootView,
- keyguardBlueprintViewModel,
- keyguardClockViewModel
- )
+ if (ComposeLockscreen.isEnabled) {
+ val composeView =
+ ComposeFacade.createLockscreen(
+ context = context,
+ viewModel = lockscreenContentViewModel,
+ blueprints = lockscreenSceneBlueprintsLazy.get(),
+ )
+ composeView.id = View.generateViewId()
+ val cs = ConstraintSet()
+ cs.clone(keyguardRootView)
+ cs.connect(composeView.id, START, PARENT_ID, START)
+ cs.connect(composeView.id, END, PARENT_ID, END)
+ cs.connect(composeView.id, TOP, PARENT_ID, TOP)
+ cs.connect(composeView.id, BOTTOM, PARENT_ID, BOTTOM)
+ keyguardRootView.addView(composeView)
+ } else {
+ KeyguardBlueprintViewBinder.bind(
+ keyguardRootView,
+ keyguardBlueprintViewModel,
+ keyguardClockViewModel,
+ )
+ }
}
keyguardBlueprintCommandListener.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
index bbdd903..3e6e3b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
@@ -50,14 +50,13 @@
@Background private val backgroundDispatcher: CoroutineDispatcher,
) : KeyguardQuickAffordanceConfig {
- private val intent: Intent by lazy {
- cameraIntents.getVideoCameraIntent().apply {
+ private val intent: Intent
+ get() = cameraIntents.getVideoCameraIntent(userTracker.userId).apply {
putExtra(
CameraIntents.EXTRA_LAUNCH_SOURCE,
StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE,
)
}
- }
override val key: String
get() = BuiltInKeyguardQuickAffordanceKeys.VIDEO_CAMERA
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/LockscreenSceneBlueprint.kt
similarity index 60%
copy from packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/LockscreenSceneBlueprint.kt
index 6d9cba4..2eafb83 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/LockscreenSceneBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/LockscreenSceneBlueprint.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,18 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.ui.composable.blueprint
+package com.android.systemui.keyguard.shared.model
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.SceneScope
-
-/** Defines interface for classes that can render the content for a specific blueprint/layout. */
+/**
+ * Defines interface for classes that can render the content for a specific blueprint/layout.
+ *
+ * The actual rendering is done by a compose-aware sub-interface.
+ */
interface LockscreenSceneBlueprint {
-
/** The ID that uniquely identifies this blueprint across all other blueprints. */
val id: String
-
- /** Renders the content of this blueprint. */
- @Composable fun SceneScope.Content(modifier: Modifier)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index a651c10..52d94a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -57,7 +57,7 @@
private val mainDispatcher: CoroutineDispatcher,
) : KeyguardSection() {
private val placeHolderId = R.id.nssl_placeholder
- private var disposableHandle: DisposableHandle? = null
+ private val disposableHandles: MutableList<DisposableHandle> = mutableListOf()
/**
* Align the notification placeholder bottom to the top of either the lock icon or the ambient
@@ -102,8 +102,9 @@
if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
- disposableHandle?.dispose()
- disposableHandle =
+
+ disposeHandles()
+ disposableHandles.add(
SharedNotificationContainerBinder.bind(
sharedNotificationContainer,
sharedNotificationContainerViewModel,
@@ -112,19 +113,28 @@
notificationStackSizeCalculator,
mainDispatcher,
)
+ )
+
if (sceneContainerFlags.flexiNotifsEnabled()) {
- NotificationStackAppearanceViewBinder.bind(
- context,
- sharedNotificationContainer,
- notificationStackAppearanceViewModel,
- ambientState,
- controller,
+ disposableHandles.add(
+ NotificationStackAppearanceViewBinder.bind(
+ context,
+ sharedNotificationContainer,
+ notificationStackAppearanceViewModel,
+ ambientState,
+ controller,
+ )
)
}
}
override fun removeViews(constraintLayout: ConstraintLayout) {
- disposableHandle?.dispose()
+ disposeHandles()
constraintLayout.removeView(placeHolderId)
}
+
+ private fun disposeHandles() {
+ disposableHandles.forEach { it.dispose() }
+ disposableHandles.clear()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
index d4ea728..9cf3c95 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
@@ -28,7 +28,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onStart
/** Models UI state for the alpha of the AOD (always-on display). */
@SysUISingleton
@@ -43,15 +42,13 @@
/** The alpha level for the entire lockscreen while in AOD. */
val alpha: Flow<Float> =
combine(
- keyguardTransitionInteractor.transitionValue(KeyguardState.GONE).onStart {
- emit(0f)
- },
+ keyguardTransitionInteractor.currentKeyguardState,
merge(
keyguardInteractor.keyguardAlpha,
occludedToLockscreenTransitionViewModel.lockscreenAlpha,
)
- ) { transitionToGone, alpha ->
- if (transitionToGone == 1f) {
+ ) { currentKeyguardState, alpha ->
+ if (currentKeyguardState == KeyguardState.GONE) {
// Ensures content is not visible when in GONE state
0f
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
index ba04fd3..f5e6135 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt
@@ -67,6 +67,8 @@
duration = 500.milliseconds,
onStart = { 0f },
onStep = { it },
+ onFinish = { 1f },
+ onCancel = { 1f },
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
index 785a1e8..1d3cfd2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
@@ -30,7 +30,7 @@
private val localBluetoothManager: LocalBluetoothManager?
) {
/** Creates a [LocalMediaManager] for the given package. */
- fun create(packageName: String): LocalMediaManager {
+ fun create(packageName: String?): LocalMediaManager {
return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
.run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 09e4e75..06ca3af 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2472,11 +2472,9 @@
return 0;
}
if (!mKeyguardBypassController.getBypassEnabled()) {
- if (migrateClocksToBlueprint()) {
- View nsslPlaceholder = mView.getRootView().findViewById(R.id.nssl_placeholder);
- if (!mSplitShadeEnabled && nsslPlaceholder != null) {
- return nsslPlaceholder.getTop();
- }
+ if (migrateClocksToBlueprint() && !mSplitShadeEnabled) {
+ return (int) mKeyguardInteractor.getNotificationContainerBounds()
+ .getValue().getTop();
}
return mClockPositionResult.stackScrollerPadding;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt
new file mode 100644
index 0000000..c74c396
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.interruption
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.util.Log
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+
+// Class to track avalanche trigger event time.
+@SysUISingleton
+class AvalancheProvider
+@Inject
+constructor(
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val logger: VisualInterruptionDecisionLogger,
+) {
+ val TAG = "AvalancheProvider"
+ val timeoutMs = 120000
+ var startTime: Long = 0L
+
+ private val avalancheTriggerIntents = mutableSetOf(
+ Intent.ACTION_AIRPLANE_MODE_CHANGED,
+ Intent.ACTION_BOOT_COMPLETED,
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
+ Intent.ACTION_USER_SWITCHED
+ )
+
+ private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action in avalancheTriggerIntents) {
+
+ // Ignore when airplane mode turned on
+ if (intent.action == Intent.ACTION_AIRPLANE_MODE_CHANGED
+ && intent.getBooleanExtra(/* name= */ "state", /* defaultValue */ false)) {
+ Log.d(TAG, "broadcastReceiver: ignore airplane mode on")
+ return
+ }
+ Log.d(TAG, "broadcastReceiver received intent.action=" + intent.action)
+ startTime = System.currentTimeMillis()
+ }
+ }
+ }
+
+ fun register() {
+ val intentFilter = IntentFilter()
+ for (intent in avalancheTriggerIntents) {
+ intentFilter.addAction(intent)
+ }
+ broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 8e82442..20c8add 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -16,7 +16,10 @@
package com.android.systemui.statusbar.notification.interruption
+import android.app.Notification
import android.app.Notification.BubbleMetadata
+import android.app.Notification.CATEGORY_EVENT
+import android.app.Notification.CATEGORY_REMINDER
import android.app.Notification.VISIBILITY_PRIVATE
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
@@ -224,3 +227,68 @@
override fun shouldSuppress(entry: NotificationEntry) =
keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
}
+
+
+class AvalancheSuppressor(
+ private val avalancheProvider: AvalancheProvider,
+ private val systemClock: SystemClock,
+) : VisualInterruptionFilter(
+ types = setOf(PEEK, PULSE),
+ reason = "avalanche",
+ ) {
+ val TAG = "AvalancheSuppressor"
+
+ enum class State {
+ ALLOW_CONVERSATION_AFTER_AVALANCHE,
+ ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME,
+ ALLOW_CALLSTYLE,
+ ALLOW_CATEGORY_REMINDER,
+ ALLOW_CATEGORY_EVENT,
+ ALLOW_FSI_WITH_PERMISSION_ON,
+ ALLOW_COLORIZED,
+ SUPPRESS
+ }
+
+ override fun shouldSuppress(entry: NotificationEntry): Boolean {
+ val timeSinceAvalanche = systemClock.currentTimeMillis() - avalancheProvider.startTime
+ val isActive = timeSinceAvalanche < avalancheProvider.timeoutMs
+ val state = allow(entry)
+ val suppress = isActive && state == State.SUPPRESS
+ reason = "avalanche suppress=$suppress isActive=$isActive state=$state"
+ return suppress
+ }
+
+ fun allow(entry: NotificationEntry): State {
+ if (
+ entry.ranking.isConversation &&
+ entry.sbn.notification.`when` > avalancheProvider.startTime
+ ) {
+ return State.ALLOW_CONVERSATION_AFTER_AVALANCHE
+ }
+
+ if (entry.channel?.isImportantConversation == true) {
+ return State.ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME
+ }
+
+ if (entry.sbn.notification.isStyle(Notification.CallStyle::class.java)) {
+ return State.ALLOW_CALLSTYLE
+ }
+
+ if (entry.sbn.notification.category == CATEGORY_REMINDER) {
+ return State.ALLOW_CATEGORY_REMINDER
+ }
+
+ if (entry.sbn.notification.category == CATEGORY_EVENT) {
+ return State.ALLOW_CATEGORY_EVENT
+ }
+
+ if (entry.sbn.notification.fullScreenIntent != null) {
+ return State.ALLOW_FSI_WITH_PERMISSION_ON
+ }
+
+ if (entry.sbn.notification.isColorized) {
+ return State.ALLOW_COLORIZED
+ }
+ return State.SUPPRESS
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 6878a1e..dabb18b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -33,6 +33,7 @@
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
+import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -45,22 +46,25 @@
class VisualInterruptionDecisionProviderImpl
@Inject
constructor(
- private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
- private val batteryController: BatteryController,
- deviceProvisionedController: DeviceProvisionedController,
- private val eventLog: EventLog,
- private val globalSettings: GlobalSettings,
- private val headsUpManager: HeadsUpManager,
- private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
- keyguardStateController: KeyguardStateController,
- private val logger: VisualInterruptionDecisionLogger,
- @Main private val mainHandler: Handler,
- private val powerManager: PowerManager,
- private val statusBarStateController: StatusBarStateController,
- private val systemClock: SystemClock,
- private val uiEventLogger: UiEventLogger,
- private val userTracker: UserTracker,
+ private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
+ private val batteryController: BatteryController,
+ deviceProvisionedController: DeviceProvisionedController,
+ private val eventLog: EventLog,
+ private val globalSettings: GlobalSettings,
+ private val headsUpManager: HeadsUpManager,
+ private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
+ keyguardStateController: KeyguardStateController,
+ private val logger: VisualInterruptionDecisionLogger,
+ @Main private val mainHandler: Handler,
+ private val powerManager: PowerManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val systemClock: SystemClock,
+ private val uiEventLogger: UiEventLogger,
+ private val userTracker: UserTracker,
+ private val avalancheProvider: AvalancheProvider
+
) : VisualInterruptionDecisionProvider {
+
init {
check(!VisualInterruptionRefactor.isUnexpectedlyInLegacyMode())
}
@@ -166,6 +170,10 @@
addFilter(HunJustLaunchedFsiSuppressor())
addFilter(AlertKeyguardVisibilitySuppressor(keyguardNotificationVisibilityProvider))
+ if (NotificationAvalancheSuppression.isEnabled) {
+ addFilter(AvalancheSuppressor(avalancheProvider, systemClock))
+ avalancheProvider.register()
+ }
started = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
index ee79727..2f80c5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -85,7 +85,7 @@
/** A reason why visual interruptions might be suppressed based on the notification. */
abstract class VisualInterruptionFilter(
override val types: Set<VisualInterruptionType>,
- override val reason: String,
+ override var reason: String,
override val uiEventId: UiEventEnum? = null,
override val eventLogData: EventLogData? = null
) : VisualInterruptionSuppressor {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index 6c2cbbe..50b08b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -26,6 +26,7 @@
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
import kotlin.math.roundToInt
+import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.launch
/** Binds the shared notification container to its view-model. */
@@ -38,8 +39,8 @@
viewModel: NotificationStackAppearanceViewModel,
ambientState: AmbientState,
controller: NotificationStackScrollLayoutController,
- ) {
- view.repeatWhenAttached {
+ ): DisposableHandle {
+ return view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.stackBounds.collect { bounds ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index a436f17..8b723da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -20,6 +20,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
@@ -40,9 +42,11 @@
shadeInteractor: ShadeInteractor,
flags: SceneContainerFlags,
featureFlags: FeatureFlagsClassic,
+ private val keyguardInteractor: KeyguardInteractor,
) {
/** DEBUG: whether the placeholder "Notifications" text should be shown. */
- val isPlaceholderTextVisible: Boolean = !flags.flexiNotifsEnabled()
+ val isPlaceholderTextVisible: Boolean =
+ !flags.flexiNotifsEnabled() && SceneContainerFlag.isEnabled
/** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)
@@ -64,7 +68,10 @@
right: Float,
bottom: Float,
) {
- interactor.setStackBounds(NotificationContainerBounds(left, top, right, bottom))
+ val notificationContainerBounds =
+ NotificationContainerBounds(top = top, bottom = bottom, left = left, right = right)
+ keyguardInteractor.setNotificationContainerBounds(notificationContainerBounds)
+ interactor.setStackBounds(notificationContainerBounds)
}
/** The corner radius of the placeholder, in dp. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 39ca7b2..3669ba8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -352,7 +352,7 @@
}
if (!mKeyguardStateController.isShowing()) {
- final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext);
+ final Intent cameraIntent = CameraIntents.getInsecureCameraIntent(mContext, mUserTracker.getUserId());
cameraIntent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source);
mActivityStarter.startActivityDismissingKeyguard(cameraIntent,
false /* onlyProvisioned */, true /* dismissShade */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 64fcef5..2099361 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1803,10 +1803,10 @@
}
pw.println("Camera gesture intents:");
- pw.println(" Insecure camera: " + CameraIntents.getInsecureCameraIntent(mContext));
- pw.println(" Secure camera: " + CameraIntents.getSecureCameraIntent(mContext));
+ pw.println(" Insecure camera: " + CameraIntents.getInsecureCameraIntent(mContext, mUserTracker.getUserId()));
+ pw.println(" Secure camera: " + CameraIntents.getSecureCameraIntent(mContext, mUserTracker.getUserId()));
pw.println(" Override package: "
- + CameraIntents.getOverrideCameraPackage(mContext));
+ + CameraIntents.getOverrideCameraPackage(mContext, mUserTracker.getUserId()));
}
private void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index 1a17e7c..665a571 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -32,7 +32,7 @@
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.app.tracing.TraceUtils
+import com.android.app.tracing.namedRunnable
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
@@ -125,7 +125,7 @@
}
// FrameCallback used to delay starting the light reveal animation until the next frame
- private val startLightRevealCallback = TraceUtils.namedRunnable("startLightReveal") {
+ private val startLightRevealCallback = namedRunnable("startLightReveal") {
lightRevealAnimationPlaying = true
lightRevealAnimator.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
index 92a64a6..4dfd5a1 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
@@ -18,8 +18,8 @@
import android.content.Context
import android.util.Log
-import com.android.app.tracing.TraceUtils.instantForTrack
import com.android.app.tracing.TraceUtils.traceAsync
+import com.android.app.tracing.instantForTrack
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -81,8 +81,8 @@
.pairwise()
.filter {
// Start tracking only when the foldable device is
- //folding(UNFOLDED/HALF_FOLDED -> FOLDED) or
- //unfolding(FOLDED -> HALF_FOLD/UNFOLDED)
+ // folding(UNFOLDED/HALF_FOLDED -> FOLDED) or
+ // unfolding(FOLDED -> HALF_FOLD/UNFOLDED)
foldableDeviceState ->
foldableDeviceState.previousValue == DeviceState.FOLDED ||
foldableDeviceState.newValue == DeviceState.FOLDED
@@ -172,7 +172,7 @@
fromFoldableDeviceState: Int
): DisplaySwitchLatencyEvent {
log { "fromFoldableDeviceState=$fromFoldableDeviceState" }
- instantForTrack(TAG, "fromFoldableDeviceState=$fromFoldableDeviceState")
+ instantForTrack(TAG) { "fromFoldableDeviceState=$fromFoldableDeviceState" }
return copy(fromFoldableDeviceState = fromFoldableDeviceState)
}
@@ -187,7 +187,7 @@
"toState=$toState, " +
"latencyMs=$displaySwitchTimeMs"
}
- instantForTrack(TAG, "toFoldableDeviceState=$toFoldableDeviceState, toState=$toState")
+ instantForTrack(TAG) { "toFoldableDeviceState=$toFoldableDeviceState, toState=$toState" }
return copy(
toFoldableDeviceState = toFoldableDeviceState,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
new file mode 100644
index 0000000..2ff9af9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.volume.dagger
+
+import android.content.Context
+import android.media.session.MediaSessionManager
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.volume.data.repository.MediaControllerRepository
+import com.android.settingslib.volume.data.repository.MediaControllerRepositoryImpl
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+
+@Module
+interface MediaDevicesModule {
+
+ companion object {
+
+ @Provides
+ @SysUISingleton
+ fun provideMediaDeviceSessionRepository(
+ @Application context: Context,
+ mediaSessionManager: MediaSessionManager,
+ localBluetoothManager: LocalBluetoothManager?,
+ @Application coroutineScope: CoroutineScope,
+ @Background backgroundContext: CoroutineContext,
+ ): MediaControllerRepository =
+ MediaControllerRepositoryImpl(
+ context,
+ mediaSessionManager,
+ localBluetoothManager,
+ coroutineScope,
+ backgroundContext,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index c842e5f..5cb6fa8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -62,6 +62,7 @@
@Module(
includes = {
AudioModule.class,
+ MediaDevicesModule.class
},
subcomponents = {
VolumePanelComponent.class
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
new file mode 100644
index 0000000..57ac435
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.volume.panel.component.mediaoutput.data.repository
+
+import com.android.settingslib.volume.data.repository.LocalMediaRepository
+import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.media.controls.pipeline.LocalMediaManagerFactory
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+
+class LocalMediaRepositoryFactory
+@Inject
+constructor(
+ private val localMediaManagerFactory: LocalMediaManagerFactory,
+ @Application private val coroutineScope: CoroutineScope,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
+) {
+
+ fun create(packageName: String?): LocalMediaRepository =
+ LocalMediaRepositoryImpl(
+ localMediaManagerFactory.create(packageName),
+ coroutineScope,
+ backgroundCoroutineContext,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
new file mode 100644
index 0000000..6c456f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.volume.panel.component.mediaoutput.domain.interactor
+
+import android.content.pm.PackageManager
+import android.util.Log
+import com.android.settingslib.media.MediaDevice
+import com.android.settingslib.volume.data.repository.LocalMediaRepository
+import com.android.settingslib.volume.data.repository.MediaControllerRepository
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.withContext
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumePanelScope
+class MediaOutputInteractor
+@Inject
+constructor(
+ private val localMediaRepositoryFactory: LocalMediaRepositoryFactory,
+ private val packageManager: PackageManager,
+ @VolumePanelScope private val coroutineScope: CoroutineScope,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
+ mediaControllerRepository: MediaControllerRepository
+) {
+
+ val mediaDeviceSession: Flow<MediaDeviceSession> =
+ mediaControllerRepository.activeMediaController.mapNotNull { mediaController ->
+ if (mediaController == null) {
+ MediaDeviceSession.Inactive
+ } else {
+ MediaDeviceSession.Active(
+ appLabel = getApplicationLabel(mediaController.packageName)
+ ?: return@mapNotNull null,
+ packageName = mediaController.packageName,
+ sessionToken = mediaController.sessionToken,
+ )
+ }
+ }
+ private val localMediaRepository: Flow<LocalMediaRepository> =
+ mediaDeviceSession
+ .map { (it as? MediaDeviceSession.Active)?.packageName }
+ .distinctUntilChanged()
+ .map { localMediaRepositoryFactory.create(it) }
+ .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 1)
+
+ val currentConnectedDevice: Flow<MediaDevice?> =
+ localMediaRepository.flatMapLatest { it.currentConnectedDevice }
+
+ val mediaDevices: Flow<Collection<MediaDevice>> =
+ localMediaRepository.flatMapLatest { it.mediaDevices }
+
+ private suspend fun getApplicationLabel(packageName: String): CharSequence? {
+ return try {
+ withContext(backgroundCoroutineContext) {
+ val appInfo =
+ packageManager.getApplicationInfo(
+ packageName,
+ PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
+ )
+ appInfo.loadLabel(packageManager)
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(TAG, "Unable to find info for package: $packageName")
+ null
+ }
+ }
+
+ private companion object {
+ const val TAG = "MediaOutputInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
new file mode 100644
index 0000000..f250308
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.volume.panel.component.mediaoutput.domain.model
+
+import android.media.session.MediaSession
+
+/** Represents media playing on the connected device. */
+sealed interface MediaDeviceSession {
+
+ /** Media is playing. */
+ data class Active(
+ val appLabel: CharSequence,
+ val packageName: String,
+ val sessionToken: MediaSession.Token,
+ ) : MediaDeviceSession
+
+ /** Media is not playing. */
+ data object Inactive : MediaDeviceSession
+
+ /** Current media state is unknown yet. */
+ data object Unknown : MediaDeviceSession
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index 6d3cc4c..669795b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -81,10 +81,10 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(cameraIntents.getSecureCameraIntent()).thenReturn(
+ whenever(cameraIntents.getSecureCameraIntent(anyInt())).thenReturn(
Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION)
)
- whenever(cameraIntents.getInsecureCameraIntent()).thenReturn(
+ whenever(cameraIntents.getInsecureCameraIntent(anyInt())).thenReturn(
Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 25a7eb8..9517f82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -46,6 +46,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Assume.assumeTrue
import org.junit.Before
@@ -118,6 +119,11 @@
initAndAttachContainerView()
}
+ @After
+ fun tearDown() {
+ ViewUtils.detachView(parentView)
+ }
+
@Test
fun isEnabled_communalEnabled_returnsTrue() {
communalRepository.setIsCommunalEnabled(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index da68d9c..5410864 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.notification.interruption
+import android.app.Notification.CATEGORY_EVENT
+import android.app.Notification.CATEGORY_REMINDER
+import android.app.NotificationManager
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
@@ -47,6 +50,7 @@
systemClock,
uiEventLogger,
userTracker,
+ avalancheProvider
)
}
@@ -70,6 +74,114 @@
}
}
+ // Avalanche tests are in VisualInterruptionDecisionProviderImplTest
+ // instead of VisualInterruptionDecisionProviderTestBase
+ // because avalanche code is based on the suppression refactor.
+
+ @Test
+ fun testAvalancheFilter_duringAvalanche_allowConversationFromAfterEvent() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ ensurePeekState()
+ assertShouldHeadsUp(buildEntry {
+ importance = NotificationManager.IMPORTANCE_HIGH
+ isConversation = true
+ isImportantConversation = false
+ whenMs = whenAgo(5)
+ })
+ }
+ }
+
+ @Test
+ fun testAvalancheFilter_duringAvalanche_suppressConversationFromBeforeEvent() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ ensurePeekState()
+ assertShouldNotHeadsUp(buildEntry {
+ importance = NotificationManager.IMPORTANCE_DEFAULT
+ isConversation = true
+ isImportantConversation = false
+ whenMs = whenAgo(15)
+ })
+ }
+ }
+
+ @Test
+ fun testAvalancheFilter_duringAvalanche_allowHighPriorityConversation() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ ensurePeekState()
+ assertShouldHeadsUp(buildEntry {
+ importance = NotificationManager.IMPORTANCE_HIGH
+ isImportantConversation = true
+ })
+ }
+ }
+
+ @Test
+ fun testAvalancheFilter_duringAvalanche_allowCall() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ ensurePeekState()
+ assertShouldHeadsUp(buildEntry {
+ importance = NotificationManager.IMPORTANCE_HIGH
+ isCall = true
+ })
+ }
+ }
+
+ @Test
+ fun testAvalancheFilter_duringAvalanche_allowCategoryReminder() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ ensurePeekState()
+ assertShouldHeadsUp(buildEntry {
+ importance = NotificationManager.IMPORTANCE_HIGH
+ category = CATEGORY_REMINDER
+ })
+ }
+ }
+
+ @Test
+ fun testAvalancheFilter_duringAvalanche_allowCategoryEvent() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ ensurePeekState()
+ assertShouldHeadsUp(buildEntry {
+ importance = NotificationManager.IMPORTANCE_HIGH
+ category = CATEGORY_EVENT
+ })
+ }
+ }
+
+ @Test
+ fun testAvalancheFilter_duringAvalanche_allowFsi() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ assertFsiNotSuppressed()
+ }
+ }
+
+ @Test
+ fun testAvalancheFilter_duringAvalanche_allowColorized() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ withFilter(AvalancheSuppressor(avalancheProvider, systemClock)) {
+ ensurePeekState()
+ assertShouldHeadsUp(buildEntry {
+ importance = NotificationManager.IMPORTANCE_HIGH
+ isColorized = true
+ })
+ }
+ }
+
@Test
fun testPeekCondition_suppressesOnlyPeek() {
withCondition(TestCondition(types = setOf(PEEK)) { true }) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 2ac0cb7..f89b9cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -19,7 +19,10 @@
import android.app.ActivityManager
import android.app.Notification
import android.app.Notification.BubbleMetadata
+import android.app.Notification.EXTRA_COLORIZED
+import android.app.Notification.EXTRA_TEMPLATE
import android.app.Notification.FLAG_BUBBLE
+import android.app.Notification.FLAG_CAN_COLORIZE
import android.app.Notification.FLAG_FOREGROUND_SERVICE
import android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED
import android.app.Notification.FLAG_USER_INITIATED_JOB
@@ -50,6 +53,8 @@
import com.android.internal.logging.UiEventLogger.UiEventEnum
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogcatEchoTracker
import com.android.systemui.log.core.LogLevel
@@ -122,6 +127,7 @@
protected val systemClock = FakeSystemClock()
protected val uiEventLogger = UiEventLoggerFake()
protected val userTracker = FakeUserTracker()
+ protected val avalancheProvider: AvalancheProvider = mock()
protected abstract val provider: VisualInterruptionDecisionProvider
@@ -1097,6 +1103,8 @@
var whenMs: Long? = null
var isGrouped = false
var isGroupSummary = false
+ var isCall = false
+ var category: String? = null
var groupAlertBehavior: Int? = null
var hasBubbleMetadata = false
var hasFsi = false
@@ -1106,10 +1114,12 @@
var isUserInitiatedJob = false
var isBubble = false
var isStickyAndNotDemoted = false
+ var isColorized = false
// Set on NotificationEntryBuilder:
var importance = IMPORTANCE_DEFAULT
var canBubble: Boolean? = null
+ var isImportantConversation = false
// Set on NotificationEntry:
var hasJustLaunchedFsi = false
@@ -1118,6 +1128,7 @@
var packageSuspended = false
var visibilityOverride: Int? = null
var suppressedVisualEffects: Int? = null
+ var isConversation = false
private fun buildBubbleMetadata(): BubbleMetadata {
val builder =
@@ -1158,6 +1169,13 @@
nb.setGroupSummary(true)
}
+ if (isCall) {
+ nb.extras.putString(EXTRA_TEMPLATE, Notification.CallStyle::class.java.name)
+ }
+
+ if (category != null) {
+ nb.setCategory(category)
+ }
groupAlertBehavior?.let { nb.setGroupAlertBehavior(it) }
if (hasBubbleMetadata) {
@@ -1185,6 +1203,10 @@
if (isStickyAndNotDemoted) {
n.flags = n.flags or FLAG_FSI_REQUESTED_BUT_DENIED
}
+ if (isColorized) {
+ n.extras.putBoolean(EXTRA_COLORIZED, true)
+ n.flags = n.flags or FLAG_CAN_COLORIZE
+ }
}
.let { NotificationEntryBuilder().setNotification(it) }
.also { neb ->
@@ -1193,9 +1215,10 @@
neb.setTag(TEST_TAG)
neb.setImportance(importance)
- neb.setChannel(
- NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
- )
+ val channel =
+ NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)
+ channel.isImportantConversation = isImportantConversation
+ neb.setChannel(channel)
canBubble?.let { neb.setCanBubble(it) }
}
@@ -1216,6 +1239,7 @@
}
visibilityOverride?.let { mrb.setVisibilityOverride(it) }
suppressedVisualEffects?.let { mrb.setSuppressedVisualEffects(it) }
+ mrb.setIsConversation(isConversation)
}
.build()
}
@@ -1287,7 +1311,7 @@
}
}
- private fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs
+ protected fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs
}
private const val TEST_CONTENT_TITLE = "Test Content Title"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
index 0356c2c..620ad9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
@@ -19,6 +19,7 @@
import android.os.Handler
import android.os.PowerManager
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
@@ -49,7 +50,8 @@
statusBarStateController: StatusBarStateController,
systemClock: SystemClock,
uiEventLogger: UiEventLogger,
- userTracker: UserTracker
+ userTracker: UserTracker,
+ avalancheProvider: AvalancheProvider
): VisualInterruptionDecisionProvider {
return if (VisualInterruptionRefactor.isEnabled) {
VisualInterruptionDecisionProviderImpl(
@@ -67,7 +69,8 @@
statusBarStateController,
systemClock,
uiEventLogger,
- userTracker
+ userTracker,
+ avalancheProvider
)
} else {
NotificationInterruptStateProviderWrapper(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 849a13b..b048949 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -153,6 +153,7 @@
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.init.NotificationsController;
+import com.android.systemui.statusbar.notification.interruption.AvalancheProvider;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
@@ -313,6 +314,7 @@
@Mock private CameraLauncher mCameraLauncher;
@Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock private UserTracker mUserTracker;
+ @Mock private AvalancheProvider mAvalancheProvider;
@Mock private FingerprintManager mFingerprintManager;
@Mock IPowerManager mPowerManagerService;
@Mock ActivityStarter mActivityStarter;
@@ -367,7 +369,8 @@
mStatusBarStateController,
mFakeSystemClock,
mock(UiEventLogger.class),
- mUserTracker);
+ mUserTracker,
+ mAvalancheProvider);
mVisualInterruptionDecisionProvider.start();
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 3bde6e3..99c2dc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -315,6 +315,8 @@
@After
public void tearDown() {
+ // Detaching view stops flow collection and prevents memory leak.
+ ViewUtils.detachView(mScrimBehind);
finishAnimationsImmediately();
Arrays.stream(ScrimState.values()).forEach((scrim) -> {
scrim.setAodFrontScrimAlpha(0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
index ba34ce6..bda339f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt
@@ -12,7 +12,7 @@
* permissions and limitations under the License.
*/
-package com.android.app.tracing
+package com.android.systemui.tracing
import android.os.Handler
import android.os.Looper
@@ -20,11 +20,13 @@
import android.testing.AndroidTestingRunner
import android.util.Log
import androidx.test.filters.SmallTest
+import com.android.app.tracing.TraceUtils.traceRunnable
+import com.android.app.tracing.namedRunnable
+import com.android.app.tracing.traceSection
import com.android.systemui.SysuiTestCase
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@@ -68,7 +70,6 @@
}
@Test
- @Ignore("b/267482189 - Enable once androidx.tracing >= 1.2.0-beta04")
fun testLongTraceSection_doesNotThrow_whenUsingAndroidX() {
androidx.tracing.Trace.beginSection(SECTION_NAME_THATS_TOO_LONG)
}
@@ -84,17 +85,13 @@
fun testLongTraceSection_doesNotThrow_whenUsedAsTraceNameSupplier() {
Handler(Looper.getMainLooper())
.runWithScissors(
- TraceUtils.namedRunnable(SECTION_NAME_THATS_TOO_LONG) {
- Log.v(TAG, "TraceUtils.namedRunnable() block.")
- },
+ namedRunnable(SECTION_NAME_THATS_TOO_LONG) { Log.v(TAG, "namedRunnable() block.") },
TEST_FAIL_TIMEOUT
)
}
@Test
fun testLongTraceSection_doesNotThrow_whenUsingTraceRunnable() {
- TraceUtils.traceRunnable(SECTION_NAME_THATS_TOO_LONG) {
- Log.v(TAG, "TraceUtils.traceRunnable() block.")
- }
+ traceRunnable(SECTION_NAME_THATS_TOO_LONG) { Log.v(TAG, "traceRunnable() block.") }.run()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index f3e9203..f4b36659 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -96,6 +96,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
@@ -151,6 +152,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.interruption.AvalancheProvider;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionLogger;
@@ -587,7 +589,9 @@
mock(StatusBarStateController.class),
mock(SystemClock.class),
mock(UiEventLogger.class),
- mock(UserTracker.class));
+ mock(UserTracker.class),
+ mock(AvalancheProvider.class)
+ );
interruptionDecisionProvider.start();
mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index d7e948e..29faa58 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.shared.flag.sceneContainerFlags
@@ -29,5 +30,6 @@
shadeInteractor = shadeInteractor,
flags = sceneContainerFlags,
featureFlags = featureFlagsClassic,
+ keyguardInteractor = keyguardInteractor,
)
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 85fa65a..7b5932b 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -58,6 +58,11 @@
private static ScheduledFuture<?> sPendingTimeout;
/**
+ * When enabled, attempt to detect uncaught exceptions from background threads.
+ */
+ private static final boolean ENABLE_UNCAUGHT_EXCEPTION_DETECTION = false;
+
+ /**
* When set, an unhandled exception was discovered (typically on a background thread), and we
* capture it here to ensure it's reported as a test failure.
*/
@@ -75,8 +80,10 @@
}
public static void init(RavenwoodRule rule) {
- maybeThrowPendingUncaughtException(false);
- Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+ if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+ maybeThrowPendingUncaughtException(false);
+ Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+ }
RuntimeInit.redirectLogStreams();
@@ -129,7 +136,9 @@
android.os.Binder.reset$ravenwood();
android.os.Process.reset$ravenwood();
- maybeThrowPendingUncaughtException(true);
+ if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+ maybeThrowPendingUncaughtException(true);
+ }
}
public static void logTestRunner(String label, Description description) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 687def0..a61199a 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1392,6 +1392,11 @@
}
@Override // Binder call
+ public int getMousePointerSpeed() {
+ return mNative.getMousePointerSpeed();
+ }
+
+ @Override // Binder call
public void tryPointerSpeed(int speed) {
if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED,
"tryPointerSpeed()")) {
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index e5f3484..b16df0f 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -119,6 +119,8 @@
*/
boolean transferTouch(IBinder destChannelToken, int displayId);
+ int getMousePointerSpeed();
+
void setPointerSpeed(int speed);
void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
@@ -364,6 +366,9 @@
public native boolean transferTouch(IBinder destChannelToken, int displayId);
@Override
+ public native int getMousePointerSpeed();
+
+ @Override
public native void setPointerSpeed(int speed);
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index fff4939..95189ab 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3732,6 +3732,37 @@
return InputBindResult.INVALID_DISPLAY_ID;
}
+ // In case mShowForced flag affects the next client to keep IME visible, when
+ // the current client is leaving due to the next focused client, we clear
+ // mShowForced flag when the next client's targetSdkVersion is T or higher.
+ final boolean shouldClearFlag =
+ mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
+ final boolean showForced = mVisibilityStateComputer.mShowForced;
+ if (mCurFocusedWindow != windowToken && showForced && shouldClearFlag) {
+ mVisibilityStateComputer.mShowForced = false;
+ }
+
+ // Verify if caller is a background user.
+ final int currentUserId = mSettings.getUserId();
+ if (userId != currentUserId) {
+ if (ArrayUtils.contains(
+ mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
+ // cross-profile access is always allowed here to allow
+ // profile-switching.
+ scheduleSwitchUserTaskLocked(userId, cs.mClient);
+ return InputBindResult.USER_SWITCHING;
+ }
+ Slog.w(TAG, "A background user is requesting window. Hiding IME.");
+ Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+ + " a background user, use EditorInfo.targetInputMethodUser with"
+ + " INTERACT_ACROSS_USERS_FULL permission.");
+ hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */,
+ 0 /* flags */,
+ null /* resultReceiver */,
+ SoftInputShowHideReason.HIDE_INVALID_USER);
+ return InputBindResult.INVALID_USER;
+ }
+
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
editorInfo, inputConnection, remoteAccessibilityInputConnection,
@@ -3781,32 +3812,6 @@
+ " cs=" + cs);
}
- final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
- // In case mShowForced flag affects the next client to keep IME visible, when the current
- // client is leaving due to the next focused client, we clear mShowForced flag when the
- // next client's targetSdkVersion is T or higher.
- final boolean showForced = mVisibilityStateComputer.mShowForced;
- if (mCurFocusedWindow != windowToken && showForced && shouldClearFlag) {
- mVisibilityStateComputer.mShowForced = false;
- }
-
- final int currentUserId = mSettings.getUserId();
- if (userId != currentUserId) {
- if (ArrayUtils.contains(
- mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
- // cross-profile access is always allowed here to allow profile-switching.
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
- Slog.w(TAG, "A background user is requesting window. Hiding IME.");
- Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
- + " a background user, use EditorInfo.targetInputMethodUser with"
- + " INTERACT_ACROSS_USERS_FULL permission.");
- hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
- null /* resultReceiver */, SoftInputShowHideReason.HIDE_INVALID_USER);
- return InputBindResult.INVALID_USER;
- }
-
final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
final boolean startInputByWinGainedFocus =
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index a06607b..7fb3e00 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -93,7 +93,6 @@
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.Process;
-import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -139,11 +138,11 @@
import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
-import com.android.internal.widget.ILockSettingsStateListener;
import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockSettingsStateListener;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.RebootEscrowListener;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -185,6 +184,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -332,8 +332,8 @@
private HashMap<UserHandle, UserManager> mUserManagerCache = new HashMap<>();
- private final RemoteCallbackList<ILockSettingsStateListener> mLockSettingsStateListeners =
- new RemoteCallbackList<>();
+ private final CopyOnWriteArrayList<LockSettingsStateListener> mLockSettingsStateListeners =
+ new CopyOnWriteArrayList<>();
// This class manages life cycle events for encrypted users on File Based Encryption (FBE)
// devices. The most basic of these is to show/hide notifications about missing features until
@@ -2379,25 +2379,12 @@
}
private void notifyLockSettingsStateListeners(boolean success, int userId) {
- int i = mLockSettingsStateListeners.beginBroadcast();
- try {
- while (i > 0) {
- i--;
- try {
- if (success) {
- mLockSettingsStateListeners.getBroadcastItem(i)
- .onAuthenticationSucceeded(userId);
- } else {
- mLockSettingsStateListeners.getBroadcastItem(i)
- .onAuthenticationFailed(userId);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Exception while notifying LockSettingsStateListener:"
- + " success = " + success + ", userId = " + userId, e);
- }
+ for (LockSettingsStateListener listener : mLockSettingsStateListeners) {
+ if (success) {
+ listener.onAuthenticationSucceeded(userId);
+ } else {
+ listener.onAuthenticationFailed(userId);
}
- } finally {
- mLockSettingsStateListeners.finishBroadcast();
}
}
@@ -3720,15 +3707,15 @@
}
@Override
- public void registerLockSettingsStateListener(
- @NonNull ILockSettingsStateListener listener) {
- mLockSettingsStateListeners.register(listener);
+ public void registerLockSettingsStateListener(@NonNull LockSettingsStateListener listener) {
+ Objects.requireNonNull(listener, "listener cannot be null");
+ mLockSettingsStateListeners.add(listener);
}
@Override
public void unregisterLockSettingsStateListener(
- @NonNull ILockSettingsStateListener listener) {
- mLockSettingsStateListeners.unregister(listener);
+ @NonNull LockSettingsStateListener listener) {
+ mLockSettingsStateListeners.remove(listener);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 0bbad85..dc97e5f 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -819,6 +819,7 @@
* <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
* launcher activities, only one of the icons is returned arbitrarily.
*/
+ @Nullable
public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
String callingPackageName) {
Objects.requireNonNull(packageName);
@@ -844,7 +845,7 @@
// In the rare case the archived app defined more than two launcher activities, we choose
// the first one arbitrarily.
Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
- if (getAppOpsManager().checkOp(
+ if (icon != null && getAppOpsManager().checkOp(
AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
== MODE_ALLOWED) {
icon = includeCloudOverlay(icon);
@@ -900,6 +901,7 @@
return bitmap;
}
+ @Nullable
Bitmap includeCloudOverlay(Bitmap bitmap) {
Drawable cloudDrawable =
mContext.getResources()
@@ -920,7 +922,9 @@
final int iconSize = mContext.getSystemService(
ActivityManager.class).getLauncherLargeIconSize();
Bitmap appIconWithCloudOverlay = drawableToBitmap(layerDrawable, iconSize);
- bitmap.recycle();
+ if (bitmap != null) {
+ bitmap.recycle();
+ }
return appIconWithCloudOverlay;
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 716aee3..e743172 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5150,6 +5150,15 @@
/** @return the orientation of the display when it's rotation is ROTATION_0. */
int getNaturalOrientation() {
+ return mBaseDisplayWidth <= mBaseDisplayHeight
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ }
+
+ /**
+ * Returns the orientation which is used for app's Configuration (excluding decor insets) when
+ * the display rotation is ROTATION_0.
+ */
+ int getNaturalConfigurationOrientation() {
final Configuration config = getConfiguration();
if (config.windowConfiguration.getDisplayRotation() == ROTATION_0) {
return config.orientation;
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 34d7651..4204670 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -132,7 +132,7 @@
void show(SurfaceControl.Transaction t, WindowContainer w) {
t.show(mInputSurface);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
- t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1);
+ t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1 + w.getChildCount());
}
void show(SurfaceControl.Transaction t, int layer) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2d2857a..80894b2 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1589,7 +1589,7 @@
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
// NOSENSOR means the display's "natural" orientation, so return that.
if (mDisplayContent != null) {
- return mDisplayContent.getNaturalOrientation();
+ return mDisplayContent.getNaturalConfigurationOrientation();
}
} else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
// LOCKED means the activity's orientation remains unchanged, so return existing value.
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index cc08488..df7fb99 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -31,5 +31,8 @@
per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
+# Memory
+per-file com_android_server_am_OomConnection.cpp = file:/MEMORY_OWNERS
+
# Bug component : 158088 = per-file *AnrTimer*
per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 4a6b31c..810090a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -285,6 +285,7 @@
void setInputDispatchMode(bool enabled, bool frozen);
void setSystemUiLightsOut(bool lightsOut);
void setPointerDisplayId(int32_t displayId);
+ int32_t getMousePointerSpeed();
void setPointerSpeed(int32_t speed);
void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled);
void setTouchpadPointerSpeed(int32_t speed);
@@ -1219,6 +1220,11 @@
}
}
+int32_t NativeInputManager::getMousePointerSpeed() {
+ std::scoped_lock _l(mLock);
+ return mLocked.pointerSpeed;
+}
+
void NativeInputManager::setPointerSpeed(int32_t speed) {
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -2211,6 +2217,12 @@
}
}
+static jint nativeGetMousePointerSpeed(JNIEnv* env, jobject nativeImplObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+ return static_cast<jint>(im->getMousePointerSpeed());
+}
+
static void nativeSetPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2865,6 +2877,7 @@
{"transferTouchFocus", "(Landroid/os/IBinder;Landroid/os/IBinder;Z)Z",
(void*)nativeTransferTouchFocus},
{"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouch},
+ {"getMousePointerSpeed", "()I", (void*)nativeGetMousePointerSpeed},
{"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
{"setMousePointerAccelerationEnabled", "(IZ)V",
(void*)nativeSetMousePointerAccelerationEnabled},
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 5081198..7053597 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -39,7 +39,6 @@
import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
-import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -49,8 +48,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.widget.ILockSettingsStateListener;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsStateListener;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -412,7 +411,7 @@
mSetFlagsRule.enableFlags(FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS);
final LockscreenCredential password = newPassword("password");
setCredential(PRIMARY_USER_ID, password);
- final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+ final LockSettingsStateListener listener = mock(LockSettingsStateListener.class);
mLocalService.registerLockSettingsStateListener(listener);
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
@@ -429,7 +428,7 @@
final LockscreenCredential password = newPassword("password");
setCredential(PRIMARY_USER_ID, password);
final LockscreenCredential badPassword = newPassword("badPassword");
- final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+ final LockSettingsStateListener listener = mock(LockSettingsStateListener.class);
mLocalService.registerLockSettingsStateListener(listener);
assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
@@ -445,7 +444,7 @@
final LockscreenCredential password = newPassword("password");
setCredential(PRIMARY_USER_ID, password);
final LockscreenCredential badPassword = newPassword("badPassword");
- final ILockSettingsStateListener listener = mockLockSettingsStateListener();
+ final LockSettingsStateListener listener = mock(LockSettingsStateListener.class);
mLocalService.registerLockSettingsStateListener(listener);
assertEquals(VerifyCredentialResponse.RESPONSE_OK,
@@ -599,12 +598,4 @@
assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
}
}
-
- private ILockSettingsStateListener mockLockSettingsStateListener() {
- ILockSettingsStateListener listener = mock(ILockSettingsStateListener.Stub.class);
- IBinder binder = mock(IBinder.class);
- when(binder.isBinderAlive()).thenReturn(true);
- when(listener.asBinder()).thenReturn(binder);
- return listener;
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 95850ac..f63ff6e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1003,7 +1003,13 @@
dc.computeScreenConfiguration(config, ROTATION_0);
dc.onRequestedOverrideConfigurationChanged(config);
assertEquals(Configuration.ORIENTATION_LANDSCAPE, config.orientation);
- assertEquals(Configuration.ORIENTATION_LANDSCAPE, dc.getNaturalOrientation());
+ assertEquals(Configuration.ORIENTATION_LANDSCAPE, dc.getNaturalConfigurationOrientation());
+ window.setOverrideOrientation(SCREEN_ORIENTATION_NOSENSOR);
+ assertEquals(Configuration.ORIENTATION_LANDSCAPE,
+ window.getRequestedConfigurationOrientation());
+ // Note that getNaturalOrientation is based on logical display size. So it is portrait if
+ // the display width equals to height.
+ assertEquals(Configuration.ORIENTATION_PORTRAIT, dc.getNaturalOrientation());
}
@Test
diff --git a/telecomm/OWNERS b/telecomm/OWNERS
index b57b7c7..bb2ac51 100644
--- a/telecomm/OWNERS
+++ b/telecomm/OWNERS
@@ -6,4 +6,5 @@
rgreenwalt@google.com
grantmenke@google.com
pmadapurmath@google.com
-tjstuart@google.com
\ No newline at end of file
+tjstuart@google.com
+huiwang@google.com
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index eb7e67d..1749545 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1927,34 +1927,25 @@
* Then for SDK 35+, if the caller identity is personal profile, then this will return
* subscription 1 only and vice versa.
*
- * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
- * {@link SubscriptionInfo#getSubscriptionId}.
+ * <p> Returned records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
+ * {@link SubscriptionInfo#getSubscriptionId}. Beginning with Android SDK 35, this method will
+ * never return null.
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see
* {@link TelephonyManager#hasCarrierPrivileges}).
*
- * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
- * <ul>
- * <li>
- * If null is returned the current state is unknown but if a {@link OnSubscriptionsChangedListener}
- * has been registered {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be
- * invoked in the future.
- * </li>
- * <li>
- * If the list is empty then there are no {@link SubscriptionInfo} records currently available.
- * </li>
- * <li>
- * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex}
- * then by {@link SubscriptionInfo#getSubscriptionId}.
- * </li>
- * </ul>
+ * @return a list of the active {@link SubscriptionInfo} that is visible to the caller. If
+ * an empty list or null is returned, then there are no active subscriptions that
+ * are visible to the caller. If the number of active subscriptions available to
+ * any caller changes, then this change will be indicated by
+ * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged}.
*
* @throws UnsupportedOperationException If the device does not have
- * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
+ public @Nullable List<SubscriptionInfo> getActiveSubscriptionInfoList() {
List<SubscriptionInfo> activeList = null;
try {
@@ -1970,6 +1961,8 @@
if (activeList != null) {
activeList = activeList.stream().filter(subInfo -> isSubscriptionVisible(subInfo))
.collect(Collectors.toList());
+ } else {
+ activeList = Collections.emptyList();
}
return activeList;
}
@@ -1998,12 +1991,7 @@
* {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
*/
public @NonNull List<SubscriptionInfo> getCompleteActiveSubscriptionInfoList() {
- List<SubscriptionInfo> completeList = getActiveSubscriptionInfoList(
- /* userVisibleonly */false);
- if (completeList == null) {
- completeList = new ArrayList<>();
- }
- return completeList;
+ return getActiveSubscriptionInfoList(/* userVisibleonly */ false);
}
/**
@@ -2032,7 +2020,7 @@
*
* @hide
*/
- public @Nullable List<SubscriptionInfo> getActiveSubscriptionInfoList(boolean userVisibleOnly) {
+ public @NonNull List<SubscriptionInfo> getActiveSubscriptionInfoList(boolean userVisibleOnly) {
List<SubscriptionInfo> activeList = null;
try {
@@ -2045,11 +2033,13 @@
// ignore it
}
- if (!userVisibleOnly || activeList == null) {
- return activeList;
- } else {
+ if (activeList == null || activeList.isEmpty()) {
+ return Collections.emptyList();
+ } else if (userVisibleOnly) {
return activeList.stream().filter(subInfo -> isSubscriptionVisible(subInfo))
.collect(Collectors.toList());
+ } else {
+ return activeList;
}
}
@@ -2086,7 +2076,7 @@
* @hide
*/
@SystemApi
- public List<SubscriptionInfo> getAvailableSubscriptionInfoList() {
+ public @Nullable List<SubscriptionInfo> getAvailableSubscriptionInfoList() {
List<SubscriptionInfo> result = null;
try {
@@ -2098,7 +2088,7 @@
} catch (RemoteException ex) {
// ignore it
}
- return result;
+ return (result == null) ? Collections.emptyList() : result;
}
/**
@@ -2128,7 +2118,7 @@
* @throws UnsupportedOperationException If the device does not have
* {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
*/
- public List<SubscriptionInfo> getAccessibleSubscriptionInfoList() {
+ public @Nullable List<SubscriptionInfo> getAccessibleSubscriptionInfoList() {
List<SubscriptionInfo> result = null;
try {
@@ -2139,7 +2129,7 @@
} catch (RemoteException ex) {
// ignore it
}
- return result;
+ return (result == null) ? Collections.emptyList() : result;
}
/**